From 01ac79f6239a9ccbac27778f83885844c51e5cba Mon Sep 17 00:00:00 2001 From: raphael60650 Date: Sun, 18 Jul 2021 22:17:03 +0200 Subject: [PATCH] added in_thread decorator, wszst module is now always used to edit file, ctconfig is now property of Game class and no more Gui --- source/CT_Config.py | 3 +- source/Cup.py | 2 +- source/Game.py | 362 ++++++++++++++++++++----------------------- source/Gui.py | 63 ++++---- source/definition.py | 13 ++ source/wszst.py | 78 ++++++++-- translation.json | 2 +- 7 files changed, 271 insertions(+), 252 deletions(-) diff --git a/source/CT_Config.py b/source/CT_Config.py index b64064e..f7a7313 100644 --- a/source/CT_Config.py +++ b/source/CT_Config.py @@ -79,8 +79,7 @@ class CT_Config: "%LE-FLAGS=1\n" "%WIIMM-CUP=1\n" "N N$SWAP | N$F_WII\n\n") - ctfile.write(header); - rctfile.write(header) + ctfile.write(header); rctfile.write(header) # generate cup for undefined track unordered_cups = [] diff --git a/source/Cup.py b/source/Cup.py index fe2eb8a..f89bfaa 100644 --- a/source/Cup.py +++ b/source/Cup.py @@ -1,4 +1,4 @@ -from source.Track import Track +from .Track import Track class Cup: diff --git a/source/Game.py b/source/Game.py index c8a6345..05e74e3 100644 --- a/source/Game.py +++ b/source/Game.py @@ -1,16 +1,20 @@ from tkinter import messagebox -from threading import Thread from PIL import Image -import subprocess import shutil import glob import json import os +from .CT_Config import CT_Config from .definition import * from . import wszst +class RomAlreadyPatched(Exception): + def __init__(self): + super().__init__("ROM Already patched !") + + class InvalidGamePath(Exception): def __init__(self): super().__init__("This path is not valid !") @@ -37,14 +41,20 @@ class CantConvertTrack(Exception): class Game: - def __init__(self, path: str, region_ID: str = "P", game_ID: str = "RMCP01", gui=None): - if not os.path.exists(path): raise InvalidGamePath() - self.extension = get_extension(path).upper() + def __init__(self, path: str = "", region_ID: str = "P", game_ID: str = "RMCP01", gui=None): + if not os.path.exists(path) and path: raise InvalidGamePath() + self.extension = None self.path = path + self.set_path(path) self.region = region_id_to_name[region_ID] self.region_ID = region_ID self.game_ID = game_ID self.gui = gui + self.ctconfig = CT_Config() + + def set_path(self, path): + self.extension = get_extension(path).upper() + self.path = path def convert_to(self, format: str = "FST"): """ @@ -53,19 +63,12 @@ class Game: """ if format in ["ISO", "WBFS", "CISO"]: path_game_format: str = os.path.realpath(self.path + "/../MKWFaraphel." + format.lower()) - subprocess.run(["./tools/wit/wit", "COPY", get_nodir(self.path), "--DEST", - get_nodir(path_game_format), f"--{format.lower()}", "--overwrite"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(path_game_format), - check=True, stdout=subprocess.PIPE) + wszst.wit_copy(self.path, path_game_format, format) shutil.rmtree(self.path) self.path = path_game_format self.gui.progress(statut=self.gui.translate("Changing game's ID"), add=1) - subprocess.run(["./tools/wit/wit", "EDIT", get_nodir(self.path), "--id", - f"RMC{self.region_ID}60", "--name", - f"Mario Kart Wii Faraphel {self.gui.ctconfig.version}", "--modify", "ALL"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(self.path), - check=True, stdout=subprocess.PIPE) + wszst.edit(self.path, region_ID=self.region_ID, name=f"Mario Kart Wii Faraphel {self.ctconfig.version}") def extract(self): if self.extension == "DOL": @@ -79,7 +82,7 @@ class Game: if not (os.path.exists(path_dir)): break directory_name, i = f"MKWiiFaraphel ({i})", i + 1 - wszst.extract(self.path, path_dir) + wszst.wit_extract(self.path, path_dir) self.path = path_dir if os.path.exists(self.path + "/DATA"): self.path += "/DATA" @@ -89,7 +92,7 @@ class Game: raise InvalidFormat() if glob.glob(self.path + "/files/rel/lecode-???.bin"): # if a LECODE file is already here - raise Warning("ROM Already patched") # warning already patched + raise RomAlreadyPatched() # warning already patched with open(self.path + "/setup.txt") as f: setup = f.read() @@ -99,133 +102,118 @@ class Game: self.region_ID = self.game_ID[3] self.region = region_id_to_name[self.region_ID] if self.region_ID in region_id_to_name else self.region + @in_thread def install_mod(self): - def func(): - try: - with open("./fs.json") as f: - fs = json.load(f) + try: + with open("./fs.json") as f: + fs = json.load(f) - # This part is used to estimate the max_step - extracted_file = [] - max_step, step = 1, 0 + # This part is used to estimate the max_step + extracted_file = [] + max_step, step = 1, 0 - def count_rf(path): - nonlocal max_step - max_step += 1 - if get_extension(path) == "szs": - if not (os.path.realpath(path) in extracted_file): - extracted_file.append(os.path.realpath(path)) - max_step += 1 + def count_rf(path): + nonlocal max_step + max_step += 1 + if get_extension(path) == "szs": + if not (os.path.realpath(path) in extracted_file): + extracted_file.append(os.path.realpath(path)) + max_step += 1 - for fp in fs: - for f in glob.glob(self.path + "/files/" + fp, recursive=True): - if type(fs[fp]) == str: - count_rf(path=f) - elif type(fs[fp]) == dict: - for nf in fs[fp]: - if type(fs[fp][nf]) == str: - count_rf(path=f) - elif type(fs[fp][nf]) == list: - for ffp in fs[fp][nf]: count_rf(path=f) - ### - extracted_file = [] - max_step += 4 # PATCH main.dol and PATCH lecode.bin, converting, changing ID - self.gui.progress(show=True, indeter=False, statut=self.gui.translate("Installing mod"), max=max_step, - step=0) + for fp in fs: + for f in glob.glob(self.path + "/files/" + fp, recursive=True): + if type(fs[fp]) == str: + count_rf(path=f) + elif type(fs[fp]) == dict: + for nf in fs[fp]: + if type(fs[fp][nf]) == str: + count_rf(path=f) + elif type(fs[fp][nf]) == list: + for ffp in fs[fp][nf]: count_rf(path=f) + ### + extracted_file = [] + max_step += 4 # PATCH main.dol and PATCH lecode.bin, converting, changing ID + self.gui.progress(show=True, indeter=False, statut=self.gui.translate("Installing mod"), max=max_step, + step=0) - def replace_file(path, file, subpath="/"): - self.gui.progress(statut=self.gui.translate("Editing", "\n", get_nodir(path)), add=1) - extension = get_extension(path) + def replace_file(path, file, subpath="/"): + self.gui.progress(statut=self.gui.translate("Editing", "\n", get_nodir(path)), add=1) + extension = get_extension(path) - if extension == "szs": - if not (os.path.realpath(path) in extracted_file): - subprocess.run( - ["./tools/szs/wszst", "EXTRACT", get_nodir(path), "-d", get_nodir(path) + ".d", - "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(path), - check=True, stdout=subprocess.PIPE) - extracted_file.append(os.path.realpath(path)) + if extension == "szs": + if not (os.path.realpath(path) in extracted_file): + wszst.szs_extract(path, get_nodir(path)) + extracted_file.append(os.path.realpath(path)) - szs_extract_path = path + ".d" - if os.path.exists(szs_extract_path + subpath): - if subpath[-1] == "/": - filecopy(f"./file/{file}", szs_extract_path + subpath + file) - else: - filecopy(f"./file/{file}", szs_extract_path + subpath) + szs_extract_path = path + ".d" + if os.path.exists(szs_extract_path + subpath): + if subpath[-1] == "/": + filecopy(f"./file/{file}", szs_extract_path + subpath + file) + else: + filecopy(f"./file/{file}", szs_extract_path + subpath) - elif path[-1] == "/": - filecopy(f"./file/{file}", path + file) - else: - filecopy(f"./file/{file}", path) + elif path[-1] == "/": + filecopy(f"./file/{file}", path + file) + else: + filecopy(f"./file/{file}", path) - for fp in fs: - for f in glob.glob(self.path + "/files/" + fp, recursive=True): - if type(fs[fp]) == str: - replace_file(path=f, file=fs[fp]) - elif type(fs[fp]) == dict: - for nf in fs[fp]: - if type(fs[fp][nf]) == str: - replace_file(path=f, subpath=nf, file=fs[fp][nf]) - elif type(fs[fp][nf]) == list: - for ffp in fs[fp][nf]: replace_file(path=f, subpath=nf, file=ffp) + for fp in fs: + for f in glob.glob(self.path + "/files/" + fp, recursive=True): + if type(fs[fp]) == str: + replace_file(path=f, file=fs[fp]) + elif type(fs[fp]) == dict: + for nf in fs[fp]: + if type(fs[fp][nf]) == str: + replace_file(path=f, subpath=nf, file=fs[fp][nf]) + elif type(fs[fp][nf]) == list: + for ffp in fs[fp][nf]: replace_file(path=f, subpath=nf, file=ffp) - for file in extracted_file: - self.gui.progress(statut=self.gui.translate("Recompilating", "\n", get_nodir(file)), add=1) - subprocess.run(["./tools/szs/wszst", "CREATE", get_nodir(file) + ".d", "-d", get_nodir(file), - "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), - check=True, stdout=subprocess.PIPE) - if os.path.exists(file + ".d"): shutil.rmtree(file + ".d") + for file in extracted_file: + self.gui.progress(statut=self.gui.translate("Recompilating", "\n", get_nodir(file)), add=1) + wszst.create(file) + if os.path.exists(file + ".d"): + shutil.rmtree(file + ".d") - self.gui.progress(statut=self.gui.translate("Patch main.dol"), add=1) - subprocess.run(["./tools/szs/wstrt", "patch", get_nodir(self.path) + "/sys/main.dol", "--clean-dol", - "--add-lecode"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(self.path), - check=True, stdout=subprocess.PIPE) + self.gui.progress(statut=self.gui.translate("Patch main.dol"), add=1) + wszst.str_patch(self.path) - self.gui.progress(statut=self.gui.translate("Patch lecode.bin"), add=1) + self.gui.progress(statut=self.gui.translate("Patch lecode.bin"), add=1) - shutil.copytree("./file/Track/", self.path + "/files/Race/Course/", dirs_exist_ok=True) - if not (os.path.exists(self.path + "/tmp/")): os.makedirs(self.path + "/tmp/") - filecopy("./file/CTFILE.txt", self.path + "/tmp/CTFILE.txt") - filecopy("./file/lpar-default.txt", self.path + "/tmp/lpar-default.txt") - filecopy(f"./file/lecode-{self.region}.bin", self.path + f"/tmp/lecode-{self.region}.bin") + shutil.copytree("./file/Track/", self.path + "/files/Race/Course/", dirs_exist_ok=True) + if not (os.path.exists(self.path + "/tmp/")): os.makedirs(self.path + "/tmp/") + filecopy("./file/CTFILE.txt", self.path + "/tmp/CTFILE.txt") + filecopy("./file/lpar-default.txt", self.path + "/tmp/lpar-default.txt") + filecopy(f"./file/lecode-{self.region}.bin", self.path + f"/tmp/lecode-{self.region}.bin") - subprocess.run( - ["./tools/szs/wlect", "patch", f"./tmp/lecode-{self.region}.bin", "-od", - f"./files/rel/lecode-{self.region}.bin", "--track-dir", "./files/Race/Course/", - "--move-tracks", "./files/Race/Course/", "--le-define", "./tmp/CTFILE.txt", "--lpar", - "./tmp/lpar-default.txt", "--overwrite"], - creationflags=CREATE_NO_WINDOW, cwd=self.path, check=True, stdout=subprocess.PIPE) + wszst.lec_patch( + self.path, + lecode_file=f"./tmp/lecode-{self.region}.bin", + dest_lecode_file=f"./files/rel/lecode-{self.region}.bin", + ) - shutil.rmtree(self.path + "/tmp/") + shutil.rmtree(self.path + "/tmp/") - output_format = self.gui.stringvar_game_format.get() - self.gui.progress(statut=self.gui.translate("Converting to", " ", output_format), add=1) - self.convert_to(output_format) + output_format = self.gui.stringvar_game_format.get() + self.gui.progress(statut=self.gui.translate("Converting to", " ", output_format), add=1) + self.convert_to(output_format) - messagebox.showinfo(self.gui.translate("End"), self.gui.translate("The mod has been installed !")) + messagebox.showinfo(self.gui.translate("End"), self.gui.translate("The mod has been installed !")) - except: - self.gui.log_error() - finally: - self.gui.progress(show=False) - - t = Thread(target=func) - t.setDaemon(True) - t.start() - return t + except: + self.gui.log_error() + finally: + self.gui.progress(show=False) def patch_autoadd(self, auto_add_dir: str = "./file/auto-add"): if os.path.exists(auto_add_dir): shutil.rmtree(auto_add_dir) if not os.path.exists(self.path + "/tmp/"): os.makedirs(self.path + "/tmp/") - subprocess.run(["./tools/szs/wszst", "AUTOADD", get_nodir(self.path) + "/files/Race/Course/", - "--DEST", get_nodir(self.path) + "/tmp/auto-add/"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(self.path), - check=True, stdout=subprocess.PIPE) + wszst.autoadd(self.path, get_nodir(self.path) + "/tmp/auto-add/") shutil.move(self.path + "/tmp/auto-add/", auto_add_dir) shutil.rmtree(self.path + "/tmp/") def patch_bmg(self, gamefile: str): # gamefile est le fichier .szs trouvé dans le /files/Scene/UI/ du jeu NINTENDO_CWF_REPLACE = "Wiimmfi" - MAINMENU_REPLACE = f"MKWFaraphel {self.gui.ctconfig.version}" + MAINMENU_REPLACE = f"MKWFaraphel {self.ctconfig.version}" menu_replacement = { "CWF de Nintendo": NINTENDO_CWF_REPLACE, "Wi-Fi Nintendo": NINTENDO_CWF_REPLACE, @@ -245,18 +233,11 @@ class Game: bmglang = gamefile[-len("E.txt"):-len(".txt")] # Langue du fichier self.gui.progress(statut=self.gui.translate("Patching text", " ", bmglang), add=1) - subprocess.run(["./tools/szs/wszst", "EXTRACT", get_nodir(gamefile), "-d", get_nodir(gamefile) + ".d", - "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(gamefile)) + wszst.szs_extract(gamefile, get_nodir(gamefile)) - # Menu.bmg - bmgmenu = subprocess.run(["./tools/szs/wbmgt", "CAT", get_nodir(gamefile) + ".d/message/Menu.bmg"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(gamefile), - check=True, stdout=subprocess.PIPE).stdout.decode() + bmgmenu = wszst.bmg_cat(gamefile, ".d/message/Menu.bmg") # Menu.bmg + bmgtracks = wszst.bmg_cat(gamefile, ".d/message/Common.bmg") # Common.bmg - # Common.bmg - bmgtracks = subprocess.run(["./tools/szs/wbmgt", "CAT", get_nodir(gamefile) + ".d/message/Common.bmg"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(gamefile), - check=True, stdout=subprocess.PIPE).stdout.decode() trackheader = "#--- standard track names" trackend = "2328" bmgtracks = bmgtracks[bmgtracks.find(trackheader) + len(trackheader):bmgtracks.find(trackend)] @@ -290,14 +271,10 @@ class Game: if not (os.path.exists("./file/tmp/")): os.makedirs("./file/tmp/") filecopy(gamefile + ".d/message/Common.bmg", "./file/tmp/Common.bmg") - bmgcommon = subprocess.run( - ["tools/szs/wctct", "bmg", "--le-code", "--long", "./file/CTFILE.txt", "--patch-bmg", - "OVERWRITE=./file/tmp/Common.bmg", "--patch-bmg", "OVERWRITE=./file/ExtraCommon.txt"], - creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE).stdout.decode() - rbmgcommon = subprocess.run( - ["tools/szs/wctct", "bmg", "--le-code", "--long", "./file/RCTFILE.txt", "--patch-bmg", - "OVERWRITE=./file/tmp/Common.bmg", "--patch-bmg", "OVERWRITE=./file/ExtraCommon.txt"], - creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE).stdout.decode() + bmgcommon = wszst.ctc_patch_bmg(ctfile="./file/CTFILE.txt", + bmgs=["./file/tmp/Common.bmg", "./file/ExtraCommon.txt"]) + rbmgcommon = wszst.ctc_patch_bmg(ctfile="./file/RCTFILE.txt", + bmgs=["./file/tmp/Common.bmg", "./file/ExtraCommon.txt"]) shutil.rmtree(gamefile + ".d") os.remove("./file/tmp/Common.bmg") @@ -308,59 +285,52 @@ class Game: for text, colored_text in replacement_list.items(): bmgtext = bmgtext.replace(text, colored_text) with open(file, "w", encoding="utf-8") as f: f.write(bmgtext) - subprocess.run(["./tools/szs/wbmgt", "ENCODE", get_nodir(file), "--overwrite"], - creationflags=CREATE_NO_WINDOW, cwd=get_dir(file)) + wszst.bmg_encode(file) os.remove(file) finalise(f"./file/Menu_{bmglang}.txt", bmgmenu, menu_replacement) finalise(f"./file/Common_{bmglang}.txt", bmgcommon) finalise(f"./file/Common_R{bmglang}.txt", rbmgcommon) + @in_thread def patch_file(self): - def func(): - try: - if not (os.path.exists("./file/Track-WU8/")): os.makedirs("./file/Track-WU8/") - with open("./convert_file.json") as f: - fc = json.load(f) - max_step = len(fc["img"]) + len(self.gui.ctconfig.all_tracks) + 3 + len("EGFIS") + try: + if not (os.path.exists("./file/Track-WU8/")): os.makedirs("./file/Track-WU8/") + with open("./convert_file.json") as f: + fc = json.load(f) + max_step = len(fc["img"]) + len(self.ctconfig.all_tracks) + 3 + len("EGFIS") - self.gui.progress(show=True, indeter=False, statut=self.gui.translate("Converting files"), max=max_step, - step=0) - self.gui.progress(statut=self.gui.translate("Configurating LE-CODE"), add=1) - self.gui.ctconfig.create_ctfile() + self.gui.progress(show=True, indeter=False, statut=self.gui.translate("Converting files"), + max=max_step, step=0) + self.gui.progress(statut=self.gui.translate("Configurating LE-CODE"), add=1) + self.ctconfig.create_ctfile() - self.gui.progress(statut=self.gui.translate("Creating ct_icon.png"), add=1) - ct_icon = self.gui.ctconfig.get_cticon() - ct_icon.save("./file/ct_icons.tpl.png") + self.gui.progress(statut=self.gui.translate("Creating ct_icon.png"), add=1) + ct_icon = self.ctconfig.get_cticon() + ct_icon.save("./file/ct_icons.tpl.png") - self.gui.progress(statut=self.gui.translate("Creating descriptive images"), add=1) - self.patch_img_desc() - self.patch_image(fc) - for file in glob.glob(self.path + "/files/Scene/UI/MenuSingle_?.szs"): self.patch_bmg(file) - # MenuSingle could be any other file, Common and Menu are all the same in all other files. - self.patch_autoadd() - if self.patch_tracks() != 0: return + self.gui.progress(statut=self.gui.translate("Creating descriptive images"), add=1) + self.patch_img_desc() + self.patch_image(fc) + for file in glob.glob(self.path + "/files/Scene/UI/MenuSingle_?.szs"): self.patch_bmg(file) + # MenuSingle could be any other file, Common and Menu are all the same in all other files. + self.patch_autoadd() + if self.patch_tracks() != 0: return - self.gui.button_install_mod.grid(row=2, column=1, columnspan=2, sticky="NEWS") - self.gui.button_install_mod.config( - text=self.gui.translate("Install mod", " (v", self.gui.ctconfig.version, ")")) + self.gui.button_install_mod.grid(row=2, column=1, columnspan=2, sticky="NEWS") + self.gui.button_install_mod.config( + text=self.gui.translate("Install mod", " (v", self.ctconfig.version, ")")) - except: - self.gui.log_error() - finally: - self.gui.progress(show=False) - - t = Thread(target=func) - t.setDaemon(True) - t.start() - return t + except: + self.gui.log_error() + finally: + self.gui.progress(show=False) def patch_image(self, fc): for i, file in enumerate(fc["img"]): self.gui.progress(statut=self.gui.translate("Converting images") + f"\n({i + 1}/{len(fc['img'])}) {file}", add=1) - subprocess.run(["./tools/szs/wimgt", "ENCODE", "./file/" + file, "-x", fc["img"][file], "--overwrite"], - creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE) + wszst.img_encode("./file/" + file, fc["img"][file]) def patch_img_desc(self, img_desc_path: str = "./file/img_desc", dest_dir: str = "./file"): il = Image.open(img_desc_path + "/illustration.png") @@ -390,7 +360,7 @@ class Game: def add_process(track): nonlocal error_count, error_max, process_list track_file = track.get_track_name() - total_track = len(self.gui.ctconfig.all_tracks) + total_track = len(self.ctconfig.all_tracks) process_list[track_file] = None # Used for showing track in progress even if there's no process self.gui.progress(statut=self.gui.translate("Converting tracks", f"\n({i + 1}/{total_track})\n", @@ -407,15 +377,14 @@ class Game: if download_returncode == -1: # can't download error_count += 1 if error_count > error_max: # Too much track wasn't correctly converted - """messagebox.showerror( - gui.translate("Error"), - gui.translate("Too much tracks had a download issue.")) - return -1""" + messagebox.showerror( + self.gui.translate("Error"), + self.gui.translate("Too much tracks had a download issue.")) raise TooMuchDownloadFailed() else: - """messagebox.showwarning(gui.translate("Warning"), - gui.translate("Can't download this track !", - f" ({error_count} / {error_max})"))""" + messagebox.showwarning(self.gui.translate("Warning"), + self.gui.translate("Can't download this track !", + f" ({error_count} / {error_max})")) elif download_returncode == 2: break # if download is disabled, do not check sha1 @@ -424,22 +393,21 @@ class Game: if not track.check_sha1(): # Check si le sha1 du fichier est le bon error_count += 1 if error_count > error_max: # Too much track wasn't correctly converted - """messagebox.showerror( - gui.translate("Error"), - gui.translate("Too much tracks had an issue during sha1 check."))""" + messagebox.showerror( + self.gui.translate("Error"), + self.gui.translate("Too much tracks had an issue during sha1 check.")) raise TooMuchSha1CheckFailed() continue break - if not ( - os.path.exists( - track.file_szs)) or download_returncode == 3: # returncode 3 is track has been updated + if not (os.path.exists(track.file_szs)) or download_returncode == 3: + # returncode 3 is track has been updated if os.path.exists(track.file_wu8): process_list[track_file] = track.convert_wu8_to_szs() else: - """messagebox.showerror(gui.translate("Error"), - gui.translate("Can't convert track.\nEnable track download and retry."))""" + messagebox.showerror(self.gui.translate("Error"), + self.gui.translate("Can't convert track.\nEnable track download and retry.")) raise CantConvertTrack() elif self.gui.boolvar_del_track_after_conv.get(): os.remove(track.file_wu8) @@ -459,28 +427,28 @@ class Game: os.remove(track.file_szs) error_count += 1 if error_count > error_max: # Too much track wasn't correctly converted - """messagebox.showerror( - gui.translate("Error"), - gui.translate("Too much track had a conversion issue."))""" - raise CantConvertTrack + messagebox.showerror( + self.gui.translate("Error"), + self.gui.translate("Too much track had a conversion issue.")) + raise CantConvertTrack() else: # if the error max hasn't been reach - """messagebox.showwarning( - gui.translate("Warning"), - gui.translate("The track", " ", track.file_wu8, - "do not have been properly converted.", - f" ({error_count} / {error_max})"))""" + messagebox.showwarning( + self.gui.translate("Warning"), + self.gui.translate("The track", " ", track.file_wu8, + "do not have been properly converted.", + f" ({error_count} / {error_max})")) else: if self.gui.boolvar_del_track_after_conv.get(): os.remove(track.file_wu8) else: process_list.pop(track_file) - if not (any(process_list.values())): return 1 # si il n'y a plus de processus + if not (any(process_list.values())): return 1 # if there is no more process if len(process_list): return 1 else: return 0 - for i, track in enumerate(self.gui.ctconfig.all_tracks): + for i, track in enumerate(self.ctconfig.all_tracks): while True: if len(process_list) < max_process: returncode = add_process(track) diff --git a/source/Gui.py b/source/Gui.py index 446f010..6109e31 100644 --- a/source/Gui.py +++ b/source/Gui.py @@ -1,10 +1,10 @@ from tkinter import filedialog, ttk from tkinter import * -import zipfile +import subprocess import traceback import requests +import zipfile -from source.CT_Config import CT_Config from source.Option import Option from source.Game import * @@ -24,8 +24,8 @@ class Gui: self.option = Option() self.option.load_from_file("./option.json") - self.ctconfig = CT_Config() - self.ctconfig.load_ctconfig_file("./ct_config.json") + self.game = Game(gui=self) + self.game.ctconfig.load_ctconfig_file("./ct_config.json") self.is_dev_version = False # Is this installer version a dev ? self.stringvar_language = StringVar(value=self.option.language) @@ -70,7 +70,7 @@ class Gui: self.menu_marktrackversion = Menu(self.menu_trackselection, tearoff=0) self.menu_trackselection.add_cascade(label=self.translate("Mark all tracks from version"), menu=self.menu_marktrackversion) self.menu_marktrackversion.add_radiobutton(label=self.translate("None"), variable=self.stringvar_mark_track_from_version, value="None") - for version in self.ctconfig.all_version: + for version in self.game.ctconfig.all_version: self.menu_marktrackversion.add_radiobutton(label=f"v{version}", variable=self.stringvar_mark_track_from_version, value=version) self.menu_advanced = Menu(self.menu_bar, tearoff=0) @@ -109,53 +109,48 @@ class Gui: self.frame_game_path_action.grid(row=2, column=1, columnspan=2, sticky="NEWS") self.frame_game_path_action.columnconfigure(1, weight=1) + @in_thread def use_path(): - def func(): - self.frame_action.grid_forget() - try: - self.game = Game(path=entry_game_path.get(), gui=self) - self.progress(show=True, indeter=True, statut=self.translate("Extracting the game...")) - self.game.extract() - self.frame_action.grid(row=3, column=1, sticky="NEWS") - except InvalidGamePath: - messagebox.showerror(self.translate("Error"), self.translate("The file path in invalid")) - except InvalidFormat: - messagebox.showerror(self.translate("Error"), self.translate("This game's format is invalid")) - except: - self.log_error() - finally: - self.progress(show=False) - - t = Thread(target=func) - t.setDaemon(True) - t.start() - return t + self.frame_action.grid_forget() + try: + self.game.set_path(entry_game_path.get()) + self.progress(show=True, indeter=True, statut=self.translate("Extracting the game...")) + self.game.extract() + self.frame_action.grid(row=3, column=1, sticky="NEWS") + except RomAlreadyPatched: + messagebox.showerror(self.translate("Error"), self.translate("This game is already modded")) + except InvalidGamePath: + messagebox.showerror(self.translate("Error"), self.translate("The file path in invalid")) + except InvalidFormat: + messagebox.showerror(self.translate("Error"), self.translate("This game's format is invalid")) + except: + self.log_error() + finally: + self.progress(show=False) self.button_game_extract = Button(self.frame_game_path_action, text=self.translate("Extract file"), relief=RIDGE, command=use_path) self.button_game_extract.grid(row=1, column=1, sticky="NEWS") + @in_thread def do_everything(): - def func(): - use_path().join() - self.game.patch_file(self).join() - self.game.install_mod(self).join() + use_path().join() + self.game.patch_file().join() + self.game.install_mod().join() if messagebox.askyesno(self.translate("Experimental functionality"), self.translate("This will extract the selected ROM, prepare files and install mod. " "Do you wish to continue ?")): - t = Thread(target=func) - t.setDaemon(True) - t.start() + do_everything() self.button_do_everything = Button(self.frame_game_path_action, text=self.translate("Do everything"), relief=RIDGE, command=do_everything) self.button_do_everything.grid(row=1, column=2, sticky="NEWS") self.frame_action = LabelFrame(self.root, text=self.translate("Action")) - self.button_prepare_file = Button(self.frame_action, text=self.translate("Prepare files"), relief=RIDGE, command=lambda: self.game.patch_file(self), width=45) + self.button_prepare_file = Button(self.frame_action, text=self.translate("Prepare files"), relief=RIDGE, command=lambda: self.game.patch_file(), width=45) self.button_prepare_file.grid(row=1, column=1, columnspan=2, sticky="NEWS") - self.button_install_mod = Button(self.frame_action, text=self.translate("Install mod"), relief=RIDGE, command=lambda: self.game.install_mod(self), width=45) + self.button_install_mod = Button(self.frame_action, text=self.translate("Install mod"), relief=RIDGE, command=lambda: self.game.install_mod(), width=45) # Install mod button will only appear after prepare file step self.progressbar = ttk.Progressbar(self.root) diff --git a/source/definition.py b/source/definition.py index e7edc0c..b678eb3 100644 --- a/source/definition.py +++ b/source/definition.py @@ -1,3 +1,5 @@ +from threading import Thread + CREATE_NO_WINDOW = 0x08000000 GITHUB_REPOSITORY = "Faraphel/MKWF-Install" GITHUB_MASTER_BRANCH = f"https://raw.githubusercontent.com/{GITHUB_REPOSITORY}/master/" @@ -94,7 +96,18 @@ region_id_to_name = { "E": "USA" } + def filecopy(src, dst): with open(src, "rb") as f1: with open(dst, "wb") as f2: f2.write(f1.read()) # could be buffered + + +def in_thread(func): + def wrapped_func(*args, **kwargs): + thread = Thread(target=func, args=args, kwargs=kwargs) + thread.setDaemon(True) + thread.start() + return thread + + return wrapped_func diff --git a/source/wszst.py b/source/wszst.py index 249a664..935705f 100644 --- a/source/wszst.py +++ b/source/wszst.py @@ -31,7 +31,7 @@ def normalize(src_file: str, dest_dir: str = "./file/Track/", dest_name: str = " autoadd_path], creationflags=CREATE_NO_WINDOW, stderr=subprocess.PIPE) -def extract(file: str, dest_dir: str): +def wit_extract(file: str, dest_dir: str): """ :param file: game's file to extract (can be WBFS, ISO, CISO) :param dest_dir: where to extract the game @@ -42,35 +42,79 @@ def extract(file: str, dest_dir: str): def create(file: str): - pass + """ + :param file: create a .szs file from the directory {file}.d + :return: + """ + subprocess.run(["./tools/szs/wszst", "CREATE", get_nodir(file) + ".d", "-d", get_nodir(file), + "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), + check=True, stdout=subprocess.PIPE) -def wstrt_patch(file: str): - pass +def str_patch(file: str): + subprocess.run(["./tools/szs/wstrt", "patch", get_nodir(file) + "/sys/main.dol", "--clean-dol", + "--add-lecode"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), + check=True, stdout=subprocess.PIPE) -def wlect_patch(file: str, - lecode_file: str = f"./files/rel/lecode-PAL.bin", - game_track_path: str = "./files/Race/Course/", - move_track_path: str = "./files/Race/Course/", - ctfile_path: str = "./tmp/CTFILE.txt", - lpar_path: str = "./tmp/lpar-default.txt"): - pass +def lec_patch(file: str, + lecode_file: str = f"./tmp/lecode-PAL.bin", + dest_lecode_file: str = f"./files/rel/lecode-PAL.bin", + game_track_path: str = "./files/Race/Course/", + move_track_path: str = "./files/Race/Course/", + ctfile_path: str = "./tmp/CTFILE.txt", + lpar_path: str = "./tmp/lpar-default.txt"): + subprocess.run( + ["./tools/szs/wlect", "patch", lecode_file, "-od", + dest_lecode_file, "--track-dir", game_track_path, + "--move-tracks", move_track_path, "--le-define", ctfile_path, "--lpar", + lpar_path, "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=file, check=True, stdout=subprocess.PIPE) -def edit(): pass +def edit(file, region_ID: str = "P", name: str = "Mario Kart Wii"): + subprocess.run(["./tools/wit/wit", "EDIT", get_nodir(file), "--id", + f"RMC{region_ID}60", "--name", name, "--modify", "ALL"], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), + check=True, stdout=subprocess.PIPE) -def autoadd(): pass +def autoadd(file: str, dest_dir: str): + subprocess.run(["./tools/szs/wszst", "AUTOADD", get_nodir(file) + "/files/Race/Course/", "--DEST", dest_dir], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), + check=True, stdout=subprocess.PIPE) -def bmg_decode(): pass +def bmg_encode(file: str): + subprocess.run(["./tools/szs/wbmgt", "ENCODE", get_nodir(file), "--overwrite"], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(file)) -def bmg_encode(): pass +def bmg_cat(path: str, subfile: str = ".d/message/Common.bmg"): + return subprocess.run(["./tools/szs/wbmgt", "CAT", get_nodir(path) + subfile], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(path), + check=True, stdout=subprocess.PIPE).stdout.decode() -def wctct_bmg(): pass +def wit_copy(src_path, dst_path, format: str = "ISO"): + subprocess.run(["./tools/wit/wit", "COPY", get_nodir(src_path), "--DEST", + get_nodir(dst_path), f"--{format.lower()}", "--overwrite"], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(dst_path), + check=True, stdout=subprocess.PIPE) -def wit_copy(): pass \ No newline at end of file +def ctc_patch_bmg(bmgs: list, ctfile: str = "./file/CTFILE.txt"): + bmg_cmd = [] + for bmg in bmgs: bmg_cmd.extend(["--patch-bmg", f"OVERWRITE={bmg}"]) + return subprocess.run( + ["tools/szs/wctct", "bmg", "--le-code", "--long", ctfile, *bmg_cmd], + creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE).stdout.decode() + + +def img_encode(src_file, format): + subprocess.run(["./tools/szs/wimgt", "ENCODE", src_file, "-x", format, "--overwrite"], + creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE) + + +def szs_extract(file, dest_dir): + subprocess.run(["./tools/szs/wszst", "EXTRACT", get_nodir(file), "--DEST", dest_dir+".d"], + creationflags=CREATE_NO_WINDOW, cwd=get_dir(file)) \ No newline at end of file diff --git a/translation.json b/translation.json index 4e402c2..39482a5 100644 --- a/translation.json +++ b/translation.json @@ -19,7 +19,7 @@ "This directory will be overwritten if you install the mod !\nAre you sure you want to use it ?": "Ce dossier sera écrasé si vous installer le mod !\nÊtes-vous sûr de vouloir l'utiliser ?", "Extracting the game...": "Extraction du jeu...", "This file type is not supported": "Le type de fichier n'est pas reconnu", - "This game is already modded, it is not recommended to use it to install the mod": "Cette ROM est déjà moddé, il est déconseillé de l'utiliser pour installer le mod", + "This game is already modded": "Cette ROM est déjà moddé", "Can't find game region.\nPAL region will be used.": "Impossible de trouver la région de votre jeu.\nla région PAL sera utilisé par défaut.", "Extract file": "Extraire le fichier", "Experimental functionality": "Fonctionnalité expérimentale",