#!/usr/bin/env python3 import sys oldexcepthook = sys.excepthook def newexcepthook(type,value,traceback): oldexcepthook(type,value,traceback) input("Press ENTER to quit.") sys.excepthook = newexcepthook import os p = os.path.join pUp = os.path.dirname s = False if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): s = os.path.realpath(sys.executable) else: s = os.path.realpath(__file__) sp = pUp(s) import configparser import subprocess import json import hashlib import platform import urllib.request def download(url,decode = "utf-8"): request = urllib.request.Request(url,headers={"User-Agent":"Mozilla/5.0"}) response = urllib.request.urlopen(request) rt = response.read() if decode == False: return rt return response.read().decode(decode) def readFile(file,decode = "utf-8"): fileh = False fileh = open(file,"rb") data = fileh.read() fileh.close() if decode == False: return data return data.decode(decode) def fileDl(url,file,read = True,decode = "utf-8"): if os.path.isfile(file): if read == True: return readFile(file,decode) return print(url) data = download(url,False) fileh = open(tmpFile,"wb") fileh.write(data) fileh.close() if pUp(file) != "" and not os.path.isdir(pUp(file)): os.makedirs(pUp(file)) os.rename(tmpFile,file) if read == False: return if decode == False: return data return data.decode(decode) def readJsonFile(file): fileh = open(file,"r") data = fileh.read() fileh.close() return json.loads(data) def findInChain(versionsPath,version,path): versionPath = p(versionsPath,version) clientJson = readJsonFile(p(versionPath,version + ".json")) curPath = clientJson.copy() success = True for dir in path: if dir in curPath: curPath = curPath[dir] else: success = False break if success == False: return findInChain(versionsPath,clientJson["inheritsFrom"],path) return curPath def findInChainDeepest(versionsPath,version,path): found = False while True: versionPath = p(versionsPath,version) clientJson = readJsonFile(p(versionPath,version + ".json")) curPath = clientJson.copy() for dir in path: if dir in curPath: curPath = curPath[dir] else: if "inheritsFrom" in clientJson: version = clientJson["inheritsFrom"] continue else: curPath = False break found = curPath if "inheritsFrom" in clientJson: version = clientJson["inheritsFrom"] continue else: break if found == False: raise return found def processVersion(versionsPath,libraryPath,nativePath,version): versionPath = p(versionsPath,version) clientJson = readJsonFile(p(versionPath,version + ".json")) libraries = [] natives = [] arguments = [] downloadLibraries = {} if "inheritsFrom" in clientJson: _,libraries,downloadLibraries,natives,arguments = processVersion(versionsPath,libraryPath,nativePath,clientJson["inheritsFrom"]) for library in clientJson["libraries"]: if "downloads" in library: if "artifact" in library["downloads"]: library["downloads"]["artifact"]["path"] = library["downloads"]["artifact"]["path"].replace("/",os.path.sep) libraries.append(p(libraryPath,library["downloads"]["artifact"]["path"])) if "classifiers" in library["downloads"]: native = False if "natives-" +lv["osName"] in library["downloads"]["classifiers"]: native = library["downloads"]["classifiers"]["natives-" +lv["osName"]] if lv["osName"] == "macos" and native == False and "natives-osx" in library["downloads"]["classifiers"]: native = library["downloads"]["classifiers"]["natives-osx"] if native != False: native["path"] = native["path"].replace("/",os.path.sep) natives.append(p(nativePath,native["path"])) elif "name" in library: #net.fabricmc:sponge-mixin:0.9.2+mixin.0.8.2 libSplit = library["name"].rsplit(":",1) libVersion = libSplit[1] libName = libSplit[0].rsplit(":",1)[1] libPath = libSplit[0].replace(".","/").replace(":","/") + "/" + libVersion + "/" +libName+ "-" +libVersion+ ".jar" library["path"] = libPath.replace("/",os.path.sep) libraries.append(p(libraryPath,library["path"])) if "url" in library: fullUrl = library["url"] while len(fullUrl) > 0 and fullUrl[-1] == "/": fullUrl = fullUrl[:-1] fullUrl = fullUrl + "/" + libPath downloadLibraries[p(libraryPath,library["path"])] = fullUrl libraries.append(p(versionPath,version + ".jar")) if "arguments" in clientJson: for arg in clientJson["arguments"]["game"]: if type(arg) != str: continue arguments.append(arg) elif "minecraftArguments" in clientJson: margs = clientJson["minecraftArguments"].replace('"',"").split(" ") for arg in margs: if type(arg) != str: continue arguments.append(arg) return clientJson,libraries,downloadLibraries,natives,arguments def main(): print("Reading config...") config = configparser.ConfigParser() config.read(os.path.splitext(s)[0] + ".ini") global lv lv = config["default"] for var in lv: glbs = globals() for glb in glbs: lv[var] = lv[var].replace("$+" +glb+ "$",str(glbs[glb])) lcs = locals() for lc in lcs: lv[var] = lv[var].replace("$" +lc+ "$",str(lcs[lc])) if lv["osName"] == "": lv["osName"] = platform.system().lower() if lv["osName"] == "darwin": lv["osName"] = "macos" if len(sys.argv) > 1: for arg in sys.argv[1:]: argSplit = arg.split("=",1) if len(argSplit) > 1: lv[argSplit[0]] = argSplit[1] else: lv[argSplit[0]] = True if not lv["osName"] in ["windows","linux","macos"]: print("Warning, unsupported OS detected: " +lv["osName"]) print("Needs to be either windows, linux or macos. Define it with osName=name in the config.") print("") if not "version" in lv: lv["version"] = input("Version ID: ") if not "name" in lv: lv["name"] = input("Player name: ") global tmpFile tmpFile = p(lv["gamePath"],"file.tmp") if os.path.isfile(tmpFile): os.remove(tmpFile) print("Scanning .json(s)...") versionsPath = p(lv["gamePath"],"versions") libraryPath = p(lv["gamePath"],"libraries") nativePath = p(lv["gamePath"],"natives") clientJson,libraries,downloadLibraries,natives,arguments = processVersion(versionsPath,libraryPath,nativePath,lv["version"]) versionPath = p(lv["gamePath"],"versions",lv["version"]) nativesOutPath = p(versionPath,"natives-" +lv["osName"]) assetsPath = p(lv["gamePath"],"assets") separator = ";" if lv["osName"] != "windows": separator = ":" libraryList = separator.join(libraries) print("Downloading libraries...") for library in downloadLibraries: fileDl(downloadLibraries[library],library,read = False) print("Extracting natives...") if not os.path.isdir(nativesOutPath): os.makedirs(nativesOutPath) for native in natives: subprocess.run(["7z","x",native,"-o" +nativesOutPath,"-aos"],check=True,stdout=subprocess.DEVNULL) launcherVariables = {} launcherVariables["auth_player_name"] = lv["name"] launcherVariables["version_name"] = findInChainDeepest(versionsPath,lv["version"],["id"]) launcherVariables["game_directory"] = lv["gamePath"] launcherVariables["assets_root"] = assetsPath launcherVariables["assets_index_name"] = findInChain(versionsPath,lv["version"],["assets"]) launcherVariables["auth_access_token"] = "-" launcherVariables["auth_uuid"] = hashlib.md5(lv["name"].encode('utf-8')).hexdigest() launcherVariables["user_type"] = "offline" launcherVariables["version_type"] = clientJson["type"] # legacy: launcherVariables["game_assets"] = assetsPath launcherVariables["auth_session"] = "-" args = json.loads(lv["jvmArguments"]) args.append("-Djava.library.path=" +nativesOutPath) args.append("-cp") args.append(libraryList) args.append(clientJson["mainClass"]) for arg in arguments: for var in launcherVariables: arg = arg.replace("${" +var+ "}",launcherVariables[var]) args.append(arg) print("Launching Minecraft...") if lv["console"] == "1": subprocess.run([lv["java"]] + args,check = True) else: pkwargs = { "stdout": subprocess.DEVNULL, "stdin": subprocess.DEVNULL, "stderr": subprocess.DEVNULL, "close_fds": True } if lv["osName"] == "windows": pkwargs["creationflags"] = 0x00000008 subprocess.Popen([lv["java"] + "w"] + args,**pkwargs) main()