moved ExtractedGame to a new file, changed the patch to fit the new syntax of the Patch system

This commit is contained in:
Faraphel 2022-06-25 18:48:30 +02:00
parent 4db2d9ee3a
commit b34833397d
22 changed files with 195 additions and 139 deletions

View file

@ -1,3 +1,4 @@
{ {
"regex": "*[!_]?.szs" "mode": "match",
"match_regex": "*[!_]?.szs"
} }

View file

@ -1,3 +1,3 @@
{ {
"if": "{{ getattr(mod_config, 'extension', False) == 'JAP' }}" "if": "getattr(mod_config, 'extension', False) == 'JAP'"
} }

View file

@ -1,3 +1,3 @@
{ {
"if": "{{ getattr(mod_config, 'extension', False) == 'KOR' }}" "if": "getattr(mod_config, 'extension', False) == 'KOR'"
} }

View file

@ -1,3 +1,3 @@
{ {
"if": "{{ getattr(mod_config, 'extension', False) == 'PAL' }}" "if": "getattr(mod_config, 'extension', False) == 'PAL'"
} }

View file

@ -1,3 +1,3 @@
{ {
"if": "{{ getattr(mod_config, 'extension', False) == 'USA' }}" "if": "getattr(mod_config, 'extension', False) == 'USA'"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*/*.thp" "mode": "match",
"match_regex": "*/*.thp"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_*.szs" "mode": "match",
"match_regex": "*_*.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*[!_]?.szs" "mode": "match",
"match_regex": "*[!_]?.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_E.szs" "mode": "match",
"match_regex": "*_E.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_F.szs" "mode": "match",
"match_regex": "*_F.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_G.szs" "mode": "match",
"match_regex": "*_G.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_I.szs" "mode": "match",
"match_regex": "*_I.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_J.szs" "mode": "match",
"match_regex": "*_J.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_K.szs" "mode": "match",
"match_regex": "*_K.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_M.szs" "mode": "match",
"match_regex": "*_M.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_Q.szs" "mode": "match",
"match_regex": "*_Q.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_S.szs" "mode": "match",
"match_regex": "*_S.szs"
} }

View file

@ -1,3 +1,4 @@
{ {
"regex": "*_U.szs" "mode": "match",
"match_regex": "*_U.szs"
} }

View file

@ -251,6 +251,7 @@ class SourceGame(ttk.LabelFrame):
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, str(path.absolute())) self.entry.insert(0, str(path.absolute()))
if path.suffix == ".dol": path = path.parent.parent
self.master.destination_game.set_path(path.parent) self.master.destination_game.set_path(path.parent)
def get_path(self) -> Path: def get_path(self) -> Path:

110
source/mkw/ExtractedGame.py Normal file
View file

@ -0,0 +1,110 @@
from pathlib import Path
from typing import Generator
from source.mkw.ModConfig import ModConfig
from source.wt import szs
import json
class ExtractedGame:
"""
Class that represents an extracted game
"""
def __init__(self, path: Path | str):
self.path = Path(path)
def extract_autoadd(self, destination_path: Path | str) -> Generator[dict, None, None]:
"""
Extract all the autoadd files from the game to destination_path
:param destination_path: directory where the autoadd files will be extracted
:return: directory where the autoadd files were extracted
"""
yield {"description": "Extracting autoadd files...", "determinate": False}
szs.autoadd(self.path / "files/Race/Course/", destination_path)
def install_mystuff(self) -> Generator[dict, None, None]:
"""
Install mystuff directory
:return:
"""
yield {"description": "Installing MyStuff directory...", "determinate": False}
...
def install_file(self, mod_config: ModConfig, patch_directory: Path | str, subfile: Path | str) \
-> Generator[dict, None, None]:
"""
Install a file into the game
:param patch_directory: patch_directory where the subfile is located
:param subfile: subfile to install
:param mod_config: the mod to install
"""
subfile = Path(subfile)
yield {"description": f"Patch {patch_directory.name}\nInstalling {subfile.name}...", "determinate": False}
'''
configuration = {
"base": "/files/test.json", # path a another json file to use as a base
"mode": "copy", # copy, replace, ignore, edit the subfile [default: copy]
# if edit is set, use the file in the extracted game as a source
# replace can't be used inside szs
"replace_regex": None, # regex expression to match the file on the game to replace
"if": "True", # safe eval expression to check if the file should be installed
"operation": { # other operation for the file
"img-generate": {
# width, height, default color, ... can be determined from the base file
"format": "RGB", # type of the image
"layers": [
{"type": "image", ...},
{"type": "text", ...}
]
}
"tpl-encode": {"encoding": "TPL.RGB565"}, # encode an image to a tpl with the given format
"bmg-replace": {
"mode": "regex" # regex or id
"template": {
"CWF": "{{ ONLINE_SERVICE }}", # regex type expression
"0x203F": "{{ ONLINE_SERVICE }}" # id type expression
}
}
}
}
'''
configuration = { # default configuration
"mode": "copy",
"if": "True",
}
configuration_path = subfile.with_suffix(subfile.suffix + ".json")
if configuration_path.exists(): configuration |= json.loads(configuration_path.read_text(encoding="utf8"))
def install_patch(self, mod_config: ModConfig, patch_directory: Path | str) -> Generator[dict, None, None]:
"""
Install a patch into the game
:param mod_config: the mod to install
:param patch_directory: directory containing the patch
"""
patch_directory = Path(patch_directory)
yield {"description": f"Installing Patch {patch_directory.parent.name}...", "determinate": False}
for subfile in filter(lambda sf: sf.suffix == ".json", patch_directory.glob("*")):
self.install_file(mod_config, patch_directory, subfile)
def install_all_patch(self, mod_config: ModConfig) -> Generator[dict, None, None]:
"""
Install all patchs of the mod_config into the game
:param mod_config: the mod to install
:return:
"""
yield {"description": "Installing all Patch...", "determinate": False}
# for all directory that are in the root of the mod, and don't start with an underscore,
# for all the subdirectory named "_PATCH", apply the patch
for part_directory in mod_config.get_mod_directory().glob("[!_]*"):
for patch_directory in part_directory.glob("_PATCH/"):
self.install_patch(mod_config, patch_directory)

