from ftplib import FTP_TLS as FTP import io import json import tempfile import subprocess import time import os import datetime import zipfile from customtkinter import * import time import math import tarfile from pathlib import Path import shutil def get_tar_members_stripped(tar, n_folders_stripped = 1): members = [] for member in tar.getmembers(): p = Path(member.path) member.path = p.relative_to(*p.parts[:n_folders_stripped]) members.append(member) return members def unzip(zip, dest): for file_info in zip.infolist(): if file_info.is_dir(): continue file_path = file_info.filename extracted_path = file_path.split("/", 1)[1] extracted_path = os.path.join(dest, extracted_path) print(extracted_path) os.makedirs(os.path.dirname(extracted_path), exist_ok=True) with open(extracted_path, 'wb') as dst: with zip.open(file_info, 'r') as src: shutil.copyfileobj(src, dst) def install_java(mc_version): global data_dir tokens = mc_version.split(".") java = 8 if tokens[1] == "17": java = 17 elif tokens[1] == "18" or tokens[1] == "19": java = 17 elif int(tokens[1]) > 19: if tokens[1] == "20" and int(tokens[2]) < 5: java = 17 else: java = 21 suff = "win" ext = ".zip" if os.name == "posix": suff = "lin" ext = ".tar.gz" java_path = "java/java-"+str(java)+"-"+suff+ext if not os.path.exists(data_dir+"/java/java-"+str(java)+"-"+suff): print("Downloading Java "+str(java)) ftp = FTP("gitea.piwalker.net") ftp.login() ftp.prot_p() with io.BytesIO() as buff: ftp.retrbinary("RETR "+java_path,buff.write) if os.name == "posix": buff.seek(0) with tarfile.open(fileobj=buff) as tar: print(tar.getnames()) tar.extractall(data_dir+"/java/java-"+str(java)+"-"+suff, members=get_tar_members_stripped(tar, 1)) else: with zipfile.ZipFile(buff) as zip: unzip(zip, data_dir+"/java/java-"+str(java)+"-"+suff) return java def resource_path(relative_path): try: base_path = sys._MEIPASS except Exception: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) def perform_installation(instance_name, prism_command, prism_instance_path, pack): global data_dir if os.name == "posix": data_dir = os.getenv("HOME") + "/.local/share/FCLauncher" else: data_dir = os.getenv("APPDATA") + "/FCLauncher" Path(data_dir).mkdir(parents=True, exist_ok=True) Path(data_dir+"/java").mkdir(parents=True, exist_ok=True) try: ftp = FTP("gitea.piwalker.net") ftp.login() ftp.prot_p() ftp.cwd(pack) # Fetching versions.json from FTP bio = io.BytesIO() ftp.retrbinary("RETR versions.json", bio.write) bio.seek(0) versions = json.load(bio) bio.close() # Checking current version version = "0.0.0" version_file_path = os.path.join(prism_instance_path, instance_name, ".minecraft", "version.txt") if os.path.exists(version_file_path): with open(version_file_path, 'r') as fp: version = fp.readline().rstrip() # Checking if update is needed if version != versions[-1]["Version"]: print(f"Current version: {version}") print(f"Latest version: {versions[-1]['Version']}") # Downloading modpack with tempfile.TemporaryDirectory() as temp_dir: modpack_file_path = os.path.join(temp_dir, instance_name + ".mrpack") with open(modpack_file_path, 'wb') as modpack: ftpDownload(ftp, versions[-1]["File"], modpack) # Running PrismLauncher with modpack subprocess.Popen([prism_command, '-I', modpack_file_path]) # Waiting for installation to complete while True: time.sleep(5) if os.path.exists(version_file_path): with open(version_file_path, 'r') as fp: version = fp.readline().rstrip() if version == versions[-1]["Version"]: info_file_path = os.path.join(prism_instance_path, instance_name, "mmc-pack.json") mc_version = "" with open(info_file_path) as file: info = json.load(file) for component in info["components"]: if component["uid"] == "net.minecraft": mc_version = component["version"] break; java = install_java(mc_version) option_path = os.path.join(prism_instance_path, instance_name, "instance.cfg") data = [] suff = "win" cmd = "java.exe" if os.name == "posix": suff = "lin" cmd = "java" with open(option_path, 'r') as file: data = file.readlines() was_set = False for ind, line in enumerate(data): if line.startswith("JavaPath="): data[ind] = "JavaPath="+data_dir.replace("\\", "/")+"/java/java-"+str(java)+"-"+suff+"/bin/"+cmd+"\n" was_set = True if not was_set: data.append("JavaPath="+data_dir.replace("\\", "/")+"/java/java-"+str(java)+"-"+suff+"/bin/"+cmd+"\n") data.append("OverrideJavaLocation=true\n") data.append("OverrideJava=true\n") with open(option_path, 'w') as file: file.writelines(data) print(data) subprocess.run([prism_command, '-l', instance_name]) time.sleep(30) break else: subprocess.run([prism_command, '-l', instance_name]) except Exception as e: print(e) print("Unable to check for updates. Modpack may be out of date") subprocess.run([prism_command, '-l', instance_name]) def upload_pack(username, password, version_tag, fileName, pack): ftp = FTP("gitea.piwalker.net", username, password) ftp.prot_p() ftp.cwd(pack) bio = io.BytesIO() ftp.retrbinary("RETR versions.json", bio.write) bio.seek(0) versions = json.load(bio) bio.close() time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") versions.append({"Version":version_tag, "Date":time, "File":"Versions/"+pack+time+".mrpack"}) with zipfile.ZipFile(fileName, 'r') as zin: zipbytes = io.BytesIO() zout = zipfile.ZipFile(zipbytes, 'w') for item in zin.infolist(): buffer = zin.read(item.filename) if item.filename != "overrides/version.txt": zout.writestr(item, buffer) zout.writestr("overrides/version.txt", version_tag) zout.close() zipbytes.seek(0) ftp.storbinary("STOR "+versions[len(versions)-1]["File"], zipbytes) bio = io.BytesIO() bio.write(json.dumps(versions).encode()) bio.seek(0) ftp.storbinary("STOR versions.json", bio) bio.close() modpackUpdate(pack, ftp) ftp.close() def getModpacks(): ftp = FTP("gitea.piwalker.net") ftp.login() ftp.prot_p() bio = io.BytesIO() ftp.retrbinary("RETR modpacks.json", bio.write) bio.seek(0) ftp.close() return json.load(bio) def uploadModpacks(modpacks, ftp): ftp.cwd("/ftp"); bio = io.BytesIO() bio.write(json.dumps(modpacks).encode()) bio.seek(0) ftp.storbinary("STOR modpacks.json", bio) bio.close() def modpackUpdate(id, ftp): modpacks = getModpacks() time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") for modpack in modpacks: if modpack["id"] == id: modpack["last-updated"] = time uploadModpacks(modpacks, ftp); def createModpack(id, name, username, password): modpacks = getModpacks() time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") modpacks.append({"id":id, "name":name, "last-updated":time}) ftp = FTP("gitea.piwalker.net", username, password) ftp.prot_p() uploadModpacks(modpacks,ftp) ftp.mkd(id) ftp.cwd(id) versions = [] bio = io.BytesIO() bio.write(json.dumps(versions).encode()) bio.seek(0) ftp.storbinary("STOR versions.json", bio) ftp.mkd("Versions") ftp.close() def deleteModpack(username, password, id): ftp = FTP("gitea.piwalker.net", username, password) ftp.prot_p() modpacks = getModpacks() for pack in modpacks: if pack["id"] == id: modpacks.remove(pack) break uploadModpacks(modpacks, ftp) deleteFolder(ftp, id) def deleteFolder(ftp, path): print("Deleting folder: "+path) ftp.cwd(path) for item in ftp.nlst(): try: print("deleting file: "+path) ftp.delete(item) except: deleteFolder(ftp, item) ftp.cwd("..") ftp.rmd(path) def ftpDownload(ftp, file, stream): #create tkinter window print("downloading file: "+file) dialog = CTk() if os.name == 'posix': dialog.attributes('-type', 'dialog') dialog.title("Downloading Modpack") set_appearance_mode("dark") set_default_color_theme("blue") global pbar pbar = CTkProgressBar(master=dialog) pbar.pack(padx=20, pady=20) global progress global ETA progress = StringVar() ETA = StringVar() progress_label = CTkLabel(master=dialog, textvariable=progress) progress_label.pack() eta_label = CTkLabel(master=dialog, textvariable=ETA) eta_label.pack() size = ftp.size(file) global total total = 0 global start global timer timer = 0 start = time.time() dialog.update() def downloadCallback(data): global total global pbar global start global progress global ETA global timer stream.write(data) total += len(data) if time.time() - timer >= 1: progress.set(str(round(total/1048576, 1))+" MB / "+str(round(size/1048576, 1))+" MB @ " + str(round((total/1048576)/(time.time()-start), 3))+" MB/s") time_left = (size-total)/(total/(time.time()-start)) ETA.set("ETA: " + str(datetime.timedelta(seconds=math.ceil(time_left)))) pbar.set(total/size) timer = time.time() dialog.update() ftp.retrbinary("RETR " + file, downloadCallback) dialog.destroy()