Merge pull request #26 from Faraphel/dev

v0.9
This commit is contained in:
Faraphel 2021-07-15 16:28:33 +02:00 committed by GitHub
commit f3a62f8188
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 13675 additions and 333 deletions

View file

@ -5,7 +5,7 @@ import sys
Dir, ext = os.path.splitext(sys.argv[0]) Dir, ext = os.path.splitext(sys.argv[0])
if ext == ".py": if ext == ".py":
input("Ce code ne doit être lancé que sous sa forme .exe !") input("This code need to be started from its .exe version !")
exit() exit()
VERSION_FILE_URL = "https://raw.githubusercontent.com/Faraphel/MKWF-Install/master/version" VERSION_FILE_URL = "https://raw.githubusercontent.com/Faraphel/MKWF-Install/master/version"
@ -18,28 +18,26 @@ try:
dl_size = int(dl.headers["Content-Length"]) dl_size = int(dl.headers["Content-Length"])
with open("./download.zip", "wb") as file: with open("./download.zip", "wb") as file:
print(f"Téléchargement de la version {gitversion['version']}.{gitversion['subversion']} en cours...") print(f"Downloading version {gitversion['version']}.{gitversion['subversion']}...")
chunk_size = 1024 chunk_size = 1024
for i, chunk in enumerate(dl.iter_content(chunk_size=chunk_size)): for i, chunk in enumerate(dl.iter_content(chunk_size=chunk_size)):
progress = int((i * chunk_size * 100) / dl_size) progress = int((i * chunk_size * 100) / dl_size)
print("("+str(progress)+"%) | " + "#" * (progress//5) + "_"* (20 - (progress//5)) + " | " + print("("+str(progress)+"%) | " + "#" * (progress//5) + "_" * (20 - (progress//5)) + " | " +
str(round(i*chunk_size/1000000,1)) + "Mo/" + str(round(dl_size/1000000,1)) + "Mo", end="\r") str(round(i*chunk_size/1000000,1)) + "Mo/" + str(round(dl_size/1000000,1)) + "Mo" + " " * 10,
end="\r")
file.write(chunk) file.write(chunk)
file.flush() file.flush()
print("fin du téléchargement, début de l'extraction...") print("end of the download, starting extraction..." + (" " * 10))
with zipfile.ZipFile("./download.zip") as file: with zipfile.ZipFile("./download.zip") as file:
file.extractall("./") file.extractall("./")
print("fin de l'extraction") print("end of extraction")
os.remove("./download.zip") os.remove("./download.zip")
print("lancement de l'application...") print("restarting application...")
os.startfile(os.path.realpath("./MKWF-Install.exe")) os.startfile(os.path.realpath("./MKWF-Install.exe"))
except Exception as e: except Exception as e:
print(f"Impossible d'effectuer la mise à jour :\n\n{str(e)}") print(f"Can't update :\n\n{str(e)}")
input("Appuyez pour continuer...") input("Press to close...")
# TODO: Use requests.get("https://api.github.com/repos/Faraphel/MKWF-Install/git/trees/master?recursive=1") to avoid
# redownloading the same files again and again

Binary file not shown.

View file