View file

@ -1,112 +1,21 @@
import json
from pathlib import Path from pathlib import Path
from typing import Generator from typing import Generator
from source.mkw.ExtractedGame import ExtractedGame
from source.mkw.ModConfig import ModConfig from source.mkw.ModConfig import ModConfig
from source.wt import szs
from source.wt.wit import WITPath, Region, Extension from source.wt.wit import WITPath, Region, Extension
class ExtractedGame: class NotMKWGameError(Exception):
"""
Class that represents an extracted game
"""
def __init__(self, path: Path | str): def __init__(self, path: Path | str):
self.path = Path(path) path = Path(path)
super().__init__(f'Not a Mario Kart Wii game : "{path.name}"')
def extract_autoadd(self, destination_path: Path | str) -> Generator[dict, None, None]:
"""
Extract all the autoadd files from the game to destination_path
:param destination_path: directory where the autoadd files will be extracted
:return: directory where the autoadd files were extracted
"""
yield {"description": "Extracting autoadd files...", "determinate": False}
szs.autoadd(self.path / "files/Race/Course/", destination_path)
def install_mystuff(self) -> Generator[dict, None, None]: class NotVanillaError(Exception):
""" def __init__(self, path: Path | str):
Install mystuff directory path = Path(path)
:return: super().__init__(f'This game is already modded : "{path.name}"')
"""
yield {"description": "Installing MyStuff directory...", "determinate": False}
...
def install_file(self, mod_config: ModConfig, patch_directory: Path | str, subfile: Path | str) \
-> Generator[dict, None, None]:
"""
Install a file into the game
:param patch_directory: patch_directory where the subfile is located
:param subfile: subfile to install
:param mod_config: the mod to install
"""
subfile = Path(subfile)
yield {"description": f"Patch {patch_directory.name}\nInstalling {subfile.name}...", "determinate": False}
'''
configuration = {
"base": "/files/test.json", # path a another json file to use as a base
"mode": "copy", # copy, replace, ignore, edit the subfile
# if edit is set, use the file in the extracted game as a source
"replace_regex": None, # regex expression to match the file on the game to replace
"if": "True", # safe eval expression to check if the file should be installed
"operation": { # other operation for the file
"img-generate": {
# width, height, default color, ... can be determined from the base file
"format": "RGB", # type of the image
"layers": [
{"type": "image", ...},
{"type": "text", ...}
]
}
"tpl-encode": {"encoding": "TPL.RGB565"}, # encode an image to a tpl with the given format
"bmg-replace": {
"mode": "regex" # regex, bmg id
"if": "True" # safe eval expression to check if the bmg operation should be installed
"template": {
"CWF": "{{ ONLINE_SERVICE }}", # regex type expression
"0x203F": "{{ ONLINE_SERVICE }}" # bmg id type expression
}
}
}
}
'''
configuration = { # default configuration
"mode": "copy",
"if": "True",
}
configuration_path = subfile.with_suffix(subfile.suffix + ".json")
if configuration_path.exists(): configuration |= json.loads(configuration_path.read_text(encoding="utf8"))
def install_patch(self, mod_config: ModConfig, patch_directory: Path | str) -> Generator[dict, None, None]:
"""
Install a patch into the game
:param mod_config: the mod to install
:param patch_directory: directory containing the patch
"""
patch_directory = Path(patch_directory)
yield {"description": f"Installing Patch {patch_directory.parent.name}...", "determinate": False}
for subfile in filter(lambda sf: sf.suffix == ".json", patch_directory.glob("*")):
self.install_file(mod_config, patch_directory, subfile)
def install_all_patch(self, mod_config: ModConfig) -> Generator[dict, None, None]:
"""
Install all patchs of the mod_config into the game
:param mod_config: the mod to install
:return:
"""
yield {"description": "Installing all Patch...", "determinate": False}
# for all directory that are in the root of the mod, and don't start with an underscore,
# for all of the subdirectory named "_PATCH", apply the patch
for patch_directory in mod_config.get_mod_directory().glob("[!_]*").rglob("_PATCH/"):
self.install_patch(mod_config, patch_directory)
class Game: class Game:
@ -118,7 +27,7 @@ class Game:
Return True if the game is Mario Kart Wii, False otherwise Return True if the game is Mario Kart Wii, False otherwise
:return: is the game a MKW game :return: is the game a MKW game
""" """
return self.wit_path.analyze()["dol_is_mkw"] == 1 return self.wit_path.analyze().get("dol_is_mkw") == 1
def is_vanilla(self) -> bool: def is_vanilla(self) -> bool:
""" """
@ -133,17 +42,29 @@ class Game:
:param dest: destination directory :param dest: destination directory
""" """
gen = self.wit_path.progress_extract_all(dest) gen = self.wit_path.progress_extract_all(dest)
for gen_data in gen:
yield { if self.wit_path.extension == Extension.FST:
"description": f'EXTRACTING - {gen_data["percentage"]}% - (estimated time remaining: ' for gen_data in gen:
f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})', yield {
"maximum": 100, "description": "Copying Game...",
"value": gen_data["percentage"], "determinate": False
"determinate": True }
} try: next(gen)
try: next(gen) except StopIteration as e:
except StopIteration as e: return e.value
return e.value
else:
for gen_data in gen:
yield {
"description": f'Extracting - {gen_data["percentage"]}% - (estimated time remaining: '
f'{gen_data["estimation"] if gen_data["estimation"] is not None else "-:--"})',
"maximum": 100,
"value": gen_data["percentage"],
"determinate": True
}
try: next(gen)
except StopIteration as e:
return e.value
@staticmethod @staticmethod
def get_output_directory(dest: Path | str, mod_config: ModConfig) -> Path: def get_output_directory(dest: Path | str, mod_config: ModConfig) -> Path:
@ -180,6 +101,9 @@ class Game:
# get the directory where the game will be extracted # get the directory where the game will be extracted
extracted_game = ExtractedGame(self.get_output_directory(dest, mod_config)) extracted_game = ExtractedGame(self.get_output_directory(dest, mod_config))
if not self.is_mkw(): raise NotMKWGameError(self.wit_path.path)
if not self.is_vanilla(): raise NotVanillaError(self.wit_path.path)
yield from self.extract(extracted_game.path) yield from self.extract(extracted_game.path)
yield from extracted_game.extract_autoadd(cache_directory / "autoadd/") yield from extracted_game.extract_autoadd(cache_directory / "autoadd/")
yield from extracted_game.install_mystuff() yield from extracted_game.install_mystuff()

View file

@ -146,18 +146,25 @@ class WITPath:
:param dest: destination directory :param dest: destination directory
:return: the extracted file path :return: the extracted file path
""" """
process = self._run_popen("EXTRACT", self.path, "-d", dest, "--progress") if self.extension == Extension.FST:
yield {
"determinate": False
}
shutil.copytree(self._get_fst_root(), dest)
while process.poll() is None: else:
m = re.match(r'\s*(?P<percentage>\d*)%(?:.*?ETA (?P<estimation>\d*:\d*))?\s*', process.stdout.readline()) process = self._run_popen("EXTRACT", self.path, "-d", dest, "--progress")
if m:
yield {
"percentage": int(m.group("percentage")),
"estimation": m.group("estimation")
}
if process.returncode != 0: while process.poll() is None:
raise WTError(tools_path, process.returncode) m = re.match(r'\s*(?P<percentage>\d*)%(?:.*?ETA (?P<estimation>\d*:\d*))?\s*', process.stdout.readline())
if m:
yield {
"percentage": int(m.group("percentage")),
"estimation": m.group("estimation")
}
if process.returncode != 0:
raise WTError(tools_path, process.returncode)
return dest return dest