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()