@ -16,7 +16,7 @@ setup(
license='GPL-3.0', license='GPL-3.0',
author='Faraphel', author='Faraphel',
author_email='rc60650@hotmail.com', author_email='rc60650@hotmail.com',
description='Logiciel de mise à jour pour MKWF-Install.', description='MKWF-Install Updater.',
executables = [Executable("./Updater.py", executables = [Executable("./Updater.py",
target_name = "Updater.exe", target_name = "Updater.exe",
shortcut_name = "MKWF-Install Updater", shortcut_name = "MKWF-Install Updater",

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
file/Track-WU8/Shipyard.wu8 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
file/Track-WU8/Town.wu8 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -29,7 +29,7 @@ class ClassApp():
from source.patch_ct_icon import patch_ct_icon from source.patch_ct_icon import patch_ct_icon
from source.log_error import log_error from source.log_error import log_error
from source.get_github_file import get_github_file, check_track_sha1 from source.get_github_file import get_github_file, check_track_sha1
from source.patch_track import count_track, patch_track, patch_autoadd from source.patch_track import load_ct_config, patch_track, patch_autoadd, get_trackctname, get_trackname
from source.patch_image import patch_image from source.patch_image import patch_image
from source.option import load_option, change_option from source.option import load_option, change_option

View file

@ -15,6 +15,8 @@ def __init__(self):
self.root = Tk() self.root = Tk()
self.load_option() self.load_option()
self.load_ct_config()
self.stringvar_language = StringVar(value=self.option["language"]) self.stringvar_language = StringVar(value=self.option["language"])
self.stringvar_game_format = StringVar(value=self.option["format"]) self.stringvar_game_format = StringVar(value=self.option["format"])
self.boolvar_disable_download = BooleanVar(value=self.option["disable_download"]) self.boolvar_disable_download = BooleanVar(value=self.option["disable_download"])
@ -22,8 +24,12 @@ def __init__(self):
self.boolvar_dont_check_for_update = BooleanVar(value=self.option["dont_check_for_update"]) self.boolvar_dont_check_for_update = BooleanVar(value=self.option["dont_check_for_update"])
self.boolvar_dont_check_track_sha1 = BooleanVar(value=self.option["dont_check_track_sha1"]) self.boolvar_dont_check_track_sha1 = BooleanVar(value=self.option["dont_check_track_sha1"])
self.intvar_process_track = IntVar(value=self.option["process_track"]) self.intvar_process_track = IntVar(value=self.option["process_track"])
self.boolvar_use_1star_track = BooleanVar(value=True)
self.boolvar_use_2star_track = BooleanVar(value=True)
self.boolvar_use_3star_track = BooleanVar(value=True)
self.stringvar_mark_track_from_version = StringVar(value="None")
self.root.title(self.translate("MKWFaraphel Installateur")) self.root.title(self.translate("MKWFaraphel Installer"))
self.root.resizable(False, False) self.root.resizable(False, False)
self.root.iconbitmap(bitmap="./icon.ico") self.root.iconbitmap(bitmap="./icon.ico")
@ -35,43 +41,55 @@ def __init__(self):
self.root.config(menu=self.menu_bar) self.root.config(menu=self.menu_bar)
self.menu_language = Menu(self.menu_bar, tearoff=0) self.menu_language = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Langage"), menu=self.menu_language) self.menu_bar.add_cascade(label=self.translate("Language"), menu=self.menu_language)
self.menu_language.add_radiobutton(label="Français", variable=self.stringvar_language, value="fr", command=lambda: self.change_option("language", "fr", restart=True)) self.menu_language.add_radiobutton(label="Français", variable=self.stringvar_language, value="fr", command=lambda: self.change_option("language", "fr", restart=True))
self.menu_language.add_radiobutton(label="English", variable=self.stringvar_language, value="en", command=lambda: self.change_option("language", "en", restart=True)) self.menu_language.add_radiobutton(label="English", variable=self.stringvar_language, value="en", command=lambda: self.change_option("language", "en", restart=True))
self.menu_format = Menu(self.menu_bar, tearoff=0) self.menu_format = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Format"), menu=self.menu_format) self.menu_bar.add_cascade(label=self.translate("Format"), menu=self.menu_format)
self.menu_format.add_radiobutton(label=self.translate("FST (Dossier)"), variable=self.stringvar_game_format, value="FST", command=lambda: self.change_option("format", "FST")) self.menu_format.add_radiobutton(label=self.translate("FST (Directory)"), variable=self.stringvar_game_format, value="FST", command=lambda: self.change_option("format", "FST"))
self.menu_format.add_radiobutton(label="ISO", variable=self.stringvar_game_format, value="ISO", command=lambda: self.change_option("format", "ISO")) self.menu_format.add_radiobutton(label="ISO", variable=self.stringvar_game_format, value="ISO", command=lambda: self.change_option("format", "ISO"))
self.menu_format.add_radiobutton(label="CISO", variable=self.stringvar_game_format, value="CISO", command=lambda: self.change_option("format", "CISO")) self.menu_format.add_radiobutton(label="CISO", variable=self.stringvar_game_format, value="CISO", command=lambda: self.change_option("format", "CISO"))
self.menu_format.add_radiobutton(label="WBFS", variable=self.stringvar_game_format, value="WBFS", command=lambda: self.change_option("format", "WBFS")) self.menu_format.add_radiobutton(label="WBFS", variable=self.stringvar_game_format, value="WBFS", command=lambda: self.change_option("format", "WBFS"))
self.menu_trackselection = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Track selection"), menu=self.menu_trackselection)
self.menu_trackselection.add_checkbutton(label=self.translate("Select"," 1 ","star"), variable=self.boolvar_use_1star_track)
self.menu_trackselection.add_checkbutton(label=self.translate("Select"," 2 ","stars"), variable=self.boolvar_use_2star_track)
self.menu_trackselection.add_checkbutton(label=self.translate("Select"," 3 ","stars"), variable=self.boolvar_use_3star_track)
self.menu_trackselection.add_separator()
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.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) self.menu_advanced = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Avancé"), menu=self.menu_advanced) self.menu_bar.add_cascade(label=self.translate("Advanced"), menu=self.menu_advanced)
self.menu_advanced.add_checkbutton(label=self.translate("Désactiver les téléchargements"), variable=self.boolvar_disable_download, command=lambda: self.change_option("disable_download", self.boolvar_disable_download)) self.menu_advanced.add_checkbutton(label=self.translate("Disable downloads"), variable=self.boolvar_disable_download, command=lambda: self.change_option("disable_download", self.boolvar_disable_download))
self.menu_advanced.add_checkbutton(label=self.translate("Supprimer les courses wu8 après conversion en szs"), variable=self.boolvar_del_track_after_conv, command=lambda: self.change_option("del_track_after_conv", self.boolvar_del_track_after_conv)) self.menu_advanced.add_checkbutton(label=self.translate("Delete track after wu8 to szs conversion"), variable=self.boolvar_del_track_after_conv, command=lambda: self.change_option("del_track_after_conv", self.boolvar_del_track_after_conv))
self.menu_advanced.add_checkbutton(label=self.translate("Ne pas vérifier les mises à jour"), variable=self.boolvar_dont_check_for_update, command=lambda: self.change_option("dont_check_for_update", self.boolvar_dont_check_for_update)) self.menu_advanced.add_checkbutton(label=self.translate("Don't check for update"), variable=self.boolvar_dont_check_for_update, command=lambda: self.change_option("dont_check_for_update", self.boolvar_dont_check_for_update))
self.menu_advanced.add_checkbutton(label=self.translate("Ne pas vérifier le sha1 des courses"), variable=self.boolvar_dont_check_track_sha1, command=lambda: self.change_option("dont_check_track_sha1",self.boolvar_dont_check_track_sha1)) self.menu_advanced.add_checkbutton(label=self.translate("Don't check track's sha1"), variable=self.boolvar_dont_check_track_sha1, command=lambda: self.change_option("dont_check_track_sha1",self.boolvar_dont_check_track_sha1))
self.menu_advanced.add_separator() self.menu_advanced.add_separator()
self.menu_advanced.add_command(label=self.translate("Nombre de processus de conversion de course :")) self.menu_advanced.add_command(label=self.translate("Number of track conversion process", " :"))
self.menu_advanced.add_radiobutton(label=self.translate("1 processus"), variable=self.intvar_process_track, value=1, command=lambda: self.change_option("process_track", 1)) self.menu_advanced.add_radiobutton(label=self.translate("1 ", "process"), variable=self.intvar_process_track, value=1, command=lambda: self.change_option("process_track", 1))
self.menu_advanced.add_radiobutton(label=self.translate("2 processus"), variable=self.intvar_process_track, value=2, command=lambda: self.change_option("process_track", 2)) self.menu_advanced.add_radiobutton(label=self.translate("2 ", "process"), variable=self.intvar_process_track, value=2, command=lambda: self.change_option("process_track", 2))
self.menu_advanced.add_radiobutton(label=self.translate("4 processus"), variable=self.intvar_process_track, value=4, command=lambda: self.change_option("process_track", 4)) self.menu_advanced.add_radiobutton(label=self.translate("4 ", "process"), variable=self.intvar_process_track, value=4, command=lambda: self.change_option("process_track", 4))
self.menu_advanced.add_radiobutton(label=self.translate("8 processus"), variable=self.intvar_process_track, value=8, command=lambda: self.change_option("process_track", 8)) self.menu_advanced.add_radiobutton(label=self.translate("8 ", "process"), variable=self.intvar_process_track, value=8, command=lambda: self.change_option("process_track", 8))
self.frame_language = Frame(self.root) self.frame_language = Frame(self.root)
self.frame_language.grid(row=1, column=1, sticky="E") self.frame_language.grid(row=1, column=1, sticky="E")
self.frame_game_path = LabelFrame(self.root, text=self.translate("Jeu original")) self.frame_game_path = LabelFrame(self.root, text=self.translate("Original game"))
self.frame_game_path.grid(row=2, column=1) self.frame_game_path.grid(row=2, column=1)
entry_game_path = Entry(self.frame_game_path, width=50) entry_game_path = Entry(self.frame_game_path, width=50)
entry_game_path.grid(row=1, column=1, sticky="NEWS") entry_game_path.grid(row=1, column=1, sticky="NEWS")
def select_path(): def select_path():
path = filedialog.askopenfilename(filetypes=((self.translate("Jeu Wii"), path = filedialog.askopenfilename(filetypes=((self.translate("Wii game"),
r"*.iso *.wbfs main.dol *.wia *.ciso"),)) r"*.iso *.wbfs main.dol *.wia *.ciso"),))
if os.path.exists(path): if os.path.exists(path):
entry_game_path.delete(0, END) entry_game_path.delete(0, END)
@ -89,14 +107,14 @@ def __init__(self):
self.frame_action.grid_forget() self.frame_action.grid_forget()
path = entry_game_path.get() path = entry_game_path.get()
if not (os.path.exists(path)): if not (os.path.exists(path)):
messagebox.showerror(self.translate("Erreur"), self.translate("Le chemin de fichier est invalide")) messagebox.showerror(self.translate("Error"), self.translate("The file path in invalid"))
return return
extension = get_extension(path) extension = get_extension(path)
if extension.upper() == "DOL": if extension.upper() == "DOL":
if messagebox.askyesno(self.translate("Attention"), if messagebox.askyesno(self.translate("Warning"),
self.translate("Ce dossier sera écrasé si vous installer le mod !\n" + self.translate("This directory will be overwritten if you install the "
"Êtes-vous sûr de vouloir l'utiliser ?")): "mod !\n Are you sure you want to use it ?")):
self.path_mkwf = os.path.realpath(path + "/../../") self.path_mkwf = os.path.realpath(path + "/../../")
else: return else: return
elif extension.upper() in ["ISO", "WBFS", "CSIO"]: elif extension.upper() in ["ISO", "WBFS", "CSIO"]:
@ -107,7 +125,7 @@ def __init__(self):
if not(os.path.exists(self.path_mkwf)): break if not(os.path.exists(self.path_mkwf)): break
directory_name, i = f"MKWiiFaraphel ({i})", i + 1 directory_name, i = f"MKWiiFaraphel ({i})", i + 1
self.Progress(show=True, indeter=True, statut=self.translate("Extraction du jeu...")) self.Progress(show=True, indeter=True, statut=self.translate("Extracting the game..."))
subprocess.call(["./tools/wit/wit", "EXTRACT", get_nodir(path), "--DEST", directory_name] subprocess.call(["./tools/wit/wit", "EXTRACT", get_nodir(path), "--DEST", directory_name]
, creationflags=CREATE_NO_WINDOW, cwd=get_dir(path)) , creationflags=CREATE_NO_WINDOW, cwd=get_dir(path))
@ -116,23 +134,22 @@ def __init__(self):
self.Progress(show=False) self.Progress(show=False)
else: else:
messagebox.showerror(self.translate("Erreur"), self.translate("Le type de fichier n'est pas reconnu")) messagebox.showerror(self.translate("Error"), self.translate("This file type is not supported"))
self.Progress(show=False) self.Progress(show=False)
return return
if glob.glob(self.path_mkwf + "/files/rel/lecode-???.bin"): # if a LECODE file is already here if glob.glob(self.path_mkwf + "/files/rel/lecode-???.bin"): # if a LECODE file is already here
messagebox.showwarning(self.translate("Attention"), messagebox.showwarning(self.translate("Warning"),
self.translate("Cette ROM est déjà moddé, " + self.translate("This game is already modded, it is not recommended to "
"il est déconseillé de l'utiliser pour installer le mod")) "use it to install the mod"))
try: try:
with open(self.path_mkwf + "/setup.txt") as f: setup = f.read() with open(self.path_mkwf + "/setup.txt") as f: setup = f.read()
setup = setup[setup.find("!part-id = ")+len("!part-id = "):] setup = setup[setup.find("!part-id = ")+len("!part-id = "):]
self.original_game_ID = setup[:setup.find("\n")] self.original_game_ID = setup[:setup.find("\n")]
except: except:
messagebox.showwarning(self.translate("Attention"), messagebox.showwarning(self.translate("Warning"),
self.transate("Impossible de trouver la région de votre jeu.\n" self.transate("Can't find game region.\nPAL region will be used."))
"la région PAL sera utilisé par défaut."))
self.original_game_ID = "RMCP01" self.original_game_ID = "RMCP01"
try: try:
self.original_region_ID = self.original_game_ID[3] self.original_region_ID = self.original_game_ID[3]
@ -150,7 +167,7 @@ def __init__(self):
t.start() t.start()
return t return t
self.button_game_extract = Button(self.frame_game_path_action, text=self.translate("Extraire le fichier"), self.button_game_extract = Button(self.frame_game_path_action, text=self.translate("Extract file"),
relief=RIDGE, command=use_path) relief=RIDGE, command=use_path)
self.button_game_extract.grid(row=1, column=1, sticky="NEWS") self.button_game_extract.grid(row=1, column=1, sticky="NEWS")
@ -160,26 +177,26 @@ def __init__(self):
self.patch_file().join() self.patch_file().join()
self.install_mod().join() self.install_mod().join()
if messagebox.askyesno(self.translate("Fonctionnalité expérimentale"), if messagebox.askyesno(self.translate("Experimental functionality"),
self.translate("Cette action va extraire / utiliser la ROM sélectionné," self.translate("This will extract the selected ROM, prepare files and install mod. "
" préparer les fichiers et installer le mod. Voulez-vous continuer ?")): "Do you wish to continue ?")):
t = Thread(target=func) t = Thread(target=func)
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
self.button_do_everything = Button(self.frame_game_path_action, text=self.translate("Tout faire"), self.button_do_everything = Button(self.frame_game_path_action, text=self.translate("Do everything"),
relief=RIDGE, command=do_everything) relief=RIDGE, command=do_everything)
self.button_do_everything.grid(row=1, column=2, sticky="NEWS") self.button_do_everything.grid(row=1, column=2, sticky="NEWS")
self.frame_action = LabelFrame(self.root, text=self.translate("Action")) self.frame_action = LabelFrame(self.root, text=self.translate("Action"))
self.button_prepare_file = Button(self.frame_action, text=self.translate("Preparer les fichiers"), relief=RIDGE, self.button_prepare_file = Button(self.frame_action, text=self.translate("Prepare files"), relief=RIDGE,
command=self.patch_file, width=45) command=self.patch_file, width=45)
self.button_prepare_file.grid(row=1, column=1, columnspan=2, sticky="NEWS") self.button_prepare_file.grid(row=1, column=1, columnspan=2, sticky="NEWS")
self.button_install_mod = Button(self.frame_action, text=self.translate("Installer le mod"), relief=RIDGE, self.button_install_mod = Button(self.frame_action, text=self.translate("Install mod"), relief=RIDGE,
command=self.install_mod, width=45) command=self.install_mod, width=45)
# Le boutton d'installation du mod n'est affiché qu'après avoir préparer les fichiers # Install mod button will only appear after prepare file step
self.progressbar = ttk.Progressbar(self.root) self.progressbar = ttk.Progressbar(self.root)
self.progresslabel = Label(self.root) self.progresslabel = Label(self.root)

View file

@ -18,33 +18,32 @@ def check_update(self):
(float(gitversion["version"]) == float(locversion["version"])) and (float(gitversion["version"]) == float(locversion["version"])) and
float(gitversion["subversion"]) > float(locversion["subversion"])): float(gitversion["subversion"]) > float(locversion["subversion"])):
if messagebox.askyesno( if messagebox.askyesno(
self.translate("Mise à jour disponible !"), self.translate("Update available !"),
self.translate("Une mise à jour est disponible, souhaitez-vous l'installer ?") + self.translate("An update is available, do you want to install it ?",
f"\n\nVersion : {locversion['version']}.{locversion['subversion']} -> {gitversion['version']}.{gitversion['subversion']}\n" f"\n\nVersion : {locversion['version']}.{locversion['subversion']} -> "
f"Changelog :\n{gitversion['changelog']}"): f"{gitversion['version']}.{gitversion['subversion']}\n"
f"Changelog :\n{gitversion['changelog']}")):
if not(os.path.exists("./Updater/Updater.exe")): if not(os.path.exists("./Updater/Updater.exe")):
dl = requests.get(gitversion["updater_bin"], allow_redirects=True) dl = requests.get(gitversion["updater_bin"], allow_redirects=True)
with open("./download.zip", "wb") as file: with open("./download.zip", "wb") as file:
print(self.translate("Téléchargement de Updater en cours...")) print(self.translate("Downloading the Updater..."))
file.write(dl.content) file.write(dl.content)
print(self.translate("fin du téléchargement, " print(self.translate("end of the download, extracting..."))
"début de l'extraction..."))
with zipfile.ZipFile("./download.zip") as file: with zipfile.ZipFile("./download.zip") as file:
file.extractall("./Updater/") file.extractall("./Updater/")
print(self.translate("fin de l'extraction")) print(self.translate("finished extracting"))
os.remove("./download.zip") os.remove("./download.zip")
print(self.translate("lancement de l'application...")) print(self.translate("starting application..."))
os.startfile(os.path.realpath("./Updater/Updater.exe")) os.startfile(os.path.realpath("./Updater/Updater.exe"))
sys.exit() sys.exit()
except requests.ConnectionError: except requests.ConnectionError:
messagebox.showwarning(self.translate("Attention"), messagebox.showwarning(self.translate("Warning"),
self.translate("Impossible de se connecter à internet. Le téléchargement sera " self.translate("Can't connect to internet. Download will be disabled."))
"automatiquement désactivé."))
self.option["disable_download"] = True self.option["disable_download"] = True
except: except SystemExit: pass
self.log_error() except: self.log_error()

View file

@ -5,29 +5,28 @@ from .definition import *
def create_lecode_config(self): def create_lecode_config(self):
try: try:
def get_star_text(track): def get_star_text(track):
if "warning" in track: warning = "!" * track["warning"]
else: warning = ""
if "score" in track: if "score" in track:
if track["score"] > 0: if 0 < track["score"] <= 3:
return "" * track["score"] + "" * (3 - track["score"]) + warning + " " star_text = "" * track["score"] + "" * (3 - track["score"])
return trackname_color[star_text] + " "
return "" return ""
def get_ctfile_text(track, race=False): def get_ctfile_text(track, race=False):
if race: if race:
return f' T {track["music"]}; ' + \ return (f' T {track["music"]}; '
f'{track["special"]}; ' + \ f'{track["special"]}; '
f'{"0x01" if track["new"] else "0x00"}; ' + \ f'{"0x01" if track["new"] else "0x00"}; '
f'"-"; ' + \ f'"-"; '
f'"{get_star_text(track)}{get_trackctname(track=track)}\\n{track["author"]}"; ' + \ f'"{get_star_text(track)}{self.get_trackctname(track=track, color=True)}\\n{track["author"]}"; '
f'"-"\n' f'"-"\n')
else: else:
return f' T {track["music"]}; ' + \ return (f' T {track["music"]}; '
f'{track["special"]}; ' + \ f'{track["special"]}; '
f'{"0x01" if track["new"] else "0x00"}; ' + \ f'{"0x01" if track["new"] else "0x00"}; '
f'"{get_trackctname(track=track)}"; ' + \ f'"{self.get_trackctname(track=track)}"; '
f'"{get_star_text(track)}{get_trackctname(track=track)}"; ' + \ f'"{get_star_text(track)}{self.get_trackctname(track=track, color=True)}"; '
f'"-"\n' f'"-"\n')
with open("./ct_config.json", encoding="utf-8") as f: with open("./ct_config.json", encoding="utf-8") as f:
ctconfig = json.load(f) ctconfig = json.load(f)
@ -35,17 +34,17 @@ def create_lecode_config(self):
with open("./file/CTFILE.txt", "w", encoding="utf-8") as ctfile, \ with open("./file/CTFILE.txt", "w", encoding="utf-8") as ctfile, \
open("./file/RCTFILE.txt", "w", encoding="utf-8") as rctfile: open("./file/RCTFILE.txt", "w", encoding="utf-8") as rctfile:
header = "#CT-CODE\n" +\ header = ("#CT-CODE\n"
"[RACING-TRACK-LIST]\n" +\ "[RACING-TRACK-LIST]\n"
"%LE-FLAGS=1\n" +\ "%LE-FLAGS=1\n"
"%WIIMM-CUP=1\n" +\ "%WIIMM-CUP=1\n"
"N N$SWAP | N$F_WII\n\n" "N N$SWAP | N$F_WII\n\n")
ctfile.write(header) ctfile.write(header)
rctfile.write(header) rctfile.write(header)
for cup in ctconfig["cup"]: # defined cup section for cup in ctconfig["cup"]: # defined cup section
_cup_config = ctconfig["cup"][cup] _cup_config = ctconfig["cup"][cup]
if int(cup) >= 9: # Course qui ne sont ni les originales, ni les courses aléatoires. if int(cup) >= 9: # Track that are not original and not random selection
cup = f'\nC "{_cup_config["name"]}"\n' cup = f'\nC "{_cup_config["name"]}"\n'
ctfile.write(cup) ctfile.write(cup)
rctfile.write(cup) rctfile.write(cup)
@ -55,7 +54,16 @@ def create_lecode_config(self):
ctfile.write(get_ctfile_text(_course_config, race=False)) ctfile.write(get_ctfile_text(_course_config, race=False))
rctfile.write(get_ctfile_text(_course_config, race=True)) rctfile.write(get_ctfile_text(_course_config, race=True))
for i, _course_config in enumerate(ctconfig["tracks_list"]): # undefined cup section tracks_list = ctconfig["tracks_list"]
if not self.boolvar_use_1star_track.get(): # if 1 star track are disabled, remove them
tracks_list = list(filter(lambda track: track.get("score") != 1, tracks_list))
if not self.boolvar_use_2star_track.get(): # if 2 stars track are disabled, remove them
tracks_list = list(filter(lambda track: track.get("score") != 2, tracks_list))
if not self.boolvar_use_3star_track.get(): # if 3 stars track are disabled, remove them
tracks_list = list(filter(lambda track: track.get("score") != 3, tracks_list))
# using dict.get allow track that with no "score" attribute to not raise an exception by returning None
for i, _course_config in enumerate(tracks_list): # undefined cup section
if i % 4 == 0: if i % 4 == 0:
cup = f'\nC "TL{i//4}"\n' cup = f'\nC "TL{i//4}"\n'
ctfile.write(cup) ctfile.write(cup)
@ -65,8 +73,8 @@ def create_lecode_config(self):
rctfile.write(get_ctfile_text(_course_config, race=True)) rctfile.write(get_ctfile_text(_course_config, race=True))
for _ in range(1, 4-(i%4)): # Complete cup if track are missing for _ in range(1, 4-(i%4)): # Complete cup if track are missing
ctfile.write(f' T T44; T44; 0x00; "_"; ""; "-"\n') ctfile.write(EMPTY_TRACK)
rctfile.write(f' T T44; T44; 0x00; "_"; ""; "-"\n') rctfile.write(EMPTY_TRACK)
except: except:
self.log_error() self.log_error()

View file

@ -1,3 +1,5 @@
import json
CREATE_NO_WINDOW = 0x08000000 CREATE_NO_WINDOW = 0x08000000
GITHUB_REPOSITORY = "Faraphel/MKWF-Install" GITHUB_REPOSITORY = "Faraphel/MKWF-Install"
GITHUB_CONTENT_ROOT = f"https://raw.githubusercontent.com/{GITHUB_REPOSITORY}/master/" GITHUB_CONTENT_ROOT = f"https://raw.githubusercontent.com/{GITHUB_REPOSITORY}/master/"
@ -13,23 +15,87 @@ get_track_szs = lambda track: f"./file/Track/{track}.szs"
region_ID = { region_ID = {
"J": "JAP", "J": "JAP",
"P": "PAL", "P": "PAL",
"K": "KOR", "K": "KO",
"E": "USA" "E": "USA"
} }
EMPTY_TRACK = ' T T44; T44; 0x00; "_"; ""; "-"\n'
def get_trackname(name=None, prefix=None, suffix=None, track=None): with open("./translation.json", encoding="utf-8") as f:
if track: translation_dict = json.load(f)
name = track["name"]
if "prefix" in track: prefix = track["prefix"]
if "suffix" in track: suffix = track["suffix"]
if prefix: name = prefix + " " + name
if suffix: name = name + " (" + suffix + ")"
return name
bmgID_track_move = {
"T11": 0x7008, "T12": 0x7001, "T13": 0x7002, "T14": 0x7004,
"T21": 0x7000, "T22": 0x7005, "T23": 0x7006, "T24": 0x7007,
"T31": 0x7009, "T32": 0x700f, "T33": 0x700b, "T34": 0x7003,
"T41": 0x700e, "T42": 0x700a, "T43": 0x700c, "T44": 0x700d,
def get_trackctname(name=None, prefix=None, suffix=None, track=None): "T51": 0x7010, "T52": 0x7014, "T53": 0x7019, "T54": 0x701a,
return get_trackname(name=name, prefix=prefix, suffix=suffix, track=track).replace("_", "") "T61": 0x701b, "T62": 0x701f, "T63": 0x7017, "T64": 0x7012,
"T71": 0x7015, "T72": 0x701e, "T73": 0x701d, "T74": 0x7011,
"T81": 0x7018, "T82": 0x7016, "T83": 0x7013, "T84": 0x701c,
}
trackname_color = {
"MSRDS": "\\\\c{green}MSRDS\\\\c{off}",
"CTR": "\\\\c{YOR4}CTR\\\\c{off}",
"CTTR": "\\\\c{YOR5}CTTR\\\\c{off}",
"CNR": "\\\\c{YOR5}CNR\\\\c{off}",
"DKR": "\\\\c{YOR6}DKR\\\\c{off}",
"LCP": "\\\\c{green}LCP\\\\c{off}",
"LEGO-R": "\\\\c{red2}LEGO-R\\\\c{off}",
"MP9": "\\\\c{YOR0}MP9\\\\c{off}",
"MSUSA": "\\\\c{green}MSUSA\\\\c{off}",
"FZMV": "\\\\c{YOR2}FZMV\\\\c{off}",
"KAR": "\\\\c{green}KAR\\\\c{off}",
"KO": "\\\\c{YOR5}KO\\\\c{off}",
"FZ": "\\\\c{YOR2}FZ\\\\c{off}",
"RV": "\\\\c{white}RV\\\\c{off}",
"SADX": "\\\\c{blue2}SADX\\\\c{off}",
"SCR": "\\\\c{YOR2}SCR\\\\c{off}",
"SH": "\\\\c{red}SH\\\\c{off}",
"SM64": "\\\\c{red1}SM64\\\\c{off}",
"SMB1": "\\\\c{red2}SMB1\\\\c{off}",
"SMB2": "\\\\c{red3}SMB2\\\\c{off}",
"SSBB": "\\\\c{red4}SSBB\\\\c{off}",
"SMS": "\\\\c{YOR6}SMS\\\\c{off}",
"SMO": "\\\\c{YOR7}SMO\\\\c{off}",
"VVVVVV": "\\\\c{blue}VVVVVV\\\\c{off}",
"WF": "\\\\c{green}WF\\\\c{off}",
"WP": "\\\\c{yellow}WP\\\\c{off}",
"Zelda OoT": "\\\\c{green}Zelda OoT\\\\c{off}",
"Zelda TP": "\\\\c{green}Zelda TP\\\\c{off}",
"Zelda WW": "\\\\c{green}Zelda WW\\\\c{off}",
"PMWR": "\\\\c{yellow}PMWR\\\\c{off}",
"SHR": "\\\\c{green}SHR\\\\c{off}",
"SK64": "\\\\c{green}SK64\\\\c{off}",
"SMG": "\\\\c{red2}SMG\\\\c{off}",
"Spyro 1": "\\\\c{blue}Spyro 1\\\\c{off}",
"Wii U": "\\\\c{red4}Wii U\\\\c{off}",
"Wii": "\\\\c{blue}Wii\\\\c{off}",
"3DS": "\\\\c{YOR3}3DS\\\\c{off}",
"DS": "\\\\c{white}DS\\\\c{off}",
"GCN": "\\\\c{blue2}GCN\\\\c{off}",
"GBA": "\\\\c{blue1}GBA\\\\c{off}",
"N64": "\\\\c{red}N64\\\\c{off}",
"SNES": "\\\\c{green}SNES\\\\c{off}",
"RMX": "\\\\c{YOR4}RMX\\\\c{off}",
"MKT": "\\\\c{YOR5}MKT\\\\c{off}",
"GP": "\\\\c{YOR6}GP\\\\c{off}",
"Boost": "\\\\c{YOR3}Boost\\\\c{off}",
"★★★": "\\\\c{YOR2}★★★\\\\c{off}",
"★★☆": "\\\\c{YOR2}★★☆\\\\c{off}",
"★☆☆": "\\\\c{YOR2}★☆☆\\\\c{off}",
"★★★!": "\\\\c{YOR4}★★★\\\\c{off}",
"★★☆!": "\\\\c{YOR4}★★☆\\\\c{off}",
"★☆☆!": "\\\\c{YOR4}★☆☆\\\\c{off}",
"★★★!!": "\\\\c{YOR6}★★★\\\\c{off}",
"★★☆!!": "\\\\c{YOR6}★★☆\\\\c{off}",
"★☆☆!!": "\\\\c{YOR6}★☆☆\\\\c{off}",
}
def filecopy(src, dst): def filecopy(src, dst):

View file

@ -39,11 +39,11 @@ def install_mod(self):
for ffp in fs[fp][nf]: count_rf(path=f) for ffp in fs[fp][nf]: count_rf(path=f)
### ###
extracted_file = [] extracted_file = []
max_step += 4 # PATCH main.dol et PATCH lecode.bin, conversion, changement d'ID max_step += 4 # PATCH main.dol and PATCH lecode.bin, converting, changing ID
self.Progress(show=True, indeter=False, statut=self.translate("Installation du mod"), max=max_step, step=0) self.Progress(show=True, indeter=False, statut=self.translate("Installing mod"), max=max_step, step=0)
def replace_file(path, file, subpath="/"): def replace_file(path, file, subpath="/"):
self.Progress(statut=self.translate("Modification de")+f"\n{get_nodir(path)}", add=1) self.Progress(statut=self.translate("Editing", "\n", get_nodir(path)), add=1)
extension = get_extension(path) extension = get_extension(path)
if extension == "szs": if extension == "szs":
@ -77,7 +77,7 @@ def install_mod(self):
for ffp in fs[fp][nf]: replace_file(path=f, subpath=nf, file=ffp) for ffp in fs[fp][nf]: replace_file(path=f, subpath=nf, file=ffp)
for file in extracted_file: for file in extracted_file:
self.Progress(statut=self.translate("Recompilation de")+f"\n{get_nodir(file)}", add=1) self.Progress(statut=self.translate("Recompilating", "\n", get_nodir(file)), add=1)
subprocess.run(["./tools/szs/wszst", "CREATE", get_nodir(file) + ".d", "-d", get_nodir(file), subprocess.run(["./tools/szs/wszst", "CREATE", get_nodir(file) + ".d", "-d", get_nodir(file),
"--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(file), "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(file),
check=True, stdout=subprocess.PIPE) check=True, stdout=subprocess.PIPE)
@ -106,7 +106,7 @@ def install_mod(self):
shutil.rmtree(self.path_mkwf + "/tmp/") shutil.rmtree(self.path_mkwf + "/tmp/")
outputformat = self.stringvar_game_format.get() outputformat = self.stringvar_game_format.get()
self.Progress(statut=self.translate("Conversion en")+f" {outputformat}", add=1) self.Progress(statut=self.translate("Converting to", " ", outputformat), add=1)
if outputformat in ["ISO", "WBFS", "CISO"]: if outputformat in ["ISO", "WBFS", "CISO"]:
self.path_mkwf_format = os.path.realpath(self.path_mkwf + "/../MKWFaraphel." + outputformat.lower()) self.path_mkwf_format = os.path.realpath(self.path_mkwf + "/../MKWFaraphel." + outputformat.lower())
@ -116,14 +116,14 @@ def install_mod(self):
check=True, stdout=subprocess.PIPE) check=True, stdout=subprocess.PIPE)
shutil.rmtree(self.path_mkwf) shutil.rmtree(self.path_mkwf)
self.Progress(statut=self.translate("Changement de l'ID du jeu"), add=1) self.Progress(statut=self.translate("Changing game's ID"), add=1)
subprocess.run(["./tools/wit/wit", "EDIT", get_nodir(self.path_mkwf_format), "--id", subprocess.run(["./tools/wit/wit", "EDIT", get_nodir(self.path_mkwf_format), "--id",
f"RMC{self.original_region_ID}60", "--name", f"Mario Kart Wii Faraphel {self.VERSION}", f"RMC{self.original_region_ID}60", "--name", f"Mario Kart Wii Faraphel {self.VERSION}",
"--modify", "ALL"], "--modify", "ALL"],
creationflags=CREATE_NO_WINDOW, cwd=get_dir(self.path_mkwf_format), creationflags=CREATE_NO_WINDOW, cwd=get_dir(self.path_mkwf_format),
check=True, stdout=subprocess.PIPE) check=True, stdout=subprocess.PIPE)
messagebox.showinfo(self.translate("Fin"), self.translate("L'installation est terminé !")) messagebox.showinfo(self.translate("End"), self.translate("The mod has been installed !"))
except: self.log_error() except: self.log_error()
finally: self.Progress(show=False) finally: self.Progress(show=False)

View file

@ -5,4 +5,4 @@ from tkinter import messagebox
def log_error(self): def log_error(self):
error = traceback.format_exc() error = traceback.format_exc()
with open("./error.log", "a") as f: f.write(f"---\n{error}\n") with open("./error.log", "a") as f: f.write(f"---\n{error}\n")
messagebox.showerror(self.translate("Erreur"), self.translate("Une erreur est survenue :") + f"\n{error}\n\n") messagebox.showerror(self.translate("Error"), self.translate("An error occured", " :", "\n", error, "\n\n"))

View file

@ -7,86 +7,6 @@ from .definition import *
def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /files/Scene/UI/ du jeu def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /files/Scene/UI/ du jeu
try: try:
bmgID_track_move = {
"T11": 0x7008, "T12": 0x7001, "T13": 0x7002, "T14": 0x7004,
"T21": 0x7000, "T22": 0x7005, "T23": 0x7006, "T24": 0x7007,
"T31": 0x7009, "T32": 0x700f, "T33": 0x700b, "T34": 0x7003,
"T41": 0x700e, "T42": 0x700a, "T43": 0x700c, "T44": 0x700d,
"T51": 0x7010, "T52": 0x7014, "T53": 0x7019, "T54": 0x701a,
"T61": 0x701b, "T62": 0x701f, "T63": 0x7017, "T64": 0x7012,
"T71": 0x7015, "T72": 0x701e, "T73": 0x701d, "T74": 0x7011,
"T81": 0x7018, "T82": 0x7016, "T83": 0x7013, "T84": 0x701c,
}
trackname_color = {
"MSRDS ": "\c{green}MSRDS\c{off} ",
"CTR ": "\c{YOR4}CTR\c{off} ",
"CTTR ": "\c{YOR5}CTTR\c{off} ",
"CNR ": "\c{YOR5}CNR\c{off} ",
"DKR ": "\c{YOR6}DKR\c{off} ",
"LCP ": "\c{green}LCP\c{off} ",
"LEGO-R ": "\c{red2}LEGO-R\c{off} ",
"MP9 ": "\c{YOR0}MP9\c{off} ",
"MSUSA ": "\c{green}MSUSA\c{off} ",
"FZMV ": "\c{YOR2}FZMV\c{off} ",
"KAR ": "\c{green}KAR\c{off} ",
"KO ": "\c{YOR5}KO\c{off} ",
"FZ ": "\c{YOR2}FZ\c{off} ",
"RV ": "\c{white}RV\c{off} ",
"SADX ": "\c{blue2}SADX\c{off} ",
"SCR ": "\c{YOR2}SCR\c{off} ",
"SH ": "\c{red}SH\c{off} ",
"SM64 ": "\c{red1}SM64\c{off} ",
"SMB1 ": "\c{red2}SMB1\c{off} ",
"SMB2 ": "\c{red3}SMB2\c{off} ",
"SSBB ": "\c{red4}SSBB\c{off} ",
"SMS ": "\c{YOR6}SMS\c{off} ",
"SMO ": "\c{YOR7}SMO\c{off} ",
"VVVVVV ": "\c{blue}VVVVVV\c{off} ",
"WF ": "\c{green}WF\c{off} ",
"WP ": "\c{yellow}WP\c{off} ",
"Zelda OoT ": "\c{green}Zelda OoT\c{off} ",
"Zelda TP ": "\c{green}Zelda TP\c{off} ",
"Zelda WW ": "\c{green}Zelda WW\c{off} ",
"PMWR ": "\c{yellow}PMWR\c{off} ",
"SHR ": "\c{green}SHR\c{off} ",
"SK64 ": "\c{green}SK64\c{off} ",
"SMG ": "\c{red2}SMG\c{off} ",
"Spyro 1 ": "\c{blue}Spyro 1\c{off} ",
"Aléatoire: ": "\c{white}Aléatoire: ",
"Random: ": "\c{white}Random: ",
"Zufällig: ": "\c{white}Zufällig: ",
"Casuale: ": "\c{white}Casuale: ",
"Aleatorio: ": "\c{white}Aleatorio: ",
"Wii U ": "WiiU ",
"Wii ": "\c{blue}Wii\c{off} ",
"WiiU ": "\c{red4}Wii U\c{off} ", # Permet d'éviter que Wii et Wii U se mélange
"3DS ": "\c{YOR3}3DS\c{off} ",
"DS ": "\c{white}DS\c{off} ",
"GCN ": "\c{blue2}GCN\c{off} ",
"GBA ": "\c{blue1}GBA\c{off} ",
"N64 ": "\c{red}N64\c{off} ",
"SNES ": "\c{green}SNES\c{off} ",
"RMX ": "\c{YOR4}RMX\c{off} ",
"MKT ": "\c{YOR5}MKT\c{off} ",
"GP ": "\c{YOR6}GP\c{off} ",
"(Boost)": "\c{YOR3}(Boost)\c{off}",
"★★★ ": "\c{YOR2}★★★\c{off} ",
"★★☆ ": "\c{YOR2}★★☆\c{off} ",
"★☆☆ ": "\c{YOR2}★☆☆\c{off} ",
"★★★! ": "\c{YOR4}★★★\c{off} ",
"★★☆! ": "\c{YOR4}★★☆\c{off} ",
"★☆☆! ": "\c{YOR4}★☆☆\c{off} ",
"★★★!! ": "\c{YOR6}★★★\c{off} ",
"★★☆!! ": "\c{YOR6}★★☆\c{off} ",
"★☆☆!! ": "\c{YOR6}★☆☆\c{off} ",
}
NINTENDO_CWF_REPLACE = "Wiimmfi" NINTENDO_CWF_REPLACE = "Wiimmfi"
MAINMENU_REPLACE = f"MKWFaraphel {self.VERSION}" MAINMENU_REPLACE = f"MKWFaraphel {self.VERSION}"
menu_replacement = { menu_replacement = {
@ -106,7 +26,7 @@ def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /
} }
bmglang = gamefile[-len("E.txt"):-len(".txt")] # Langue du fichier bmglang = gamefile[-len("E.txt"):-len(".txt")] # Langue du fichier
self.Progress(statut=self.translate("Patch des textes ") + bmglang, add=1) self.Progress(statut=self.translate("Patching text", " ", bmglang), add=1)
subprocess.run(["./tools/szs/wszst", "EXTRACT", get_nodir(gamefile), "-d", get_nodir(gamefile) + ".d", subprocess.run(["./tools/szs/wszst", "EXTRACT", get_nodir(gamefile), "-d", get_nodir(gamefile) + ".d",
"--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(gamefile)) "--overwrite"], creationflags=CREATE_NO_WINDOW, cwd=get_dir(gamefile))
@ -126,10 +46,10 @@ def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /
with open("./file/ExtraCommon.txt", "w", encoding="utf8") as f: with open("./file/ExtraCommon.txt", "w", encoding="utf8") as f:
f.write("#BMG\n\n" f.write("#BMG\n\n"
f" 703e\t= {self.translate('Aléatoire: Toutes les pistes', lang=bmglang)}\n" f" 703e\t= \\\\c{{white}}{self.translate('Random: All tracks', lang=bmglang)}\n"
f" 703f\t= {self.translate('Aléatoire: Pistes Originales', lang=bmglang)}\n" f" 703f\t= \\\\c{{white}}{self.translate('Random: Original tracks', lang=bmglang)}\n"
f" 7040\t= {self.translate('Aléatoire: Custom Tracks', lang=bmglang)}\n" f" 7040\t= \\\\c{{white}}{self.translate('Random: Custom Tracks', lang=bmglang)}\n"
f" 7041\t= {self.translate('Aléatoire: Pistes Nouvelles', lang=bmglang)}\n") f" 7041\t= \\\\c{{white}}{self.translate('Random: New tracks', lang=bmglang)}\n")
for bmgtrack in bmgtracks.split("\n"): for bmgtrack in bmgtracks.split("\n"):
if "=" in bmgtrack: if "=" in bmgtrack:
@ -138,7 +58,8 @@ def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /
if "T" in bmgtrack[:bmgtrack.find("=")]: if "T" in bmgtrack[:bmgtrack.find("=")]:
sTid = bmgtrack.find("T") sTid = bmgtrack.find("T")
Tid = bmgtrack[sTid:sTid + 3] Tid = bmgtrack[sTid:sTid + 3]
if Tid[1] in "1234": prefix = "Wii " # Si la course est original à la wii if Tid[1] in "1234":
prefix = trackname_color["Wii"] + " " # Si la course est original à la wii
Tid = hex(bmgID_track_move[Tid])[2:] Tid = hex(bmgID_track_move[Tid])[2:]
else: # Arena else: # Arena
@ -165,7 +86,8 @@ def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /
os.remove("./file/tmp/Common.bmg") os.remove("./file/tmp/Common.bmg")
os.remove("./file/ExtraCommon.txt") os.remove("./file/ExtraCommon.txt")
def finalise(file, bmgtext, replacement_list): def finalise(file, bmgtext, replacement_list=None):
if replacement_list:
for text, colored_text in replacement_list.items(): bmgtext = bmgtext.replace(text, colored_text) 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) with open(file, "w", encoding="utf-8") as f: f.write(bmgtext)
subprocess.run(["./tools/szs/wbmgt", "ENCODE", get_nodir(file), "--overwrite"], subprocess.run(["./tools/szs/wbmgt", "ENCODE", get_nodir(file), "--overwrite"],
@ -173,8 +95,8 @@ def patch_bmg(self, gamefile): # gamefile est le fichier .szs trouvé dans le /
os.remove(file) os.remove(file)
finalise(f"./file/Menu_{bmglang}.txt", bmgmenu, menu_replacement) finalise(f"./file/Menu_{bmglang}.txt", bmgmenu, menu_replacement)
finalise(f"./file/Common_{bmglang}.txt", bmgcommon, trackname_color) finalise(f"./file/Common_{bmglang}.txt", bmgcommon)
finalise(f"./file/Common_R{bmglang}.txt", rbmgcommon, trackname_color) finalise(f"./file/Common_R{bmglang}.txt", rbmgcommon)
except: except:
self.log_error() self.log_error()

View file

@ -30,9 +30,9 @@ def patch_ct_icon(self):
draw.text((4, 4), "CT", (255, 165, 0), font=font) draw.text((4, 4), "CT", (255, 165, 0), font=font)
font = ImageFont.truetype("./file/SuperMario256.ttf", 60) font = ImageFont.truetype("./file/SuperMario256.ttf", 60)
draw.text((5 - 2, 80 - 2), "%03i" % (i-10), (0, 0, 0), font=font) # i-10 car on ne compte pas les 8 coupes draw.text((5 - 2, 80 - 2), "%03i" % (i-10), (0, 0, 0), font=font) # i-10 because first 8 cup are not
draw.text((5 + 2, 80 - 2), "%03i" % (i-10), (0, 0, 0), font=font) # de base (0-7), la coupe aléatoire, et draw.text((5 + 2, 80 - 2), "%03i" % (i-10), (0, 0, 0), font=font) # counted as new, random cup,
draw.text((5 - 2, 80 + 2), "%03i" % (i-10), (0, 0, 0), font=font) # les icones droite et gauche. draw.text((5 - 2, 80 + 2), "%03i" % (i-10), (0, 0, 0), font=font) # left and right arrow
draw.text((5 + 2, 80 + 2), "%03i" % (i-10), (0, 0, 0), font=font) draw.text((5 + 2, 80 + 2), "%03i" % (i-10), (0, 0, 0), font=font)
draw.text((5, 80), "%03i" % (i-10), (255, 165, 0), font=font) draw.text((5, 80), "%03i" % (i-10), (255, 165, 0), font=font)

View file

@ -9,24 +9,23 @@ def patch_file(self):
try: try:
if not(os.path.exists("./file/Track-WU8/")): os.makedirs("./file/Track-WU8/") if not(os.path.exists("./file/Track-WU8/")): os.makedirs("./file/Track-WU8/")
with open("./convert_file.json") as f: fc = json.load(f) with open("./convert_file.json") as f: fc = json.load(f)
tracks, total_track = self.count_track() max_step = len(fc["img"]) + self.TOTAL_TRACK + 3 + len("EGFIS")
max_step = len(fc["img"]) + total_track + 3 + len("EGFIS")
self.Progress(show=True, indeter=False, statut=self.translate("Conversion des fichiers"), max=max_step, step=0) self.Progress(show=True, indeter=False, statut=self.translate("Converting files"), max=max_step, step=0)
self.Progress(statut=self.translate("Configuration de LE-CODE"), add=1) self.Progress(statut=self.translate("Configurating LE-CODE"), add=1)
self.create_lecode_config() self.create_lecode_config()
self.Progress(statut=self.translate("Création de ct_icon.png"), add=1) self.Progress(statut=self.translate("Creating ct_icon.png"), add=1)
self.patch_ct_icon() self.patch_ct_icon()
self.Progress(statut=self.translate("Création des images descriptives"), add=1) self.Progress(statut=self.translate("Creating descriptive images"), add=1)
self.patch_img_desc() self.patch_img_desc()
self.patch_image(fc) self.patch_image(fc)
for file in glob.glob(self.path_mkwf+"/files/Scene/UI/MenuSingle_?.szs"): self.patch_bmg(file) for file in glob.glob(self.path_mkwf+"/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. # MenuSingle could be any other file, Common and Menu are all the same in all other files.
self.patch_autoadd() self.patch_autoadd()
if self.patch_track(tracks, total_track) != 0: return if self.patch_track() != 0: return
self.button_install_mod.grid(row=2, column=1, columnspan=2, sticky="NEWS") self.button_install_mod.grid(row=2, column=1, columnspan=2, sticky="NEWS")
self.button_install_mod.config(text=f'{self.translate("Installer le mod")} (v{self.VERSION})') self.button_install_mod.config(text=self.translate("Install mod", " (v", self.VERSION, ")"))
except: self.log_error() except: self.log_error()
finally: self.Progress(show=False) finally: self.Progress(show=False)

View file

@ -4,6 +4,6 @@ import subprocess
def patch_image(self, fc): def patch_image(self, fc):
for i, file in enumerate(fc["img"]): for i, file in enumerate(fc["img"]):
self.Progress(statut=self.translate("Conversion des images") + f"\n({i + 1}/{len(fc['img'])}) {file}", add=1) self.Progress(statut=self.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"], subprocess.run(["./tools/szs/wimgt", "ENCODE", "./file/" + file, "-x", fc["img"][file], "--overwrite"],
creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE) creationflags=CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE)

View file

@ -6,16 +6,54 @@ import json
import os import os
def count_track(self): def get_trackname(self, track, color=False):
hl_prefix, hl_suffix = "", ""
if color:
if track.get("since_version") == self.stringvar_mark_track_from_version.get():
hl_prefix, hl_suffix = "\\\\c{blue1}", "\\\\c{off}"
name = track["name"]
name = hl_prefix + name + hl_suffix
if "prefix" in track:
prefix = track["prefix"]
if color:
if prefix in trackname_color:
prefix = trackname_color[prefix]
name = prefix + " " + name
if "suffix" in track:
suffix = track["suffix"]
if color:
if suffix in trackname_color:
suffix = trackname_color[suffix]
name = name + " (" + suffix + ")"
return name
def get_trackctname(self, *args, **kwargs):
return self.get_trackname(*args, **kwargs).replace("_", "")
def load_ct_config(self):
tracks = [] tracks = []
with open("./ct_config.json", encoding="utf-8") as f: ctconfig = json.load(f) with open("./ct_config.json", encoding="utf-8") as f: ctconfig = json.load(f)
self.VERSION = ctconfig["version"]
for cup in ctconfig["cup"].values(): for cup in ctconfig["cup"].values(): # defined order tracks
if not (cup["locked"]): tracks.extend(cup["courses"].values()) if not (cup["locked"]): tracks.extend(cup["courses"].values())
tracks.extend(ctconfig["tracks_list"])
tracks = [dict(t) for t in {tuple(d.items()) for d in tracks}] tracks.extend(ctconfig["tracks_list"]) # unordered tracks
total_track = len(tracks)
return tracks, total_track self.TRACKS = [dict(t) for t in {tuple(d.items()) for d in tracks}] # removing duplicate
self.TOTAL_TRACK = len(tracks)
self.VERSION = ctconfig["version"]
self.ALL_VERSION = []
for track in self.TRACKS:
if not track.get("since_version") in self.ALL_VERSION:
self.ALL_VERSION.append(track["since_version"])
self.ALL_VERSION.sort()
def patch_autoadd(self): def patch_autoadd(self):
@ -29,19 +67,18 @@ def patch_autoadd(self):
shutil.rmtree(self.path_mkwf + "/tmp/") shutil.rmtree(self.path_mkwf + "/tmp/")
def patch_track(self, tracks, total_track="?"): def patch_track(self):
max_process = self.intvar_process_track.get() max_process = self.intvar_process_track.get()
process_list = {} process_list = {}
error_count, error_max = 0, 3 error_count, error_max = 0, 3
def add_process(track): def add_process(track):
track_file = get_trackname(track=track) track_file = self.get_trackname(track=track)
nonlocal error_count, error_max, process_list nonlocal error_count, error_max, process_list
process_list[track_file] = None # Used for process_list[track_file] = None # Used for
self.Progress(statut=self.translate("Conversion des courses") + f"\n({i + 1}/{total_track})\n" + self.Progress(statut=self.translate("Converting tracks", f"\n({i + 1}/{self.TOTAL_TRACK})\n",
"\n".join(process_list.keys()), add=1) "\n".join(process_list.keys())), add=1)
for _track in [get_track_szs(track_file), get_track_wu8(track_file)]: for _track in [get_track_szs(track_file), get_track_wu8(track_file)]:
if os.path.exists(_track): if os.path.exists(_track):
@ -54,14 +91,14 @@ def patch_track(self, tracks, total_track="?"):
error_count += 1 error_count += 1
if error_count > error_max: # Too much track wasn't correctly converted if error_count > error_max: # Too much track wasn't correctly converted
messagebox.showerror( messagebox.showerror(
self.translate("Erreur"), self.translate("Error"),
self.translate("Trop de course ont eu une erreur du téléchargement.")) self.translate("Too much tracks had a download issue."))
return -1 return -1
else: else:
messagebox.showwarning(self.translate("Attention"), messagebox.showwarning(self.translate("Warning"),
self.translate("Impossible de télécharger cette course ! (") + self.translate("Can't download this track !",
str(error_count) + "/" + str(error_max) + ")") f" ({error_count} / {error_max})"))
elif download_returncode == 2: break # Si le téléchargement est désactivé, ne pas checker le sha1 elif download_returncode == 2: break # if download is disabled, don't check sha1
if "sha1" in track: if "sha1" in track:
if not self.boolvar_dont_check_track_sha1.get(): if not self.boolvar_dont_check_track_sha1.get():
@ -69,8 +106,8 @@ def patch_track(self, tracks, total_track="?"):
error_count += 1 error_count += 1
if error_count > error_max: # Too much track wasn't correctly converted if error_count > error_max: # Too much track wasn't correctly converted
messagebox.showerror( messagebox.showerror(
self.translate("Erreur"), self.translate("Error"),
self.translate("Trop de course ont eu une erreur de vérification de sha1.")) self.translate("Too much tracks had an issue during sha1 check."))
return -1 return -1
continue continue
@ -84,9 +121,8 @@ def patch_track(self, tracks, total_track="?"):
"./file/Track/%N.szs", "--szs", "--overwrite", "--autoadd-path", "./file/Track/%N.szs", "--szs", "--overwrite", "--autoadd-path",
"./file/auto-add/"], creationflags=CREATE_NO_WINDOW, stderr=subprocess.PIPE) "./file/auto-add/"], creationflags=CREATE_NO_WINDOW, stderr=subprocess.PIPE)
else: else:
messagebox.showerror(self.translate("Erreur"), messagebox.showerror(self.translate("Error"),
self.translate("Impossible de convertir la course.\n" self.translate("Can't convert track.\nEnable track download and retry."))
"Réactiver le téléchargement des courses et réessayer."))
return -1 return -1
elif self.boolvar_del_track_after_conv.get(): os.remove(get_track_wu8(track_file)) elif self.boolvar_del_track_after_conv.get(): os.remove(get_track_wu8(track_file))
return 0 return 0
@ -106,16 +142,15 @@ def patch_track(self, tracks, total_track="?"):
error_count += 1 error_count += 1
if error_count > error_max: # Too much track wasn't correctly converted if error_count > error_max: # Too much track wasn't correctly converted
messagebox.showerror( messagebox.showerror(
self.translate("Erreur"), self.translate("Error"),
self.translate("Trop de course ont eu une erreur de conversion.")) self.translate("Too much track had a conversion issue."))
return -1 return -1
else: # if the error max hasn't been reach else: # if the error max hasn't been reach
messagebox.showwarning( messagebox.showwarning(
self.translate("Attention"), self.translate("Warning"),
self.translate("La course ") + self.translate("The track", " ", get_track_wu8(track_file),
get_track_wu8(track_file) + "do not have been properly converted.",
self.translate(" n'a pas été correctement converti. (") + f" ({error_count} / {error_max})"))
str(error_count) + "/" + str(error_max) + ")")
else: else:
if self.boolvar_del_track_after_conv.get(): os.remove(get_track_wu8(track_file)) if self.boolvar_del_track_after_conv.get(): os.remove(get_track_wu8(track_file))
else: else:
@ -125,7 +160,7 @@ def patch_track(self, tracks, total_track="?"):
if len(process_list): return 1 if len(process_list): return 1
else: return 0 else: return 0
for i, track in enumerate(tracks): for i, track in enumerate(self.TRACKS):
while True: while True:
if len(process_list) < max_process: if len(process_list) < max_process:
returncode = add_process(track) returncode = add_process(track)

View file

@ -1,20 +1,21 @@
import json from .definition import translation_dict
def translate(self, text, lang = None): def translate(self, *texts, lang=None):
if lang == None: lang = self.stringvar_language.get() if lang is None: lang = self.stringvar_language.get()
elif lang == "E": lang = "en" elif lang == "F": lang = "fr"
elif lang == "G": lang = "ge" elif lang == "G": lang = "ge"
elif lang == "I": lang = "it" elif lang == "I": lang = "it"
elif lang == "S": lang = "sp" elif lang == "S": lang = "sp"
with open("./translation.json", encoding="utf-8") as f: if lang in translation_dict:
translation = json.load(f) _lang_trad = translation_dict[lang]
if lang in translation: translated_text = ""
_lang_trad = translation[lang] for text in texts:
if text in _lang_trad: return _lang_trad[text] if text in _lang_trad: translated_text += _lang_trad[text]
else: else: translated_text += text
print(f"no translation for : \"{text}\"") return translated_text
return text
return "".join(texts) # if no translation language is found

View file

@ -1,87 +1,90 @@
{ {
"en": { "fr": {
"Ne pas vérifier le sha1 des courses": "Don't check for track's sha1", "MKWFaraphel Installer": "MKWFaraphel Installateur",
"Impossible de se connecter à internet. Le téléchargement sera automatiquement désactivé.": "Can't connect to internet, download will automatically be disabled.", "FST (Directory)": "FST (Dossier)",
"Advanced": "Avancé",
"Language": "Langage",
"Disable downloads": "Désactiver les téléchargements",
"Delete track after wu8 to szs conversion": "Supprimer les courses wu8 après conversion en szs",
"Don't check for update": "Ne pas vérifier les mises à jour",
"Don't check track's sha1": "Ne pas vérifier le sha1 des courses",
"Number of track conversion process": "Nombre de processus de conversion de course",
"process": "processus",
"Original game": "Jeu original",
"Wii game": "Jeu Wii",
"Error": "Erreur",
"The file path in invalid": "Le chemin de fichier est invalide",
"Warning": "Attention",
"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",
"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",
"This will extract the selected ROM, prepare files and install mod. Do you wish to continue ?": "Cette action va extraire / utiliser la ROM sélectionné, préparer les fichiers et installer le mod. Voulez-vous continuer ?",
"Do everything": "Tout faire",
"Action": "Action",
"Prepare files": "Préparer les fichiers",
"Install mod": "Installer le mod",
"Update available !": "Mise à jour disponible !",
"An update is available, do you want to install it ?": "Une mise à jour est disponible, souhaitez-vous l'installer ?",
"Downloading the Updater...": "Téléchargement de Updater en cours...",
"end of the download, extracting...": "fin du téléchargement, début de l'extraction...",
"finished extracting": "fin de l'extraction",
"starting application...": "lancement de l'application...",
"Can't connect to internet. Download will be disabled.": "Impossible de se connecter à internet. Le téléchargement sera désactivé.",
"Installing mod": "Installation du mod",
"Editing": "Modification de",
"Recompilating": "Recompilation de",
"Patch main.dol": "Patch main.dol", "Patch main.dol": "Patch main.dol",
"Patch lecode.bin": "Patch lecode.bin", "Patch lecode.bin": "Patch lecode.bin",
"Installation du mod": "Installing mod", "Converting to": "Conversion en",
"Impossible de convertir la course.\nRéactiver le téléchargement des courses et réessayer.": "Impossible to convert track.\nEnable track download and retry.", "Changing game's ID": "Changement de l'ID du jeu",
"Langage": "Language", "End": "Fin",
"Format": "Format", "The mod has been installed !": "L'installation est terminé !",
"FST (Dossier)": "FST (Directory)", "An error occured": "Une erreur est survenue",
"Avancé": "Advanced", "Patching text": "Patch des textes",
"Désactiver les téléchargements": "Disable downloads", "Random: All tracks": "Aléatoire: Toutes les pistes",
"Supprimer les courses wu8 après conversion en szs": "Delete wu8 track after conversion to szs", "Random: Original tracks": "Aléatoire: Pistes Originales",
"Ne pas vérifier les mises à jour": "Don't check for update", "Random: Custom Tracks": "Aléatoire: Custom Tracks",
"Nombre de processus de conversion de course :": "Number of process for track conversion :", "Random: New tracks": "Aléatoire: Pistes Nouvelles",
"1 processus": "1 process", "Converting files": "Conversion des fichiers",
"2 processus": "2 process", "Configurating LE-CODE": "Configuration de LE-CODE",
"4 processus": "4 process", "Creating ct_icon.png": "Création de ct_icon.png",
"8 processus": "8 process", "Creating descriptive images": "Création des images descriptives",
"La course ": "The track ", "Converting images": "Conversion des images",
" n'a pas été correctement converti. (": "hasn't been correctly converted. (", "Converting tracks": "Conversion des courses",
"Trop de course ont eu une erreur de conversion.": "Too much track had a conversion issue.", "Too much tracks had a download issue.": "Trop de course ont eu une erreur de téléchargement.",
"Une erreur est survenue :": "An error occured :", "Can't download this track !": "Impossible de télécharger cette course !",
"Cette action va extraire / utiliser la ROM sélectionné, préparer les fichiers et installer le mod. Voulez-vous continuer ?": "This will extract the selected ROM, prepare files and install mod. Do you wish to continue ?", "Too much tracks had an issue during sha1 check.": "Trop de course ont eu une erreur de vérification de sha1.",
"Fonctionnalité expérimentale": "Experimental functionality", "Can't convert track.\nEnable track download and retry.": "Impossible de convertir la course.\nRéactiver le téléchargement des courses et réessayer.",
"Tout faire": "Do everything", "Too much track had a conversion issue.": "Trop de course ont eu une erreur de conversion.",
"Création des images descriptives": "Creation of descriptive images", "La course": "The track",
"Création de ct_icon.png": "Creating ct_icon.png", "do not have been properly converted.": "n'a pas été correctement converti.",
"Patch des textes ": "Patching text ", "Track selection": "Sélection de course",
"Aléatoire: Toutes les pistes": "Random: All tracks", "Select": "Sélectionner",
"Aléatoire: Pistes Originales": "Random: Original tracks", "star": "étoile",
"Aléatoire: Custom Tracks": "Random: Custom Tracks", "stars": "étoiles",
"Aléatoire: Pistes Nouvelles": "Random: New tracks", "Mark all tracks from version": "Marquer toutes les courses de la version",
"MKWFaraphel Installateur": "MKWFaraphel Installer", "None": "Aucune"
"Jeu Wii": "Wii game",
"Jeu original": "Original game",
"Erreur": "Error",
"Le chemin de fichier est invalide": "The file path in invalid",
"Attention": "Warning",
"Ce dossier sera écrasé si vous installer le mod !\nÊtes-vous sûr de vouloir l'utiliser ?": "This directory will be overwritten if you install the mod !\nAre you sure you want to use it ?",
"Extraction du jeu...": "Extracting the game...",
"Le type de fichier n'est pas reconnu": "This file type is not supported",
"Cette ROM est déjà moddé, il est déconseillé de l'utiliser pour installer le mod": "This game is already modded, it is not recommended to use it to install the mod",
"Extraire le fichier": "Extract file",
"Preparer les fichiers": "Prepare files",
"Action": "Action",
"Installer le mod": "Install mod",
"Dossier": "Directory",
"Langage : ": "Language : ",
"Mise à jour disponible !": "Update available !",
"Une mise à jour est disponible, souhaitez-vous l'installer ?": "An update is available, do you want to install it ?",
"Téléchargement de Updater en cours...": "Downloading the Updater...",
"fin du téléchargement, début de l'extraction...": "end of the download, extracting...",
"fin de l'extraction": "finished extracting",
"lancement de l'application...": "starting application...",
"Modification de": "Modifying",
"Recompilation de": "Recompilating",
"Conversion en": "Converting to",
"Changement de l'ID du jeu": "editing game's ID",
"Fin": "End",
"L'installation est terminé !": "The mod has been installed !",
"Conversion des fichiers": "Converting files",
"Conversion des images": "Converting images",
"Conversion des textes": "Converting texts",
"Conversion des courses": "Converting races",
"Configuration de LE-CODE": "Configurating LE-CODE"
}, },
"ge": { "ge": {
"Aléatoire: Toutes les pistes": "Zufällig: Alle Spuren", "Random: All tracks": "Zufällig: Alle Spuren",
"Aléatoire: Pistes Originales": "Zufällig: Original-Spuren", "Random: Original tracks": "Zufällig: Original-Spuren",
"Aléatoire: Custom Tracks": "Zufällig: Custom Tracks", "Random: Custom Tracks": "Zufällig: Custom Tracks",
"Aléatoire: Pistes Nouvelles": "Zufällig: Neue Spuren" "Random: New tracks": "Zufällig: Neue Spuren"
}, },
"it": { "it": {
"Aléatoire: Toutes les pistes": "Casuale: Tutte le tracce", "Random: All tracks": "Casuale: Tutte le tracce",
"Aléatoire: Pistes Originales": "Casuale: Tracce originali", "Random: Original tracks": "Casuale: Tracce originali",
"Aléatoire: Custom Tracks": "Casuale: Custom Tracks", "Random: Custom Tracks": "Casuale: Custom Tracks",
"Aléatoire: Pistes Nouvelles": "Casuale: Nuovi brani" "Random: New tracks": "Casuale: Nuovi brani"
}, },
"sp": { "sp": {
"Aléatoire: Toutes les pistes": "Aleatorio: Todas las pistas", "Random: All tracks": "Aleatorio: Todas las pistas",
"Aléatoire: Pistes Originales": "Aleatorio: Pistas originales", "Random: Original tracks": "Aleatorio: Pistas originales",
"Aléatoire: Custom Tracks": "Aleatorio: Custom Tracks", "Random: Custom Tracks": "Aleatorio: Custom Tracks",
"Aléatoire: Pistes Nouvelles": "Aleatorio: Pistas nuevas" "Random: New tracks": "Aleatorio: Pistas nuevas"
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"version": "0.8", "version": "0.9",
"subversion": "0", "subversion": "0",
"changelog": "", "changelog": "- Updated GBA Sky Garden, Quag Beach -> Pokemon Beach, Rainbow Mountain Fortress, Sarasa Kingdom, Sunset Sewer, Torrential Flood Lake, Red Sector B -> Red Sector A.\n- Added 33 new tracks.\n\n- Added a page in the github wiki about forking the project to make your own ISO patcher based on MKWF-Install (https://github.com/Faraphel/MKWF-Install/wiki/Customizing-the-installer)\n- Reworked translation code.\n- English is now used in the program instead of French to make the code easier to read for everyone !\n- Reworked prefix and suffix code, now only them can be colored (the name will no longer be colored if it contains N64 for example)\n\n- You can now color track from a specific version (for example, if you only want to play on new track)\n- Added a \"track selection\" menu allowing you to only patch game for track with a certain amount of stars. (This does not apply for track from other Mario Kart like SNES, GBA, GCN, ...)\n\n- An error occurred with the updater making it unusable. If you had it, please delete ./Updater directory.\n- Fixed an error occurring when trying to update.",
"download_bin": "https://github.com/Faraphel/MKWF-Install/releases/download/0.8/MKWF.v0.8.zip", "download_bin": "https://github.com/Faraphel/MKWF-Install/releases/download/0.9/MKWF.v0.9.zip",
"updater_bin": "https://github.com/Faraphel/MKWF-Install/raw/master/Updater/Updater.zip" "updater_bin": "https://github.com/Faraphel/MKWF-Install/raw/master/Updater/Updater.zip"
} }