commit 589b6ab339ed0c67829fd946e865f361de699096 Author: raphael60650 Date: Sun Jun 7 00:09:58 2020 +0200 v3.1 Dernière archive du projet diff --git a/LauncherTexture.zip b/LauncherTexture.zip new file mode 100644 index 0000000..1cd9b69 Binary files /dev/null and b/LauncherTexture.zip differ diff --git a/Thorium PRE 3.1.py b/Thorium PRE 3.1.py new file mode 100644 index 0000000..bb746d2 --- /dev/null +++ b/Thorium PRE 3.1.py @@ -0,0 +1,781 @@ +LauncherVersion = "Pre 3.1" + +import json, os, uuid, zipfile, urllib.request, traceback, subprocess, sys, ssl +from tkinter import * +from PIL import Image, ImageTk +from tkinter import ttk, messagebox, filedialog +from threading import Thread + +SSL_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + +Libraries = [] # Liste de toutes les librairies à charger +GameDirectory = ".Thorium/" # Dossier du jeu +Texture = {} # Texture du launcher +ProfilUsed = {} # Dictionnaire utilisé pour lancer le jeu +AllProfil = {} # Liste de tous les profils chargés +Config = {} # Liste des options du launcher + +HelpDownloadLink = "https://drive.google.com/uc?export=download&id=1AbmNzrkpY53DhBP51Uwjub1Hvo0oYeUf" +VersionDownloadLink = "https://drive.google.com/uc?export=download&id=1K5fnVWZer0A5HHYwDLPS166iK5l7QPbE" +try: + Data = urllib.request.urlopen(context = SSL_context, url = HelpDownloadLink) + with open("Help.json", "wb") as File: File.write(Data.read()) +except: pass +try: + with open("Help.json", "rb") as File: Help = json.load(File) +except: Help = {} # Donnée d'aide + + +def SaveConfig(event = None): + Config["Option"] = { + + "Custom Args JVM": ProfilUsed["Custom Args JVM"].get(), # Ligne de code supplémentaire + "RAM Min": ProfilUsed["RAM Min"].get(), # RAM Min + "RAM Max": ProfilUsed["RAM Max"].get(), # RAM Max + "Authentificate": ProfilUsed["Authentificate"].get(), # Activé l'authentification + "RememberToken": ProfilUsed["RememberToken"].get(), # Se souvenir du token + "UseOfficialLauncher": ProfilUsed["UseOfficialLauncher"].get(), # Va chercher dans les données du launcher officiel le token + "Pseudo": ProfilUsed["Pseudo"].get(), + "Logs": ProfilUsed["Logs"].get(), + } + + Config["SelectProfil"] = SelectProfil.get() + Config["FastServerIP"] = FastServerIP.get() + + with open("Config.json", "w") as File: File.write(json.dumps(Config)) +def LoadConfig(event = None): + global Config + try: + with open("Config.json", "r") as File: Config = json.load(File) + + for keys in list(Config["Option"].keys()): + ProfilUsed[keys].set(Config["Option"][keys]) + + SelectProfil.set(Config["SelectProfil"]) + FastServerIP.set(Config["FastServerIP"]) + except: print("Impossible de charger la config") +if not(os.path.exists(GameDirectory + "versions/")): os.makedirs(GameDirectory + "versions/") +if not(os.path.exists(GameDirectory + "launcher_profiles.json")): + with open(GameDirectory + "launcher_profiles.json", "w") as File: + File.write("""{ + "settings": {}, + "launcherVersion": {}, + "clientToken": "", + "profiles": {}, + "analyticsFailcount": 0, + "selectedUser": {}, + "analyticsToken": "", + "selectedProfile": "", + "authenticationDatabase": {} + }""")# Crée le fichier launcher_profiles.json, nécéssaire pour installer Forge + +def AssetsSearch(Version): + if not(os.path.exists(GameDirectory + "versions/" + Version)): os.makedirs(GameDirectory + "versions/" + Version) # Crée un dossier s'il n'existe pas, sur la version désiré + + if not(os.path.exists(GameDirectory + "versions/{0}/{0}.json".format(Version))): # Vérifie si le fichier existe déjà + LaunchStatut.config(text = "Téléchargement du fichier VersionManifest") + try: VersionManifest = json.load(urllib.request.urlopen(context = SSL_context, url = "https://launchermeta.mojang.com/mc/game/version_manifest.json")) # Sinon va chercher le fichier contenant des informations sur des versions + except: print(" | Impossible de télécharger le fichier VersionManifest") # Log + else: print(" | Succès !") + + for VersionInfo in VersionManifest["versions"]: # Cherche la séquence d'information correspondant à la version + if VersionInfo["id"] == Version: + VersionData = VersionInfo + break + with open(GameDirectory + "versions/{0}/{0}.json".format(Version), "wb") as File: # Télécharge le fichier + LaunchStatut.config(text = "Téléchargement du fichier Version") + try: File.write(urllib.request.urlopen(context = SSL_context, url = VersionData["url"]).read()) + except: print(" | Impossible de télécharger le fichier Version") # Log + else: print(" | Succès !") + + with open(GameDirectory + "versions/{0}/{0}.json".format(Version)) as JsonFile: # Charge le fichier Json + Json = json.load(JsonFile) + + if not(os.path.exists(GameDirectory + "versions/{0}/{0}.jar".format(Version))): # Vérifie si le fichier .jar existe + with open(GameDirectory + "versions/{0}/{0}.jar".format(Version), "wb") as File: # Télécharge le fichier + LaunchStatut.config(text = "Téléchargement du fichier Client") + try: File.write(urllib.request.urlopen(context = SSL_context, url = Json["downloads"]["client"]["url"]).read()) + except: print(" | Impossible de télécharger le fichier Client") # Log + else: print(" | Succès !") + + if list(Json.keys()).count("inheritsFrom") > 0: AssetsSearch(Json["inheritsFrom"]) # Si le fichier à une dépendance, la scan également + else: + if not(os.path.exists(GameDirectory + "assets/indexes/")): os.makedirs(GameDirectory + "assets/indexes/") # Crée un dossier pour les assets + if not(os.path.exists(GameDirectory + "assets/indexes/%s.json" % Json["assetIndex"]["id"])): # Crée le fichier pour les assets + LaunchStatut.config(text = "Téléchargement du fichier assetIndex") + try: JsonAssets = urllib.request.urlopen(context = SSL_context, url = Json["assetIndex"]["url"]).read() # Va chercher le fichier contenant les assets + except: print(" | Impossible de télécharger le fichier assetIndex") # Log + else: print(" | Succès !") + + with open(GameDirectory + "assets/indexes/%s.json" % Json["assetIndex"]["id"], "wb") as File: File.write(JsonAssets) # Sauvegarde de dossier des assets + with open(GameDirectory + "assets/indexes/%s.json" % Json["assetIndex"]["id"]) as File: JsonAssets = json.load(File) + + for AssetsKeys in JsonAssets.keys(): # Fouille dans le fichier les assets + for Assets in JsonAssets[AssetsKeys]: + AssetsHash = JsonAssets[AssetsKeys][Assets]['hash'] + AssetsPrefix = AssetsHash[:2] + + if not(os.path.exists(GameDirectory + "assets/%s/%s" % (AssetsKeys, AssetsPrefix))): # Si le dossier n'existe pas, + os.makedirs(GameDirectory + "assets/%s/%s" % (AssetsKeys, AssetsPrefix)) # le crée + + if not(os.path.exists(GameDirectory + "assets/%s/%s/%s" % (AssetsKeys, AssetsPrefix, AssetsHash))): # Si le fichier n'existe pas, + with open(GameDirectory + "assets/%s/%s/%s" % (AssetsKeys, AssetsPrefix, AssetsHash), 'wb') as File: # Le crée + LaunchStatut.config(text = "Téléchargement de l'asset : " + Assets) + try: File.write(urllib.request.urlopen(context = SSL_context, url = "http://resources.download.minecraft.net/%s/%s" % (AssetsPrefix, AssetsHash)).read()) # Téléchargement + except: print(" | Impossible de télécharger l'asset : " + Assets) # Log + else: print(" | Succès !") +def LibrariesSearch(JsonFile): + with open(JsonFile, "rb") as JsonFile: Json = json.load(JsonFile) # Charge le fichier Json + + for Lib in Json["libraries"]: # Recherche toutes les librairies noté à l'intérieur + ActualLib = Lib.copy() # Sert à faire une recherche approfondie + while True: + try: + package, name, version = ActualLib["name"].split(':') # Convertie le nom en lien de fichier + EchecLibUrl = "{0}/{1}/{2}/{1}-{2}.jar".format(package.replace('.', '/'), name, version) + LibPath = "libraries/" + EchecLibUrl + Libraries.append(LibPath) # Ajoute la librairies à la liste des librairies total + + if not(os.path.exists(GameDirectory + LibPath)): # Vérifie l'existence de la librairie (classique) + if not(os.path.exists(os.path.dirname(GameDirectory + LibPath))): os.makedirs(os.path.dirname(GameDirectory + LibPath)) + with open(GameDirectory + LibPath, "wb") as LibFile: # Sinon la télécharge + + try: + LaunchStatut.config(text = "Téléchargement du fichier : " + ActualLib["name"]) # Log + Download = urllib.request.urlopen(context = SSL_context, url = ActualLib["downloads"]["artifact"]["url"]).read() + LibFile.write(Download) # Téléchargement + + except: + try: + print(" | -> Téléchargement du fichier (sans echec)") + Download = urllib.request.urlopen(context = SSL_context, url = "https://libraries.minecraft.net/" + EchecLibUrl).read() + LibFile.write(Download) + except: + print(" | Impossible de télécharger la librairie (classique) : " + LibPath + "\n | (Vérifier si Forge est bien installé)") # Log + else: + print(" |") + else: print(" | Succès !") + + + if list(ActualLib.keys()).count("natives") > 0: # Vérifie que le fichier Json contient une information sur les "natives" + if list(ActualLib['natives'].keys()).count("windows") > 0: # Si le fichier contient une donnée "natives" à propos de l'OS "windows" + + Native = ActualLib["natives"]["windows"].replace('${arch}', "64") # Recherche une librairies contenant les natives + rlPath = "{0}/{1}/{2}/{1}-{2}-{3}.jar".format(package.replace('.', '/'), name, version, Native) # Crée un lien vers ces librairies + rPath = "libraries/" + rlPath # Le rl est plus utile au téléchargement, le r est plus utile au chemin uri + LibRep = ActualLib.get('url', 'https://libraries.minecraft.net/') + + try: + if not(os.path.exists(GameDirectory + rPath)): # Vérifie l'existence de la librairie (native) + if not(os.path.exists(os.path.dirname(GameDirectory + rPath))): os.makedirs(os.path.dirname(GameDirectory + rPath)) + with open(GameDirectory + rPath, "wb") as LibFile: # Sinon la télécharge + + LaunchStatut.config(text = "Téléchargement du fichier : " + Native) # Log + try: LibFile.write(urllib.request.urlopen(context = SSL_context, url = LibRep + rlPath).read()) # Téléchargement + except: print(" | Impossible de télécharger la librairie (native) %s" % str(LibPath)) # Log + else: print(" | Succès !") + + with zipfile.ZipFile(GameDirectory + rPath, 'r') as LibFile: # Extrait les natives de ces librairies + for name in LibFile.namelist(): + if not (name.startswith('META-INF') or name.startswith('.')): LibFile.extract(name, GameDirectory + 'natives') + + except zipfile.BadZipFile: + print("Erreur pour l'extraction de : " + LibPath) # Si le fichier est corrompu, il est évité + os.remove(GameDirectory + rPath) + + except Exception as e: + print("Erreur : " + traceback.format_exc()) # En cas d'erreur, log le nom de la librairies problématique + if list(ActualLib.keys()).count("downloads") > 0: ActualLib = ActualLib["downloads"] # Recommence la cherche en allant plus loin dans les données + elif list(ActualLib.keys()).count("artifact") > 0: ActualLib = ActualLib["artifact"] + else: + print("Impossible d'aller plus loin /!\\ : " + str(ActualLib)) + break + else: + break # Passe à la librairie suivante + + + if list(Json.keys()).count("inheritsFrom") > 0: # Vérifie une dépendance ( utilisé par Forge ) + return LibrariesSearch(GameDirectory + "versions/{0}/{0}.json".format(Json["jar"])) # Fonction récursif pour la dépendance + else: + if list(Json.keys()).count("assetIndex"): AssetIndex = Json["assetIndex"]["id"] + return AssetIndex + +Fen = Tk() + +ScreenWidth = Fen.winfo_screenwidth() +ScreenHeight = Fen.winfo_screenheight() + +Fen.title(u"Thorium λ (%s)" % LauncherVersion) +if os.path.exists("icon.ico"): Fen.iconbitmap("icon.ico") +Fen.resizable(width=False, height=False) +Fen.columnconfigure(0, weight = 1) + +def LoadTexture(Path = "LauncherTexture.zip"): + if os.path.exists(Path): + with zipfile.ZipFile(Path) as File: + for Name in File.namelist(): + File.extract(Name, "tempTexture") + Texture[Name] = ImageTk.PhotoImage(Image.open("tempTexture/" + Name)) + + else: + messagebox.showerror("Erreur", "Impossible de charger les textures") +LoadTexture() + +def ShowMenu(SelectMenu = "MainMenu"): + for _Menu, MenuWidget in Menu.items(): MenuWidget.grid_forget() + Menu[SelectMenu].grid(row = 1, column = 1) + +Menu = {} # Liste contenant tout les widget "Menu", sert a changer de menu plus facilement +Menu["MainMenu"] = Frame(Fen) # Menu Principal +ShowMenu() + +NewsDisplayScroll = Scrollbar(Menu["MainMenu"], orient = VERTICAL) # Scrollbar des nouveautés +NewsDisplayScroll.grid(row = 1, column = 3, sticky = "NSW") +NewsDisplay = Canvas(Menu["MainMenu"], yscrollcommand = NewsDisplayScroll.set, scrollregion = (0, 0, 300, 800), width = ScreenWidth / 3, height = ScreenHeight / 3) # Display des nouveautés +NewsDisplay.grid(row = 1, column = 1, sticky = "NEWS", columnspan = 2) +NewsDisplayScroll.config(command = NewsDisplay.yview) # Configuration pour bind la Scrollbar au Canvas + +Fastbar = LabelFrame(Menu["MainMenu"], text = "Lancement") # Barre inférieur qui permet d'afficher les options de lancement +Fastbar.grid(row = 4, column = 1) + +Label(Fastbar, text = "Profil :").grid(row = 1, column = 1, rowspan = 2) +SelectProfil = StringVar() # Sélection du profil +ProfilBox = ttk.Combobox(Fastbar, value = AllProfil, textvariable = SelectProfil) +ProfilBox.grid(row = 1, column = 2, rowspan = 2) +ProfilBox.bind("<>", SaveConfig) + +Label(Fastbar, text = " Pseudo / Email :").grid(row = 1, column = 3, padx = 5) +Label(Fastbar, text = " Mot De Passe :").grid(row = 2, column = 3, padx = 5) +ProfilUsed["Pseudo"] = StringVar(value = "ThoriumPlayer") +ProfilUsed["Password"] = StringVar() +PseudoBox = Entry(Fastbar, textvariable = ProfilUsed["Pseudo"]) +PseudoBox.grid(row = 1, column = 4) +PasswordBox = Entry(Fastbar, show = "*", textvariable = ProfilUsed["Password"]) +PasswordBox.grid(row = 2, column = 4) + +def Auth(Email, password): + try: + print("- Authentification par défaut") + if ProfilUsed["Authentificate"].get(): + data = {'agent': {'name': 'Minecraft', 'version': 1}, 'username': Email, + 'password': password} + req = urllib.request.Request(url='https://authserver.mojang.com/authenticate', data=json.dumps(data).encode(), headers={'Content-Type': 'application/json'}) + jsonData = json.loads(urllib.request.urlopen(context = SSL_context, url = req).read()) + Pseudo = jsonData['selectedProfile']['name'] + Token = jsonData['accessToken'] + UUID = jsonData['selectedProfile']['id'] + + if ProfilUsed["RememberToken"].get(): + Config["Authentification"] = {"Pseudo": Pseudo, "Token": Token, "UUID": UUID} + + return(Pseudo, Token, UUID, False) # Pseudo, Token, UUID & Erreur ? + else: raise Exception + + except: + try: + print("- Authentification par souvenir") + if list(Config.keys()).count("Authentification") > 0 and ProfilUsed["RememberToken"].get(): + messagebox.showwarning("Attention", "L'authentification à échoué. Tentative avec des données antérieures...") # Authentification par donnée antérieure + return(Config["Authentification"]["Pseudo"], Config["Authentification"]["Token"], Config["Authentification"]["UUID"], False) + else: raise Exception + except: + try: + print("- Authentification par launcher officiel") + if ProfilUsed["UseOfficialLauncher"].get(): + with open('%s\\AppData\\Roaming\\.minecraft\\launcher_profiles.json' % os.getenv('HOME'), "r") as File: + OfficialLauncherData = json.load(File) + + AuthData = OfficialLauncherData['authenticationDatabase'][list(OfficialLauncherData['authenticationDatabase'].keys())[0]] # Authentification par le launcher officiel + UUID = list(AuthData['profiles'].keys())[0] + Pseudo, Token = AuthData['profiles'][profile_id]['displayName'], AuthData['accessToken'] + + return(Pseudo, Token, UUID, False) + else: raise Exception + except: + print("- Authentification en mode Echec") + return(Email, "ERROR", str(uuid.uuid3(type('', (), dict(bytes=b''))(), ProfilUsed["Pseudo"].get())), True) +def LaunchGame(Profil): + CancelStartGame = False # Dans le cas ou une erreur survient, l'utilisateur peut être mener à annuler le lancement du jeu. + + LaunchProgress["value"] = 1 + LaunchStatut.config(text = "Authentification") # Log + Pseudo, Token, UUID, AuthSuccess = Auth(ProfilUsed["Pseudo"].get(), ProfilUsed["Password"].get()) + if AuthSuccess and ProfilUsed["Authentificate"].get(): + if not(messagebox.askyesno("Erreur", "Impossible de s'authentifier. Voulez-vous lancer le jeu en version offline ?")): CancelStartGame = True + + LaunchProgress["value"] = 2 + if not(Profil["ZipFileInstalled"]) and not(CancelStartGame): # Si les fichiers + try: + for ZipFile in Profil["AddZipFile"]: + LaunchStatut.config(text = "Téléchargement des fichiers additionnels... (Peut être long !)") # Log + if ZipFile != "": + with open("AdditionnalFile.temp", "wb") as File: + File.write(urllib.request.urlopen(context = SSL_context, url = ZipFile).read()) + + with zipfile.ZipFile("AdditionnalFile.temp") as File: + File.extractall(GameDirectory + Profil["DirectoryFile"]) + except Exception as e: + if not(messagebox.askyesno("Erreur", "Une erreur est survenue pendant le téléchargement des fichiers additionnels.\ + Souhaitez vous vraiment lancer le jeu ? (Ceci peut engendrer des dysfonctionnements !)\n\n\n\n" + str(e))): + CancelStartGame = True + + if not(CancelStartGame): + global CmdLine + + CmdLine = "java -Djava.library.path=natives " # Ligne de commande de lancement + CmdLine += "-Xmn%iM -Xmx%iM " % (ProfilUsed["RAM Min"].get(), ProfilUsed["RAM Max"].get()) + JsonPath = GameDirectory + r"versions/{0}/{0}.json".format(Profil["Json"]) # Lien uri du fichier Json + + if os.path.exists(JsonPath): # Vérifie si le fichier .json existe + with open(JsonPath, "rb") as JsonFile: JsonFile = json.load(JsonFile) + if list(JsonFile.keys()).count("jar") > 0: VersionName = JsonFile["jar"] # Si le Json n'est pas officiel, prend la version du .jar + + else: VersionName = JsonPath.split("/")[-2] # Sinon, utilise le nom du chemin pour déterminer la version + else: VersionName = JsonPath.split("/")[-2] + AssetIndex = VersionName # Sert à initialiser la valeur de l'AssetIndex, qui est actualiser dans LibrariesSearch + + LaunchProgress["value"] = 3 + LaunchStatut.config(text = "Recherche d'assets") # Log + AssetsSearch(VersionName) # Rafraichi les Assets + + LaunchProgress["value"] = 4 + LaunchStatut.config(text = "Recherche de librairies") # Log + AssetIndex = LibrariesSearch(JsonPath) # Rafraichi les Librairies, et détermine également la valeur "AssetIndex" + + LaunchProgress["value"] = 5 + LaunchStatut.config(text = "Lancement du jeu") # Log + with open(JsonPath, "rb") as JsonFile: JsonFile = json.load(JsonFile) + ReplaceValue = {"${auth_player_name}": Pseudo, # Valeur à remplacer dans la ligne de commande + "${version_name}": VersionName, # Nom de la version + "${game_directory}": Profil["DirectoryFile"], # Dossier du jeu, déjà placer dans la commande avec le "cd GameDirectory" + "${assets_root}": "assets", # Dossier des assets + "${assets_index_name}": AssetIndex, # Sous dossier des assets + "${auth_uuid}": UUID, # uuid (ici, offline) + "${auth_access_token}": Token, # Token d'accès (pour compte officiel) + "${user_type}": "Forge", # Je sais pas a quoi sa sert + "${version_type}": "\"Thorium - Par Raphael60650\"", # Le message ici est customisable :) + "${user_properties}": "{}"} + + CmdLine += "-cp \"%s\" %s " % (";".join(Libraries) + ";versions\\{0}\\{0}.jar".format(VersionName), JsonFile["mainClass"]) # Formatage de la ligne de commande + try: CmdLine += JsonFile["minecraftArguments"] # Dans la majorité des fichiers .json + except: CmdLine += " ".join([part for part in JsonFile["arguments"]["game"] if type(part) == str]) # .json comme celui de la 1.13 + + for Old in ReplaceValue.keys(): + print("Java JVM : " + str(Old) + " -> " + str(ReplaceValue[Old])) + CmdLine = CmdLine.replace(Old, str(ReplaceValue[Old])) # Remplacement des valeurs de ReplaceValue + + print(CmdLine) # Log la ligne de commande + GameProcess = subprocess.Popen("cd %s && %s" % (GameDirectory, CmdLine), shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE) # Lance le jeu + + SaveConfig() + if ProfilUsed["Logs"].get(): + + LogsFen = Toplevel() + try: LogsFen.iconbitmap("icon.ico") + except: pass + LogsText = Text(LogsFen) + LogsText.grid(row = 1, column = 1, sticky = "NEWS") + + def RefreshLog(): + while not(GameProcess.poll()): # Tant que le jeu n'est pas fermé + for Line in GameProcess.stdout: LogsText.insert(END, Line) # Ligne normal + for Line in GameProcess.stderr: LogsText.insert(END, Line) # Ligne d'erreur + LogsText.see(END) + + LogsFen.after(10, RefreshLog) + + RefreshLog() + + LaunchProgress["value"] = 0 + LaunchStatut.config(text = "") # Log + + +LaunchStatut = Label(Menu["MainMenu"], text = "") +LaunchStatut.grid(row = 2, column = 1, columnspan = 2) + +LaunchProgress = ttk.Progressbar(Menu["MainMenu"], orient = HORIZONTAL, maximum = 5) # Barre de chargement +LaunchProgress.grid(row = 3, column = 1, columnspan = 2, sticky = "NEWS") + +Button(Fastbar, text = "Démarrer", relief = RIDGE, command = lambda: Thread(target = lambda: LaunchGame(AllProfil[SelectProfil.get()])).start()).grid(row = 3, column = 1, columnspan = 3, sticky = "NEWS") # Bouton pour lancer le jeu +Button(Fastbar, text = "Options", relief = RIDGE, command = lambda: ShowMenu("Option")).grid(row = 3, column = 4, sticky = "NEWS") # Bouton d'accès au menu des options + + +FastServerInfo = LabelFrame(Menu["MainMenu"], text = "Serveur") +FastServerInfo.grid(row = 4, column = 2, rowspan = 2, sticky = "NEWS") +FastServerIP = StringVar(value = "Thorium.omgcraft.fr") +FastServerEntry = Entry(FastServerInfo, textvariable = FastServerIP) +FastServerEntry.grid(row = 1, column = 1) + +ServerStatut = Label(FastServerInfo, text = "", font = ("Purisa", 10)) +ServerStatut.grid(row = 2, column = 1, sticky = "WE") + +ServerPlayer = Label(FastServerInfo, text = "0 / 0", font = ("Purisa", 10)) +ServerPlayer.grid(row = 3, column = 1, sticky = "WE") + +try: + import mcquery + def RefreshFastServer(): + query = mcquery.MineStat(FastServerIP.get(), 15040) + + if query.online: + ServerStatut.config(text = "Ouvert", fg = "green") + ServerPlayer.config(text = str(query.current_players) + " / " + str(query.max_players)) + else: ServerStatut.config(text = "Fermé", fg = "red") + + Fen.after(1000, RefreshFastServer) + RefreshFastServer() +except: pass + + +def RefreshNews(): + NewsDisplay.create_rectangle(0, 0, 1000, 1000, fill = "green") + NewsDisplay.create_image(0, 0, image = Texture["th_background.png"]) + NewsDisplay.create_image(NewsDisplay.winfo_width() // 2, NewsDisplay.winfo_height() // 2, image = Texture["th_title.png"])# Sert a faire le rendu sur le canvas dans le menu principal, appelé juste avant le mainloop() + +#################################################################################################### +#################################################################################################### + +Menu["Option"] = Frame(Fen) +ExperimentSection = LabelFrame(Menu["Option"], text = "Expérimenté") # Section pour les paramètres destinés aux joueurs expérimentés +ExperimentSection.grid(row = 1, column = 1, sticky = "NEWS") + +ProfilUsed["Custom Args JVM"] = StringVar() # Ligne de code supplémentaire +ProfilUsed["RAM Min"] = IntVar(value = 1024) # RAM Min +ProfilUsed["RAM Max"] = IntVar(value = 2048) # RAM Max +ProfilUsed["Logs"] = IntVar(value = 0) + +Label(ExperimentSection, text = "Args JVM :").grid(row = 1, column = 1) +CustomArgsJVMEntry = Entry(ExperimentSection, textvariable = ProfilUsed["Custom Args JVM"]) +CustomArgsJVMEntry.grid(row = 1, column = 2, sticky = "NEWS") +CustomArgsJVMEntry.bind("", SaveConfig) + +Label(ExperimentSection, text = "RAM Minimum (Mo) :").grid(row = 2, column = 1) +RAMMinSpinbox = Spinbox(ExperimentSection, from_ = 1, to = 2**32, textvariable = ProfilUsed["RAM Min"], command = SaveConfig) +RAMMinSpinbox.grid(row = 2, column = 2, sticky = "NEWS") + +Label(ExperimentSection, text = "RAM Maximum (Mo) :").grid(row = 3, column = 1) +RAMMaxSpinbox = Spinbox(ExperimentSection, from_ = 1, to = 2**32, textvariable = ProfilUsed["RAM Max"], command = SaveConfig) +RAMMaxSpinbox.grid(row = 3, column = 2, sticky = "NEWS") + +Checkbutton(ExperimentSection, text = "Activer les logs", variable = ProfilUsed["Logs"], command = SaveConfig).grid(row = 4, column = 1, columnspan = 2) + +#################################################################################################### + +AuthentificationSection = LabelFrame(Menu["Option"], text = "Authentification") # Menu des options à propos de l'authentification +AuthentificationSection.grid(row = 2, column = 1, sticky = "NEWS") + +ProfilUsed["Authentificate"] = IntVar(value = 1) # Activé l'authentification +ProfilUsed["RememberToken"] = IntVar(value = 1) # Se souvenir du token +ProfilUsed["UseOfficialLauncher"] = IntVar(value = 0) # Va chercher dans les données du launcher officiel le token +Checkbutton(AuthentificationSection, text = "Authentification", variable = ProfilUsed["Authentificate"], command = SaveConfig).grid(row = 1, column = 1, sticky="W") +Checkbutton(AuthentificationSection, text = "Se souvenir du Token", variable = ProfilUsed["RememberToken"], command = SaveConfig).grid(row = 2, column = 1, sticky="W") +Checkbutton(AuthentificationSection, text = "Identification via \nle Launcher Officiel", variable = ProfilUsed["UseOfficialLauncher"], command = SaveConfig).grid(row = 3, column = 1, sticky="W") + +AuthTestLabel = Label(AuthentificationSection, text = "") +AuthTestLabel.grid(row = 5, column = 1) +def TestAuth(): + Pseudo, Token, UUID, AuthSuccess = Auth(ProfilUsed["Pseudo"].get(), ProfilUsed["Password"].get()) + AuthTestLabel.config(text = "Pseudo : %s \nUUID : %s \nAuthentification : %s" % (Pseudo, UUID, str(AuthSuccess).replace("True", "Echec").replace("False", "Réussi"))) + +Button(AuthentificationSection, text = "Tester l'authentification", command = TestAuth, relief = RIDGE).grid(row = 4, column = 1, sticky = "NEWS") + +#################################################################################################### + +ModSection = LabelFrame(Menu["Option"], text = "Mod") # Menu des options à propos des mods +ModSection.grid(row = 1, column = 2, rowspan = 2) + +MetaDataModList = Label(ModSection) +MetaDataModList.grid(row = 5, column = 1, columnspan = 3) + +ModSearchPath = StringVar(value = GameDirectory) # Variable qui contient le chemin du jeu +Entry(ModSection, textvariable = ModSearchPath).grid(row = 1, column = 1, sticky = "NSE") + +ModListbox = Listbox(ModSection, width = 50) +ModListbox.grid(row = 2, column = 1, columnspan = 2, rowspan = 2, sticky = "NEWS") + +def SelectModSearchPath(Path = "", Silent = False): + MetaData = "(" + + if not(Path): Path = filedialog.askdirectory() # Demande un dossier a fouiller + if os.path.exists(Path): # S'il existe, + if os.path.exists(Path + "/mods"): # Si un dossier mod existe à l'interieur, + if len(os.listdir(Path + "/mods")) > 0: + ModListbox.delete(0, END) + ModListDir = os.listdir(Path + "/mods") + MetaData += "mods : " + str(len(ModListDir)) + " | " + + for mods in ModListDir: + ModListbox.insert(END, mods) # fouille et insert les mods dans la liste + ModListbox.itemconfig(END, fg = "green") + ModSearchPath.set(Path) # Initialise une variable + else: + if not(Silent): messagebox.showerror("Erreur", "Le dossier sélectioné ne contient aucun mod") + else: + if not(Silent): messagebox.showerror("Erreur", "Le dossier de mod n'existe pas, veuillez lancer au moins une fois le jeu") + if os.path.exists(Path + "/disableMods"): # Fouille dans le dossier des mods désactivé + DisableModListDir = os.listdir(Path + "/disableMods") + MetaData += "mods désactivés : " + str(len(DisableModListDir)) + + for mods in DisableModListDir: + ModListbox.insert(END, mods) + ModListbox.itemconfig(END, fg = "gray") + else: os.makedirs(Path + "/disableMods") # S'il n'existe pas, le crée + else: + if not(Silent): messagebox.showerror("Erreur", "Ce dossier n'existe pas") + + MetaData += ")" + MetaDataModList.config(text = MetaData) +SelectModSearchPath(GameDirectory, True) # Fait une recherche par défaut pour chercher les mods dans le dossier + +Button(ModSection, text = "C:/", command = SelectModSearchPath, relief = RIDGE).grid(row = 1, column = 2, sticky = "W") # Entry où entrer le lien ou chercher les mods + +Button(ModSection, text = "Ouvrir", command = lambda: subprocess.Popen("explorer.exe " + os.path.abspath(ModSearchPath.get()) + "mods/", shell = True), relief = RIDGE).grid(row = 1, column = 3, sticky = "NEWS") # Bouton ouvrant un dossier vers le dossier de mods + +Label(ModSection, text = "Mod Installé", fg = "green").grid(row = 2, column = 3) +Label(ModSection, text = "Mod Désactivé", fg = "gray").grid(row = 3, column = 3) + +ModActionButton = Button(ModSection, text = "---------", relief = RIDGE, fg = "gray") # Bouton destiné à activé / désactivé les mods +ModActionButton.grid(row = 4, column = 1, columnspan = 2, sticky = "NEWS") +def ModListboxSelect(event): + def DisableModList(ModName): + os.rename(ModSearchPath.get() + "/mods/" + ModName, ModSearchPath.get() + "/disableMods/" + ModName) + SelectModSearchPath(ModSearchPath.get()) + + def EnableModList(ModName): + os.rename(ModSearchPath.get() + "/disableMods/" + ModName, ModSearchPath.get() + "/mods/" + ModName) + SelectModSearchPath(ModSearchPath.get()) + + Index = ModListbox.curselection()[-1] + ModName = ModListbox.get(Index) + if os.listdir(ModSearchPath.get() + "/mods").count(ModName) > 0: ModActionButton.config(text = "Désactivé", fg = "red", command = lambda: DisableModList(ModName)) + elif os.listdir(ModSearchPath.get() + "/disableMods").count(ModName) > 0: ModActionButton.config(text = "Activé", fg = "green", command = lambda: EnableModList(ModName)) +ModListbox.bind('<>', ModListboxSelect) + +#################################################################################################### + +ProfilSection = LabelFrame(Menu["Option"], text = "Profil") # Menu des options à propos des mods +ProfilSection.grid(row = 1, column = 3, rowspan = 2) + +def RefreshEditOption(event = None): + try: ProfilEditButton.config(fg = "black", command = lambda: ActionProfil(ProfilListbox.get(ProfilListbox.curselection()[-1]))) + except: ProfilEditButton.config(fg = "gray", command = lambda: "pass") + + +ProfilListbox = Listbox(ProfilSection) +ProfilListbox.grid(row = 1, column = 1, columnspan = 2) +ProfilListbox.bind("<>", RefreshEditOption) + +def ActionProfil(Profil = ""): + ShowMenu("ActionProfil") + if Profil: + ProfilName.set(Profil) + SelectVersion.set(AllProfil[Profil]["Json"]) + AddZipFile.set(";".join(AllProfil[Profil]["AddZipFile"])) + DirectoryFile.set(AllProfil[Profil]["DirectoryFile"]) + + VersionListbox.select_set(VersionListbox.get(0, END).index(SelectVersion.get())) # Sélectionne le nom de la version + VersionListboxSelect(None) + + +Button(ProfilSection, text = "Nouveau", relief = RIDGE, command = ActionProfil).grid(row = 2, column = 1, sticky = "WE") +ProfilEditButton = Button(ProfilSection, text = "Modifier", relief = RIDGE, fg = "gray") +ProfilEditButton.grid(row = 2, column = 2, sticky = "WE") + +Button(Menu["Option"], text = "Aide", command = lambda: ShowMenu("Help"), relief = RIDGE).grid(row = 10, column = 1, sticky = "W") +Button(Menu["Option"], text = "Menu Principal", command = lambda: ShowMenu("MainMenu"), relief = RIDGE).grid(row = 10, column = 1, columnspan = 10, sticky = "E") + +#################################################################################################### +#################################################################################################### + +Menu["ActionProfil"] = Frame(Fen) + +VersionList = {} # Dictionnaire qui contient les versions +ProfilName = StringVar(value = "Nouveau Profil") # Nom du profil +SelectVersion = StringVar() # Version sélectionné +AddZipFile = StringVar() # Archive à télécharger et dézipper +DirectoryFile = StringVar(value = ".") # Dossier dans lequel executé le jeu + +def VersionListRefresh(): + for Version in os.listdir(GameDirectory + "versions/"): + JsonPath = GameDirectory + "versions/{0}/{0}.json".format(Version) + if os.path.exists(JsonPath): + VersionList[Version] = {} + VersionList[Version]["Json"] = Version # Version du jeu, utilisé pour le lien du .json + VersionList[Version]["Type"] = "Unknown" # Par défaut, la version est inconnu + VersionList[Version]["Install"] = True # Check si la version est déjà installer + + try: VersionManifest = json.load(urllib.request.urlopen(context = SSL_context, url = "https://launchermeta.mojang.com/mc/game/version_manifest.json")) # Sinon va chercher le fichier contenant des informations sur des versions + except: print(" | Impossible de télécharger le fichier VersionManifest") # Log + else: + print(" | Succès !") + for VersionData in VersionManifest["versions"]: + if list(VersionList.keys()).count(VersionData["id"]) == 0: + VersionList[VersionData["id"]] = {} + VersionList[VersionData["id"]]["Json"] = VersionData["id"] # Version du jeu, utilisé pour le lien du .json + VersionList[VersionData["id"]]["Type"] = VersionData["type"] # Type de la version + VersionList[VersionData["id"]]["Install"] = False + else: + VersionList[VersionData["id"]]["Type"] = VersionData["type"] # Met à jour "l'inconnu" plus haut + + VersionListbox.delete(0, END) + for VersionData in VersionList: + VersionListbox.insert(END, VersionList[VersionData]["Json"]) + if VersionList[VersionData]["Type"] == "snapshot": VersionListbox.itemconfig(END, bg = "purple", fg = "white") + elif VersionList[VersionData]["Type"] == "release": VersionListbox.itemconfig(END, bg = "cyan") + elif VersionList[VersionData]["Type"] == "old_beta": VersionListbox.itemconfig(END, bg = "darkgray") + elif VersionList[VersionData]["Type"] == "old_alpha": VersionListbox.itemconfig(END, bg = "gray") + else: VersionListbox.itemconfig(END, bg = "gold") + + if VersionList[VersionData]["Install"]: VersionListbox.itemconfig(END, fg = "blue") # Si la version est déjà installé +def VersionListboxSelect(event): + Index = VersionListbox.curselection()[-1] + Name = VersionListbox.get(Index) + SelectVersion.set(Name) + LabelSelectVersion.config(text = Name) + + +Label(Menu["ActionProfil"], text = "Nom :").grid(row = 1, column = 1) +Entry(Menu["ActionProfil"], textvariable = ProfilName).grid(row = 1, column = 2, sticky = "WE") # Nom du profil + +VersionListbox = Listbox(Menu["ActionProfil"]) +VersionListbox.grid(row = 2, column = 1, columnspan = 2, sticky = "NEWS") # Liste des versions +LabelSelectVersion = Label(Menu["ActionProfil"], text = "Version : Aucune") +LabelSelectVersion.grid(row = 3, column = 1, columnspan = 2) +VersionListbox.bind('<>', VersionListboxSelect) + +HelpVersionTypeFrame = LabelFrame(Menu["ActionProfil"], text = "Type") +HelpVersionTypeFrame.grid(row = 2, column = 3) +Label(HelpVersionTypeFrame, text = "Release", bg = "cyan").grid(row = 1, column = 1, sticky = "WE") +Label(HelpVersionTypeFrame, text = "Snapshot", bg = "purple", fg = "white").grid(row = 2, column = 1, sticky = "WE") +Label(HelpVersionTypeFrame, text = "Beta", bg = "darkgray").grid(row = 3, column = 1, sticky = "WE") +Label(HelpVersionTypeFrame, text = "Beta", bg = "gray").grid(row = 4, column = 1, sticky = "WE") +Label(HelpVersionTypeFrame, text = "Inconnu", bg = "gold").grid(row = 5, column = 1, sticky = "WE") +Label(HelpVersionTypeFrame, text = "Installé", fg = "blue").grid(row = 6, column = 1, sticky = "WE", pady = 5) + +Label(Menu["ActionProfil"], text = "Archive additionnel :").grid(row = 4, column = 1) +Entry(Menu["ActionProfil"], textvariable = AddZipFile).grid(row = 4, column = 2, sticky = "WE") # Archive additionnel à télécharger + +Label(Menu["ActionProfil"], text = "Dossier de jeu :").grid(row = 5, column = 1) +Entry(Menu["ActionProfil"], textvariable = DirectoryFile).grid(row = 5, column = 2, sticky = "WE") # Dossier de jeu +Button(Menu["ActionProfil"], text = "...", relief = RIDGE, command = lambda: DirectoryFile.set(filedialog.askopenfilenames())).grid(row = 5, column = 3, sticky = "W") + +def RefreshVersionList(): + ProfilBox.config(values = list(AllProfil.keys())) # Actualise la liste des profil dans le menu principal + + ProfilListbox.delete(0, END) + for ProfilData in AllProfil: + ProfilListbox.insert(END, ProfilData) + +def SaveProfil(): + EditedProfil = {} + EditedProfil["Json"] = SelectVersion.get() + EditedProfil["AddZipFile"] = AddZipFile.get().split(";") + EditedProfil["DirectoryFile"] = DirectoryFile.get() + EditedProfil["ZipFileInstalled"] = False + + if ProfilName.get(): # Vérifie si un nom de profil à été saisi + if list(AllProfil.keys()).count(ProfilName.get()) != 0: # Vérifie s'il existe déjà : + if not(messagebox.askyesno("Attention", "Voulez-vous remplacer le profil %s ?" % ProfilName.get())): + return # Stop la fonction + + AllProfil[ProfilName.get()] = EditedProfil.copy() # Si oui l'inclue dans tout les profils chargés + + RefreshVersionList() # Actualise les listes de versions + ShowMenu("Option") # Affiche le menu des options + + if os.path.exists("Profil.json.backup"): os.remove("Profil.json.backup") + if os.path.exists("Profil.json"): os.rename("Profil.json", "Profil.json.backup") + with open("Profil.json", "w") as File: json.dump(AllProfil, File) + + else: messagebox.showerror("Erreur", "Veuillez entrer un nom de profil") +def DeleteProfil(): + if messagebox.askyesno("Attention", "Souhaitez-vous vraiment effacer ce profil ? (%s)" % ProfilName.get()): + if list(AllProfil.keys()).count(ProfilName.get()) > 0: # Dans le cas où l'utilisateur quitte par la sauvegarde + AllProfil.pop(ProfilName.get()) + with open("Profil.json", "w") as File: json.dump(AllProfil, File) + messagebox.showinfo("", "Profil effacé") + + try: SelectProfil.set(list(AllProfil.keys())[-1]) + except: pass + + RefreshVersionList() + RefreshEditOption() # Actualise le bouton d'édition + ShowMenu("Option") +def AskOptionMenu(): + if messagebox.askyesno("Attention", "Souhaitez-vous quitter sans sauvegarder ?"): ShowMenu("Option") + +Button(Menu["ActionProfil"], text = "Sauvegarder", command = SaveProfil, relief = RIDGE).grid(row = 10, column = 1, sticky = "WS", pady = 5) +Button(Menu["ActionProfil"], text = "Effacer", command = DeleteProfil, relief = RIDGE).grid(row = 10, column = 2, columnspan = 9, sticky = "WS", pady = 5) +Button(Menu["ActionProfil"], text = "Retour", command = AskOptionMenu, relief = RIDGE).grid(row = 10, column = 2, columnspan = 10, sticky = "ES", pady = 5) + +VersionListRefresh() + +#################################################################################################### +#################################################################################################### + +Menu["Help"] = Frame(Fen) + +def HelpRefresh(event = None): + try: HelpText.delete("0.0", END) + except: pass + HelpText.insert(END, Help[HelpListbox.get(HelpListbox.curselection())]) + +HelpListbox = Listbox(Menu["Help"], width = 25) +HelpListbox.grid(row = 1, column = 1, sticky = "NEWS") +HelpListbox.bind("<>", HelpRefresh) + +HelpText = Text(Menu["Help"], wrap = WORD) +HelpText.grid(row = 1, column = 2, sticky = "NEWS") + +for Nom in list(Help.keys()): HelpListbox.insert(END, Nom) + + +Button(Menu["Help"], text = "Menu Principal", command = lambda: ShowMenu("MainMenu"), relief = RIDGE).grid(row = 10, column = 1, columnspan = 10, sticky = "E") + +#################################################################################################### +#################################################################################################### + +def LoadProfil(): + global AllProfil + if os.path.exists("Profil.json"): + try: + with open("Profil.json") as File: AllProfil = json.load(File) + except Exception as e: + print(e) + messagebox.showerror("Erreur", "Le fichier des profils est illisible. Lecture d'une sauvegarde...") + if os.path.exists("Profil.json.backup"): + try: + with open("Profil.json.backup") as File: AllProfil = json.load(File) + except: + messagebox.showerror("Erreur", "Sauvegarde illisible.") + + ProfilBox.config(values = list(AllProfil.keys())) # Actualise la liste des profil dans le menu principal + + ProfilListbox.delete(0, END) + for ProfilData in AllProfil: + ProfilListbox.insert(END, ProfilData) + +try: SelectProfil.set(list(AllProfil.keys())[-1]) +except: pass + +LoadConfig() +LoadProfil() +#################################################################################################### +#################################################################################################### + +try: + VersionJson = json.load(urllib.request.urlopen(context = SSL_context, url = VersionDownloadLink)) + if VersionJson["LastRelease"] != LauncherVersion: + if messagebox.askyesno("Mise à jour", "Une mise à jour est disponible (%s -> %s)" % (LauncherVersion, VersionJson["LastRelease"])): + + if os.path.exists("../MAJ.pyw"): MAJProgramName = "MAJ.pyw" + else: MAJProgramName = "MAJ.exe" + subprocess.Popen('cd ' + os.path.abspath("..") + ' && start ' + os.path.abspath("../%s" % MAJProgramName), shell = True) + sys.exit() + +except Exception as e: print(str(e)) + +#################################################################################################### +#################################################################################################### +Fen.after(1, RefreshNews) +mainloop() diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..291e6a6 Binary files /dev/null and b/icon.ico differ