mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-04 03:38:26 +02:00
wit module now can simulate FST if the game is a FST. Moved many function from game to wit, added exists method to szs
This commit is contained in:
parent
9d97326f4b
commit
f9db2e96ea
3 changed files with 264 additions and 74 deletions
|
@ -1,73 +1,29 @@
|
||||||
import enum
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from source.wt.wit import WITPath
|
from source.wt.wit import WITPath, Region
|
||||||
|
|
||||||
|
|
||||||
class Extension(enum.Enum):
|
|
||||||
"""
|
|
||||||
Enum for game extension
|
|
||||||
"""
|
|
||||||
FST = ".dol"
|
|
||||||
WBFS = ".wbfs"
|
|
||||||
ISO = ".iso"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _missing_(cls, value: str) -> "Extension | None":
|
|
||||||
"""
|
|
||||||
if not found, search for the same value with lower case
|
|
||||||
:param value: value to search for
|
|
||||||
:return: None if nothing found, otherwise the found value
|
|
||||||
"""
|
|
||||||
value = value.lower()
|
|
||||||
for member in filter(lambda m: m.value == value, cls): return member
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Region(enum.Enum):
|
|
||||||
"""
|
|
||||||
Enum for game region
|
|
||||||
"""
|
|
||||||
PAL = "PAL"
|
|
||||||
USA = "USA"
|
|
||||||
EUR = "EUR"
|
|
||||||
KOR = "KOR"
|
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
def __init__(self, path: Path | str):
|
def __init__(self, path: Path | str):
|
||||||
self.path = Path(path) if isinstance(path, str) else path
|
self.wit_path = WITPath(path)
|
||||||
|
|
||||||
@property
|
|
||||||
def extension(self) -> Extension:
|
|
||||||
"""
|
|
||||||
Returns the extension of the game
|
|
||||||
:return: the extension of the game
|
|
||||||
"""
|
|
||||||
return Extension(self.path.suffix)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self) -> str:
|
|
||||||
"""
|
|
||||||
Return the id of the game (RMCP01, RMCK01, ...)
|
|
||||||
:return: the id of the game
|
|
||||||
"""
|
|
||||||
return WITPath(self.path).analyze()["id6"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def region(self) -> Region:
|
|
||||||
"""
|
|
||||||
Return the region of the game (PAL, USA, EUR, ...)
|
|
||||||
:return: the region of the game
|
|
||||||
"""
|
|
||||||
return Region(WITPath(self.path).analyze()["dol_region"])
|
|
||||||
|
|
||||||
def is_mkw(self) -> bool:
|
def is_mkw(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if the game is Mario Kart Wii, else 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 WITPath(self.path).analyze()["dol_is_mkw"] == 1
|
return self.wit_path.analyze()["dol_is_mkw"] == 1
|
||||||
|
|
||||||
def is_vanilla(self) -> bool:
|
def is_vanilla(self) -> bool:
|
||||||
...
|
"""
|
||||||
|
Return True if the game is vanilla, False if the game is modded
|
||||||
|
:return: if the game is not modded
|
||||||
|
"""
|
||||||
|
return not any(self.wit_path[f"./files/rel/lecode-{region.value}.bin"].exists() for region in Region)
|
||||||
|
|
||||||
|
def extract(self, dest: Path | str) -> Path:
|
||||||
|
"""
|
||||||
|
Extract the game to the destination directory. If the game is a FST, just copy to the destination
|
||||||
|
:param dest: destination directory
|
||||||
|
"""
|
||||||
|
return self.wit_path.extract_all(dest)
|
||||||
|
|
|
@ -5,14 +5,18 @@ tools_path = tools_szs_dir / ("wszst.exe" if system == "win64" else "wszst")
|
||||||
|
|
||||||
|
|
||||||
class SZSPath:
|
class SZSPath:
|
||||||
__slots__ = ("path",)
|
__slots__ = ("path", "_analyze")
|
||||||
|
|
||||||
def __init__(self, path: Path | str):
|
def __init__(self, path: Path | str):
|
||||||
self.path: Path = path if isinstance(path, Path) else Path(path)
|
self.path: Path = path if isinstance(path, Path) else Path(path)
|
||||||
|
self._analyze = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<SZSPath: {self.path}>"
|
return f"<SZSPath: {self.path}>"
|
||||||
|
|
||||||
|
def __eq__(self, other: "SZSPath") -> bool:
|
||||||
|
return self.path == other.path
|
||||||
|
|
||||||
@better_error(tools_path)
|
@better_error(tools_path)
|
||||||
def _run(self, *args) -> bytes:
|
def _run(self, *args) -> bytes:
|
||||||
"""
|
"""
|
||||||
|
@ -62,10 +66,11 @@ class SZSPath:
|
||||||
|
|
||||||
def analyze(self) -> dict:
|
def analyze(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Return the analyze of the szs
|
Return the analyze of the file
|
||||||
:return: dictionnary of key and value of the analyze
|
:return: dictionnary of key and value of the analyze
|
||||||
"""
|
"""
|
||||||
return self._run_dict("ANALYZE", self.path)
|
if self._analyze is None: self._analyze = self._run_dict("ANALYZE", self.path)
|
||||||
|
return self._analyze
|
||||||
|
|
||||||
def list_raw(self) -> list[str]:
|
def list_raw(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -75,7 +80,11 @@ class SZSPath:
|
||||||
|
|
||||||
# cycle though all of the output line of the command, check if the line are empty, and if not,
|
# cycle though all of the output line of the command, check if the line are empty, and if not,
|
||||||
# add it to the list. Finally, remove the first line because this is a description of the command
|
# add it to the list. Finally, remove the first line because this is a description of the command
|
||||||
return [subfile.strip() for subfile in self._run("list", self.path).decode().splitlines() if subfile][1:]
|
return [
|
||||||
|
subfile.strip()
|
||||||
|
for subfile in self._run("list", self.path).decode().splitlines()
|
||||||
|
if subfile.startswith("./")
|
||||||
|
]
|
||||||
|
|
||||||
def list(self) -> list["SZSSubPath"]:
|
def list(self) -> list["SZSSubPath"]:
|
||||||
"""
|
"""
|
||||||
|
@ -105,9 +114,12 @@ class SZSSubPath:
|
||||||
self.szs_path = szs_path
|
self.szs_path = szs_path
|
||||||
self.subfile = subfile
|
self.subfile = subfile
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"<SZSSubPath: {self.szs_path.path}/{self.subfile}>"
|
return f"<SZSSubPath: {self.szs_path.path}/{self.subfile}>"
|
||||||
|
|
||||||
|
def __eq__(self, other: "SZSSubPath") -> bool:
|
||||||
|
return self.subfile == other.subfile and self.szs_path == other.szs_path
|
||||||
|
|
||||||
def extract(self, dest: Path | str) -> Path:
|
def extract(self, dest: Path | str) -> Path:
|
||||||
"""
|
"""
|
||||||
Extract the subfile to a destination
|
Extract the subfile to a destination
|
||||||
|
@ -124,11 +136,30 @@ class SZSSubPath:
|
||||||
|
|
||||||
return dest
|
return dest
|
||||||
|
|
||||||
def is_dir(self):
|
def exists(self):
|
||||||
|
"""
|
||||||
|
Return if the subfile exist in the szs
|
||||||
|
:return: True if the subfile exist, else otherwise
|
||||||
|
"""
|
||||||
|
return self in self.szs_path.list()
|
||||||
|
|
||||||
|
def is_dir(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return if the subfile is a directory
|
||||||
|
:return: True if the subfile is a directory, else otherwise
|
||||||
|
"""
|
||||||
return self.subfile.endswith("/")
|
return self.subfile.endswith("/")
|
||||||
|
|
||||||
def is_file(self):
|
def is_file(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return if the subfile is a file
|
||||||
|
:return: True if the subfile is a file, else otherwise
|
||||||
|
"""
|
||||||
return not self.is_dir()
|
return not self.is_dir()
|
||||||
|
|
||||||
def basename(self):
|
def basename(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the basename of the subfile
|
||||||
|
:return: the basename of the subfile
|
||||||
|
"""
|
||||||
return self.subfile.rsplit("/", 1)[-1]
|
return self.subfile.rsplit("/", 1)[-1]
|
||||||
|
|
213
source/wt/wit.py
213
source/wt/wit.py
|
@ -1,14 +1,54 @@
|
||||||
|
import enum
|
||||||
|
import shutil
|
||||||
|
|
||||||
from source.wt import *
|
from source.wt import *
|
||||||
from source.wt import _run, _run_dict
|
from source.wt import _run, _run_dict
|
||||||
|
|
||||||
tools_path = tools_wit_dir / ("wit.exe" if system == "win64" else "wit")
|
tools_path = tools_wit_dir / ("wit.exe" if system == "win64" else "wit")
|
||||||
|
|
||||||
|
|
||||||
class WITPath:
|
class Extension(enum.Enum):
|
||||||
__slots__ = ("path",)
|
"""
|
||||||
|
Enum for game extension
|
||||||
|
"""
|
||||||
|
FST = ".dol"
|
||||||
|
WBFS = ".wbfs"
|
||||||
|
ISO = ".iso"
|
||||||
|
|
||||||
def __init__(self, path: Path):
|
@classmethod
|
||||||
self.path = path
|
def _missing_(cls, value: str) -> "Extension | None":
|
||||||
|
"""
|
||||||
|
if not found, search for the same value with lower case
|
||||||
|
:param value: value to search for
|
||||||
|
:return: None if nothing found, otherwise the found value
|
||||||
|
"""
|
||||||
|
value = value.lower()
|
||||||
|
for member in filter(lambda m: m.value == value, cls): return member
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Region(enum.Enum):
|
||||||
|
"""
|
||||||
|
Enum for game region
|
||||||
|
"""
|
||||||
|
PAL = "PAL"
|
||||||
|
USA = "USA"
|
||||||
|
EUR = "EUR"
|
||||||
|
KOR = "KOR"
|
||||||
|
|
||||||
|
|
||||||
|
class WITPath:
|
||||||
|
__slots__ = ("path", "_analyze")
|
||||||
|
|
||||||
|
def __init__(self, path: Path | str):
|
||||||
|
self.path: Path = path if isinstance(path, Path) else Path(path)
|
||||||
|
self._analyze = None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<WITPath: {self.path}>"
|
||||||
|
|
||||||
|
def __eq__(self, other: "WITPath") -> bool:
|
||||||
|
return self.path == other.path
|
||||||
|
|
||||||
@better_error(tools_path)
|
@better_error(tools_path)
|
||||||
def _run(self, *args) -> bytes:
|
def _run(self, *args) -> bytes:
|
||||||
|
@ -28,9 +68,172 @@ class WITPath:
|
||||||
"""
|
"""
|
||||||
return _run_dict(tools_path, *args)
|
return _run_dict(tools_path, *args)
|
||||||
|
|
||||||
|
def _get_fst_root(self) -> Path:
|
||||||
|
"""
|
||||||
|
If the game is a FST, return the root of the FST
|
||||||
|
:return: root of the FST
|
||||||
|
"""
|
||||||
|
# main.dol is located in ./sys/main.dol, so return parent of parent
|
||||||
|
if self.extension == Extension.FST: return self.path.parent.parent
|
||||||
|
|
||||||
def analyze(self) -> dict:
|
def analyze(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Return the analyze of the file
|
Return the analyze of the file
|
||||||
:return: dictionnary of key and value of the analyze
|
:return: dictionnary of key and value of the analyze
|
||||||
"""
|
"""
|
||||||
return self._run_dict("ANALYZE", self.path)
|
if self._analyze is None: self._analyze = self._run_dict("ANALYZE", self.path)
|
||||||
|
return self._analyze
|
||||||
|
|
||||||
|
def list_raw(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
Return the list of subfiles
|
||||||
|
:return: the list of subfiles
|
||||||
|
"""
|
||||||
|
if self.extension == Extension.FST:
|
||||||
|
return [
|
||||||
|
str(file.relative_to(self._get_fst_root()))
|
||||||
|
for file in self._get_fst_root().rglob("*")
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
subfile.strip() for subfile
|
||||||
|
in self._run("files", self.path).decode().splitlines()
|
||||||
|
if subfile.startswith("./")
|
||||||
|
]
|
||||||
|
|
||||||
|
def list(self) -> list["WITSubPath"]:
|
||||||
|
"""
|
||||||
|
Return the list of subfiles
|
||||||
|
:return: the list of subfiles
|
||||||
|
"""
|
||||||
|
return [self.get_subfile(subfile) for subfile in self.list_raw()]
|
||||||
|
|
||||||
|
def get_subfile(self, subfile: str) -> "WITSubPath":
|
||||||
|
"""
|
||||||
|
Return the subfile of the game
|
||||||
|
:return: the subfile
|
||||||
|
"""
|
||||||
|
return WITSubPath(self, subfile)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self.get_subfile(item)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.list())
|
||||||
|
|
||||||
|
def extract_all(self, dest: Path | str) -> Path:
|
||||||
|
"""
|
||||||
|
Extract all the subfiles to the destination directory
|
||||||
|
:param dest: destination directory
|
||||||
|
:return: the extracted file path
|
||||||
|
"""
|
||||||
|
return self["./"].extract(dest, flat=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extension(self) -> Extension:
|
||||||
|
"""
|
||||||
|
Returns the extension of the game
|
||||||
|
:return: the extension of the game
|
||||||
|
"""
|
||||||
|
return Extension(self.path.suffix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the id of the game (RMCP01, RMCK01, ...)
|
||||||
|
:return: the id of the game
|
||||||
|
"""
|
||||||
|
return self.analyze()["id6"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def region(self) -> Region:
|
||||||
|
"""
|
||||||
|
Return the region of the game (PAL, USA, EUR, ...)
|
||||||
|
:return: the region of the game
|
||||||
|
"""
|
||||||
|
return Region(self.analyze()["dol_region"])
|
||||||
|
|
||||||
|
|
||||||
|
class WITSubPath:
|
||||||
|
__slots__ = ("wit_path", "subfile")
|
||||||
|
|
||||||
|
def __init__(self, wit_path: WITPath, subfile: str):
|
||||||
|
self.wit_path = wit_path
|
||||||
|
self.subfile = subfile.removeprefix("./").replace("\\", "/")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.wit_path.extension == Extension.FST: return f"<WITSubPath: {self._get_fst_path()}>"
|
||||||
|
return f"<WITSubPath: {self.wit_path.path}/{self.subfile}>"
|
||||||
|
|
||||||
|
def __eq__(self, other: "WITSubPath") -> bool:
|
||||||
|
return self.subfile == other.subfile and self.wit_path == other.wit_path
|
||||||
|
|
||||||
|
def _get_fst_path(self) -> Path:
|
||||||
|
"""
|
||||||
|
Return the path of the subfile in the FST
|
||||||
|
:return: the path of the subfile in the FST
|
||||||
|
"""
|
||||||
|
return self.wit_path._get_fst_root() / self.subfile
|
||||||
|
|
||||||
|
def extract(self, dest: Path | str, flat: bool = True) -> Path:
|
||||||
|
"""
|
||||||
|
Extract the subfile to the destination directory
|
||||||
|
:param flat: all files will be extracted directly in the directory, instead of creating subdirectory
|
||||||
|
:param dest: destination directory
|
||||||
|
:return: the extracted file path
|
||||||
|
"""
|
||||||
|
dest: Path = dest if isinstance(dest, Path) else Path(dest)
|
||||||
|
|
||||||
|
if self.wit_path.extension == Extension.FST:
|
||||||
|
# if flat is used, extract the file / dir into the destination directory, without subdirectory
|
||||||
|
if flat:
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
# if we are extracting a directory, we need to extract every file recursively
|
||||||
|
if self.is_dir():
|
||||||
|
for file in (self._get_fst_path()).rglob("*"):
|
||||||
|
if file.is_file(): shutil.copy(file, dest / file.name)
|
||||||
|
# else we just copy the file
|
||||||
|
else:
|
||||||
|
shutil.copy(self._get_fst_path(), dest)
|
||||||
|
# if flat is not used, copy the structure of the directory, or just copy the file
|
||||||
|
else:
|
||||||
|
func = shutil.copytree if self.is_dir() else shutil.copy
|
||||||
|
func(self._get_fst_path(), dest / self.subfile)
|
||||||
|
|
||||||
|
return dest / self.basename()
|
||||||
|
|
||||||
|
else:
|
||||||
|
args = []
|
||||||
|
if flat: args.append("--flat")
|
||||||
|
self.wit_path._run("EXTRACT", self.wit_path.path, f"--files=+{self.subfile}", "-d", dest, *args)
|
||||||
|
return dest / self.basename()
|
||||||
|
|
||||||
|
def is_dir(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return if the subfile is a directory
|
||||||
|
:return: True if the subfile is a directory, else otherwise
|
||||||
|
"""
|
||||||
|
if self.wit_path.extension == Extension.FST:
|
||||||
|
return self._get_fst_path().is_dir()
|
||||||
|
return self.subfile.endswith("/")
|
||||||
|
|
||||||
|
def is_file(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return if the subfile is a file
|
||||||
|
:return: True if the subfile is a file, else otherwise
|
||||||
|
"""
|
||||||
|
return not self.is_dir()
|
||||||
|
|
||||||
|
def exists(self):
|
||||||
|
"""
|
||||||
|
Return if the subfile exist in the game
|
||||||
|
:return: True if the subfile exist, else otherwise
|
||||||
|
"""
|
||||||
|
return self in self.wit_path.list()
|
||||||
|
|
||||||
|
def basename(self) -> str:
|
||||||
|
"""
|
||||||
|
Return the basename of the subfile
|
||||||
|
:return: the basename of the subfile
|
||||||
|
"""
|
||||||
|
return self.subfile.rsplit("/", 1)[-1]
|
||||||
|
|
Loading…
Reference in a new issue