mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-06 04:38:29 +02:00
moved ExtractedGame to a new file, changed the patch to fit the new syntax of the Patch system
This commit is contained in:
parent
4db2d9ee3a
commit
b34833397d
22 changed files with 195 additions and 139 deletions
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*[!_]?.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*[!_]?.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"if": "{{ getattr(mod_config, 'extension', False) == 'JAP' }}"
|
"if": "getattr(mod_config, 'extension', False) == 'JAP'"
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"if": "{{ getattr(mod_config, 'extension', False) == 'KOR' }}"
|
"if": "getattr(mod_config, 'extension', False) == 'KOR'"
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"if": "{{ getattr(mod_config, 'extension', False) == 'PAL' }}"
|
"if": "getattr(mod_config, 'extension', False) == 'PAL'"
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"if": "{{ getattr(mod_config, 'extension', False) == 'USA' }}"
|
"if": "getattr(mod_config, 'extension', False) == 'USA'"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*/*.thp"
|
"mode": "match",
|
||||||
|
"match_regex": "*/*.thp"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_*.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_*.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*[!_]?.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*[!_]?.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_E.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_E.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_F.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_F.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_G.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_G.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_I.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_I.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_J.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_J.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_K.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_K.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_M.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_M.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_Q.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_Q.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_S.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_S.szs"
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"regex": "*_U.szs"
|
"mode": "match",
|
||||||
|
"match_regex": "*_U.szs"
|
||||||
}
|
}
|
|
@ -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
110
source/mkw/ExtractedGame.py
Normal 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)
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue