implemented a very experimental function for packs

This commit is contained in:
Faraphel 2022-01-18 14:04:51 +01:00
parent fd997719b3
commit 6042ca441a
8 changed files with 120 additions and 146 deletions

View file

@ -7,28 +7,6 @@ from source.Cup import Cup
from source.Track import Track, HiddenTrackAttr
def get_cup_icon(cup_id: [str, int], font_path: str = "./file/SuperMario256.ttf",
cup_icon_dir: str = "./file/cup_icon") -> Image:
"""
:param cup_id: id of the cup
:param cup_icon_dir: directory to cup icon
:param font_path: path to the font used to generate icon
:return: cup icon
"""
if os.path.exists(f"{cup_icon_dir}/{cup_id}.png"):
cup_icon = Image.open(f"{cup_icon_dir}/{cup_id}.png").resize((128, 128))
else:
cup_icon = Image.new("RGBA", (128, 128))
draw = ImageDraw.Draw(cup_icon)
font = ImageFont.truetype(font_path, 90)
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
font = ImageFont.truetype(font_path, 60)
draw.text((5, 80), "%03i" % cup_id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
return cup_icon
class CT_Config:
def __init__(self, version: str = None, name: str = None, nickname: str = None,
game_variant: str = "01", gui=None, region: int = None, cheat_region: int = None,
@ -117,6 +95,27 @@ class CT_Config:
ctfile.write(cup.get_ctfile_cup(race=False, **kwargs))
rctfile.write(cup.get_ctfile_cup(race=True, **kwargs))
def get_cup_icon(self, cup_id: [str, int], font_path: str = "./assets/SuperMario256.ttf",
cup_icon_dir: str = "./file/cup_icon") -> Image:
"""
:param cup_id: id of the cup
:param cup_icon_dir: directory to cup icon
:param font_path: path to the font used to generate icon
:return: cup icon
"""
if os.path.exists(f"{cup_icon_dir}/{cup_id}.png"):
cup_icon = Image.open(f"{cup_icon_dir}/{cup_id}.png").resize((128, 128))
else:
cup_icon = Image.new("RGBA", (128, 128))
draw = ImageDraw.Draw(cup_icon)
font = ImageFont.truetype(font_path, 90)
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
font = ImageFont.truetype(font_path, 60)
draw.text((5, 80), "%03i" % cup_id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
return cup_icon
def get_cticon(self) -> Image:
"""
get all cup icon into a single image
@ -133,7 +132,7 @@ class CT_Config:
for index, cup_id in enumerate(icon_files):
# index is a number, id can be string or number ("left", 0, 12, ...)
cup_icon = get_cup_icon(cup_id)
cup_icon = self.get_cup_icon(cup_id, cup_icon_dir=self.pack_path+"/file/cup_icon/")
ct_icon.paste(cup_icon, (0, index * CT_ICON_WIDTH))
return ct_icon
@ -159,19 +158,20 @@ class CT_Config:
self.unordered_tracks = []
self.all_tracks = []
self.pack_path = pack_path
# default track
self.default_track = Track()
self.default_track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"])
for cup_json in ctconfig_json["cup"] if "cup" in ctconfig_json else []: # tracks with defined order
cup = Cup(default_track=self.default_track)
cup.load_from_json(cup_json)
if not cup.locked: # locked cup are not useful (they are original track or random track)
self.ordered_cups.append(cup)
self.all_tracks.extend(cup.tracks)
self.ordered_cups.append(cup)
self.all_tracks.extend(cup.tracks)
for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []: # unordered tracks
track = Track()
track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
track.load_from_json(track_json)
self.unordered_tracks.append(track)
self.all_tracks.append(track)
@ -182,7 +182,7 @@ class CT_Config:
self.nickname = ctconfig_json["nickname"] if "nickname" in ctconfig_json else self.name
if "game_variant" in ctconfig_json: self.game_variant = ctconfig_json["game_variant"]
for param in ["region", "cheat_region", "tags_color", "prefix_list", "suffix_list", "tag_retro", "pack_path"]:
for param in ["region", "cheat_region", "tags_color", "prefix_list", "suffix_list", "tag_retro"]:
setattr(self, param, ctconfig_json.get(param))
return self

View file

@ -9,7 +9,6 @@ class Cup:
track2: Track = None,
track3: Track = None,
track4: Track = None,
locked: bool = False,
*args, **kwargs):
"""
class of a cup
@ -18,13 +17,11 @@ class Cup:
:param track2: second track
:param track3: third track
:param track4: fourth track
:param locked: is the track locked (used to load ctconfig in CT_Config)
:param args: other args that I could add in the future
:param kwargs: other kwargs that I could add in the future
"""
self.name = name
self.locked = locked
self.tracks = [
track1 if track1 else default_track.copy(),
track2 if track2 else default_track.copy(),
@ -51,7 +48,7 @@ class Cup:
for key, value in cup.items(): # load all value in the json as class attribute
if key == "tracks": # if the key is tracks
for i, track_json in enumerate(value): # load all tracks from their json
self.tracks[int(i)].load_from_json(track_json)
self.tracks[i].load_from_json(track_json)
else:
setattr(self, key, value)

View file

@ -213,14 +213,14 @@ class Game:
szs_extract_path = path + ".d"
if os.path.exists(szs_extract_path + subpath):
if subpath[-1] == "/":
shutil.copyfile(f"./file/{file}", szs_extract_path + subpath + file)
shutil.copyfile(f"{self.ctconfig.pack_path}/file/{file}", szs_extract_path + subpath + file)
else:
shutil.copyfile(f"./file/{file}", szs_extract_path + subpath)
shutil.copyfile(f"{self.ctconfig.pack_path}/file/{file}", szs_extract_path + subpath)
elif path[-1] == "/":
shutil.copyfile(f"./file/{file}", path + file)
shutil.copyfile(f"{self.ctconfig.pack_path}/file/{file}", path + file)
else:
shutil.copyfile(f"./file/{file}", path)
shutil.copyfile(f"{self.ctconfig.pack_path}/file/{file}", path)
for fp in fs:
for f in glob.glob(self.path + "/files/" + fp, recursive=True):
@ -282,10 +282,11 @@ class Game:
"""
self.gui.progress(statut=self.gui.translate("Patch lecode.bin"), add=1)
lpar_path = "./file/lpar-debug.txt" if self.gui.boolvar_use_debug_mode.get() else "./file/lpar-default.txt"
lpar_path = f"{self.ctconfig.pack_path}/file/lpar-debug.txt" \
if self.gui.boolvar_use_debug_mode.get() else f"{self.ctconfig.pack_path}/file/lpar-default.txt"
lec.patch(
lecode_file=f"./file/lecode-{self.region}.bin",
lecode_file=f"{self.ctconfig.pack_path}/file/lecode-{self.region}.bin",
dest_lecode_file=f"{self.path}/files/rel/lecode-{self.region}.bin",
game_track_path=f"{self.path}/files/Race/Course/",
copy_track_paths=[f"./file/Track/"],
@ -422,13 +423,12 @@ class Game:
:param bmg_language: language of the bmg file
:return: the replaced bmg file
"""
with open("./file_process.json", encoding="utf8") as fp_file:
with open(f"{self.ctconfig.pack_path}/file_process.json", encoding="utf8") as fp_file:
file_process = json.load(fp_file)
for bmg_process in file_process["bmg"]:
if "language" in bmg_process:
if bmg_language not in bmg_process["language"]:
continue
if "language" in bmg_process and bmg_language not in bmg_process["language"]:
continue
for data, data_replacement in bmg_process["data"].items():
for key, replacement in bmg_replacement.items():
@ -462,17 +462,18 @@ class Game:
bmg.encode(file)
os.remove(file)
save_bmg(f"./file/Menu_{bmglang}.txt", process_bmg_replacement(bmgmenu, bmglang))
save_bmg(f"./file/Common_{bmglang}.txt", process_bmg_replacement(bmgcommon, bmglang))
save_bmg(f"./file/Common_R{bmglang}.txt", process_bmg_replacement(rbmgcommon, bmglang))
save_bmg(f"{self.ctconfig.pack_path}/file/Menu_{bmglang}.txt", process_bmg_replacement(bmgmenu, bmglang))
save_bmg(f"{self.ctconfig.pack_path}/file/Common_{bmglang}.txt", process_bmg_replacement(bmgcommon, bmglang))
save_bmg(f"{self.ctconfig.pack_path}/file/Common_R{bmglang}.txt", process_bmg_replacement(rbmgcommon, bmglang))
def patch_file(self):
"""
Prepare all files to install the mod (track, bmg text, descriptive image, ...)
"""
try:
if not (os.path.exists("./file/Track-WU8/")): os.makedirs("./file/Track-WU8/")
with open("./file_process.json", encoding="utf8") as fp_file:
os.makedirs(f"{self.ctconfig.pack_path}/file/Track-WU8/", exist_ok=True)
with open(f"{self.ctconfig.pack_path}/file_process.json", encoding="utf8") as fp_file:
file_process = json.load(fp_file)
max_step = len(file_process["img"]) + len(self.ctconfig.all_tracks) + 3 + len("EGFIS")
@ -486,10 +487,13 @@ class Game:
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")
ct_icon.save(f"{self.ctconfig.pack_path}/file/ct_icons.tpl.png")
self.gui.progress(statut=self.gui.translate("Creating descriptive images"), add=1)
self.patch_img_desc()
self.patch_img_desc(
img_desc_path=self.ctconfig.pack_path+"/file/img_desc/",
dest_dir=self.ctconfig.pack_path+"/file/"
)
self.patch_image(file_process["img"])
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.
@ -511,7 +515,12 @@ class Game:
for i, file in enumerate(fp_img):
self.gui.progress(statut=self.gui.translate("Converting images") + f"\n({i + 1}/{len(fp_img)}) {file}",
add=1)
img.encode(file="./file/" + file, format=fp_img[file])
# TODO: IMG DESC AND THIS PART REALLY NEED A REWRITE !
img.encode(
file=f"{self.ctconfig.pack_path}/file/{file}",
format=fp_img[file]
)
def patch_img_desc(self, img_desc_path: str = "./file/img_desc/", dest_dir: str = "./file/") -> None:
"""

View file

@ -7,7 +7,6 @@ import requests
import zipfile
import glob
import json
import os
from source.Game import Game, RomAlreadyPatched, InvalidGamePath, InvalidFormat
from source.Option import Option
@ -25,9 +24,10 @@ class Gui:
Initialize program Gui
"""
self.root = Tk()
self.root.resizable(False, False)
self.root.iconbitmap(bitmap="./icon.ico")
self.option = Option()
self.option.load_from_file("./option.json")
self.option = Option().load_from_file("./option.json")
self.game = Game(gui=self)
self.menu_bar = None
@ -47,42 +47,35 @@ class Gui:
self.boolvar_dont_check_for_update = BooleanVar(value=self.option.dont_check_for_update)
self.intvar_process_track = IntVar(value=self.option.process_track)
self.root.title(self.translate("MKWFaraphel Installer"))
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.stringvar_sort_track_by = StringVar(value="name")
self.boolvar_use_debug_mode = BooleanVar(value=False)
self.boolvar_force_unofficial_mode = BooleanVar(value=False)
self.stringvar_mystuff_folder = StringVar(value=None)
self.stringvar_mystuff_music_folder = StringVar(value=None)
self.stringvar_mystuff_vehicle_folder = StringVar(value=None)
self.stringvar_mystuff_character_folder = StringVar(value=None)
self.stringvar_mystuff_original_track_folder = StringVar(value=None)
self.root.title(self.translate("MKWFaraphel Installer"))
self.root.resizable(False, False)
self.root.iconbitmap(bitmap="./icon.ico")
if not self.boolvar_dont_check_for_update.get(): self.check_update()
self.init_gui()
self.init_menu()
def init_gui(self) -> None:
# GUI
# Mod selector
self.frame_ctconfig = LabelFrame(self.root, text=self.translate("Mod"))
self.frame_ctconfig.grid(row=1, column=1, sticky="NWS")
self.combobox_ctconfig_path = ttk.Combobox(
self.frame_ctconfig,
values=self.available_packs,
textvariable=self.stringvar_ctconfig
textvariable=self.stringvar_ctconfig,
width=30
)
self.combobox_ctconfig_path.grid(row=1, column=1, sticky="NEWS", columnspan=2)
self.combobox_ctconfig_path.bind("<<ComboboxSelected>>", lambda x=None: self.init_menu())
self.combobox_ctconfig_path.bind("<<ComboboxSelected>>", lambda x=None: self.reload_ctconfig())
self.reload_ctconfig()
# Jeu
self.frame_game_path = LabelFrame(self.root, text=self.translate("Original game"))
@ -136,21 +129,22 @@ class Gui:
self.game.patch_file()
self.game.install_mod()
self.button_do_everything = Button(self.frame_game_path_action, text=self.translate("Install mod"),
relief=RIDGE, command=do_everything)
self.button_do_everything = Button(
self.frame_game_path_action,
text=self.translate("Install mod"),
relief=RIDGE,
command=do_everything
)
self.button_do_everything.grid(row=1, column=1, columnspan=2, sticky="NEWS")
self.progressbar = ttk.Progressbar(self.root)
self.progresslabel = Label(self.root)
def init_menu(self) -> None:
if self.menu_bar: self.menu_bar.destroy()
self.menu_bar = Menu(self.root)
self.root.config(menu=self.menu_bar)
self.game.ctconfig.load_ctconfig_file(ctconfig_file=self.get_ctconfig_path_pack(self.stringvar_ctconfig.get()))
track_attr_possibilities = self.game.ctconfig.get_all_track_possibilities()
# LANGUAGE MENU
self.menu_language = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Language"), menu=self.menu_language)
@ -195,50 +189,6 @@ class Gui:
command=lambda: self.option.edit("format", "WBFS")
)
# TRACK CONFIGURATION MENU
self.menu_trackconfiguration = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label=self.translate("Track configuration"), menu=self.menu_trackconfiguration)
# sort track
self.menu_sort_track_by = Menu(self.menu_trackconfiguration, tearoff=0)
self.menu_trackconfiguration.add_cascade(label=self.translate("Sort track"), menu=self.menu_sort_track_by)
for param in track_attr_possibilities:
self.menu_sort_track_by.add_radiobutton(
label=param.title(),
variable=self.stringvar_sort_track_by,
value=param
)
# select track
self.menu_trackselection = Menu(self.menu_trackconfiguration, tearoff=0)
self.menu_trackconfiguration.add_cascade(label=self.translate("Select track"), menu=self.menu_trackselection)
self.menu_trackselection_param = {}
self.menu_trackhighlight = Menu(self.menu_trackconfiguration, tearoff=0)
self.menu_trackconfiguration.add_cascade(label=self.translate("Highlight track"), menu=self.menu_trackhighlight)
self.menu_trackhighlight_param = {}
for param, values in track_attr_possibilities.items():
for menu_param, menu in [
(self.menu_trackselection_param, self.menu_trackselection),
(self.menu_trackhighlight_param, self.menu_trackhighlight)
]:
menu_param[param] = {
"Menu": Menu(menu, tearoff=0),
"Var": []
}
menu.add_cascade(
label=param.title(),
menu=menu_param[param]["Menu"]
)
for value in values:
menu_param[param]["Var"].append(BooleanVar(value=True))
menu_param[param]["Menu"].add_checkbutton(
label=value,
variable=menu_param[param]["Var"][-1],
)
# ADVANCED MENU
## INSTALLER PARAMETER
self.menu_advanced = Menu(self.menu_bar, tearoff=0)
@ -286,7 +236,8 @@ class Gui:
## GAME PARAMETER
self.menu_advanced.add_separator()
self.menu_advanced.add_checkbutton(label=self.translate("Use debug mode"), variable=self.boolvar_use_debug_mode)
self.menu_advanced.add_checkbutton(label=self.translate("Use debug mode"),
variable=self.boolvar_use_debug_mode)
self.menu_mystuff = Menu(self.menu_advanced, tearoff=0)
self.menu_advanced.add_cascade(label=self.translate("MyStuff"), menu=self.menu_mystuff)
@ -303,7 +254,7 @@ class Gui:
self.menu_mystuff.entryconfig(index, label=self.translate(
"Apply", " ", label, f" ({stringvar.get()!r} ", "selected", ")")
)
)
_func(init=True)
self.menu_mystuff.entryconfig(index, command=_func)
@ -318,6 +269,11 @@ class Gui:
self.menu_help.add_command(label="Github Wiki", command=lambda: webbrowser.open(GITHUB_HELP_PAGE_URL))
self.menu_help.add_command(label="Discord", command=lambda: webbrowser.open(DISCORD_URL))
def reload_ctconfig(self) -> None:
self.game.ctconfig.load_ctconfig_file(
ctconfig_file=self.get_ctconfig_path_pack(self.stringvar_ctconfig.get())
)
def get_available_packs(self) -> list:
available_packs = []
@ -335,13 +291,13 @@ class Gui:
Check if an update is available
"""
try:
github_version_data = requests.get(VERSION_FILE_URL, allow_redirects=True).json()
github_version_data = requests.get(VERSION_FILE_URL, allow_redirects=True, timeout=3).json()
with open("./version", "rb") as f: local_version_data = json.load(f)
local_version = StrictVersion(f"{local_version_data['version']}.{local_version_data['subversion']}")
github_version = StrictVersion(f"{github_version_data['version']}.{github_version_data['subversion']}")
if github_version > local_version: # if github version is newer than local version
if github_version > local_version: # if github version is newer than local version
if messagebox.askyesno(
self.translate("Update available !"),
self.translate("An update is available, do you want to install it ?",
@ -360,8 +316,9 @@ class Gui:
print(self.translate("finished extracting"))
os.remove("./download.zip")
print(self.translate("starting application..."))
os.startfile(os.path.realpath("./Updater/Updater.exe"))
print(self.translate("starting application..."))
os.startfile(os.path.realpath("./Updater/Updater.exe"))
elif local_version > github_version:
self.is_dev_version = True
@ -380,13 +337,18 @@ class Gui:
"""
error = traceback.format_exc()
with open("./error.log", "a") as f:
f.write(f"---\n"
f"For game version : {self.game.ctconfig.version}\n"
f"./file/ directory : {os.listdir('./file/')}\n"
f"GAME/files/ information : {self.game.path, self.game.region}\n"
f"{error}\n"
f.write(
f"---\n"
f"For game version : {self.game.ctconfig.version}\n"
f"./file/ directory : {os.listdir('./file/')}\n"
f"ctconfig directory : {os.listdir(self.game.ctconfig.pack_path)}\n"
f"GAME/files/ information : {self.game.path, self.game.region}\n"
f"{error}\n"
)
messagebox.showerror(self.translate("Error"), self.translate("An error occured", " :", "\n", error, "\n\n"))
messagebox.showerror(
self.translate("Error"),
self.translate("An error occured", " :", "\n", error, "\n\n")
)
def progress(self, show: bool = None, indeter: bool = None, step: int = None,
statut: str = None, max: int = None, add: int = None) -> None:

View file

@ -30,7 +30,7 @@ class Option:
self.save_to_file()
if need_restart: restart()
def load_from_file(self, option_file: str = "./option.json") -> None:
def load_from_file(self, option_file: str = "./option.json"):
"""
Load all options from a json file
:param option_file: the file where to load option
@ -40,6 +40,8 @@ class Option:
file_json = json.load(file)
self.load_from_json(file_json)
return self
def load_from_json(self, option_json: dict) -> None:
"""
Load all options from a dictionnary

View file

@ -5,8 +5,8 @@ from source.wszst import *
HiddenTrackAttr = [
"file_wu8",
"file_szs",
"track_wu8_dir",
"track_szs_dir"
"_track_wu8_dir",
"_track_szs_dir"
] # These attribute shouldn't be used to reference all the possibilities of values
@ -48,7 +48,6 @@ class Track:
:param since_version: since when version did the track got added to the mod
:param score: what it the score of the track
:param warning: what is the warn level of the track (0 = none, 1 = minor bug, 2 = major bug)
:param note: note about the track
:param track_wu8_dir: where is stored the track wu8
:param track_szs_dir: where is stored the track szs
:param track_version: version of the track
@ -66,12 +65,11 @@ class Track:
self.since_version = since_version # Since which version is this track available
self.score = score # Track score between 1 and 3 stars
self.warning = warning # Track bug level (1 = minor, 2 = major)
self.note = note # Note about the track
self.version = version
self.tags = tags
self.track_wu8_dir = track_wu8_dir
self.track_szs_dir = track_szs_dir
self._track_wu8_dir = track_wu8_dir
self._track_szs_dir = track_szs_dir
self.file_wu8 = f"{track_wu8_dir}/{self.sha1}.wu8"
self.file_szs = f"{track_szs_dir}/{self.sha1}.szs"
@ -100,7 +98,10 @@ class Track:
"""
convert track to szs
"""
szs.normalize(src_file=self.file_wu8)
szs.normalize(
src_file=self.file_wu8,
dest_dir="./file/Track/"
)
def get_author_str(self) -> str:
"""
@ -128,7 +129,8 @@ class Track:
else:
ctfile_text += (
f'"-"; ' # track path, not used in Race_*.szs, save a bit of space
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}\\n{self.get_author_str()}"; ' # only in race show author's name
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}\\n{self.get_author_str()}"; '
# only in race show author's name
f'"-"\n' # sha1, not used in Race_*.szs, save a bit of space
)
@ -185,8 +187,8 @@ class Track:
for key, value in track_json.items(): # load all value in the json as class attribute
setattr(self, key, value)
self.file_wu8 = f"{self.track_wu8_dir}/{self.sha1}.wu8"
self.file_szs = f"{self.track_szs_dir}/{self.sha1}.szs"
self.file_wu8 = f"{self._track_wu8_dir}/{self.sha1}.wu8"
self.file_szs = f"{self._track_szs_dir}/{self.sha1}.szs"
return self

View file

@ -5,11 +5,13 @@ WIMGT_PATH = "./tools/szs/wimgt"
@error.better_wszst_error(wszst_tools=WIMGT_PATH)
def encode(file: str, format: str) -> None:
def encode(file: str, format: str, dest_file: str = None) -> None:
"""
Encode an .png image into a new format
:param dest_file: destination
:param file: .png image
:param format: new image format
"""
subprocess.run([WIMGT_PATH, "ENCODE", file, "-x", format, "--overwrite"],
creationflags=subprocess.CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE)
cmd = [WIMGT_PATH, "ENCODE", file, "-x", format, "--overwrite"]
if dest_file: cmd.extend(["--dest", dest_file])
subprocess.run(cmd, creationflags=subprocess.CREATE_NO_WINDOW, check=True, stdout=subprocess.PIPE)