From 9fb0fdbc17c11a40e638f2a6a27765114ddeca77 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Wed, 14 Aug 2024 01:26:46 -0400 Subject: [PATCH] Added setting.txt parser, moved some modules under a new "nand" subpackage --- docs/source/libWiiPy.md | 1 + docs/source/libWiiPy.nand.md | 27 +++++ docs/source/libWiiPy.title.md | 16 --- pyproject.toml | 2 +- src/libWiiPy/__init__.py | 3 +- src/libWiiPy/nand/__init__.py | 6 ++ src/libWiiPy/{title => nand}/emunand.py | 6 +- src/libWiiPy/nand/setting.py | 135 ++++++++++++++++++++++++ src/libWiiPy/{title => nand}/sys.py | 4 +- src/libWiiPy/title/__init__.py | 2 - src/libWiiPy/title/tmd.py | 6 +- 11 files changed, 180 insertions(+), 28 deletions(-) create mode 100644 docs/source/libWiiPy.nand.md create mode 100644 src/libWiiPy/nand/__init__.py rename src/libWiiPy/{title => nand}/emunand.py (97%) create mode 100644 src/libWiiPy/nand/setting.py rename src/libWiiPy/{title => nand}/sys.py (97%) diff --git a/docs/source/libWiiPy.md b/docs/source/libWiiPy.md index 873e05c..d794b1d 100644 --- a/docs/source/libWiiPy.md +++ b/docs/source/libWiiPy.md @@ -6,6 +6,7 @@ :maxdepth: 4 libWiiPy.archive +libWiiPy.nand libWiiPy.title ``` diff --git a/docs/source/libWiiPy.nand.md b/docs/source/libWiiPy.nand.md new file mode 100644 index 0000000..8d82fb1 --- /dev/null +++ b/docs/source/libWiiPy.nand.md @@ -0,0 +1,27 @@ +# libWiiPy.nand package + +## Submodules + +### libWiiPy.nand.emunand module +```{eval-rst} +.. automodule:: libWiiPy.nand.emunand + :members: + :undoc-members: + :show-inheritance: +``` + +### libWiiPy.nand.setting module +```{eval-rst} +.. automodule:: libWiiPy.nand.setting + :members: + :undoc-members: + :show-inheritance: +``` + +### libWiiPy.nand.sys module +```{eval-rst} +.. automodule:: libWiiPy.nand.sys + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/docs/source/libWiiPy.title.md b/docs/source/libWiiPy.title.md index c954a6a..0f85d01 100644 --- a/docs/source/libWiiPy.title.md +++ b/docs/source/libWiiPy.title.md @@ -26,14 +26,6 @@ :show-inheritance: ``` -### libWiiPy.title.emunand module -```{eval-rst} -.. automodule:: libWiiPy.title.emunand - :members: - :undoc-members: - :show-inheritance: -``` - ### libWiipy.title.iospatcher module ```{eval-rst} .. automodule:: libWiiPy.title.iospatcher @@ -50,14 +42,6 @@ :show-inheritance: ``` -### libWiiPy.title.sys module -```{eval-rst} -.. automodule:: libWiiPy.title.sys - :members: - :undoc-members: - :show-inheritance: -``` - ### libWiiPy.title.ticket module ```{eval-rst} .. automodule:: libWiiPy.title.ticket diff --git a/pyproject.toml b/pyproject.toml index 9f58671..ae83e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "libWiiPy" -version = "0.5.0" +version = "0.5.1" authors = [ { name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" }, { name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" } diff --git a/src/libWiiPy/__init__.py b/src/libWiiPy/__init__.py index 0d3aa40..8c1cf62 100644 --- a/src/libWiiPy/__init__.py +++ b/src/libWiiPy/__init__.py @@ -3,7 +3,8 @@ # # These are the essential submodules from libWiiPy that you'd probably want imported by default. -__all__ = ["archive", "title"] +__all__ = ["archive", "nand", "title"] from . import archive +from . import nand from . import title diff --git a/src/libWiiPy/nand/__init__.py b/src/libWiiPy/nand/__init__.py new file mode 100644 index 0000000..9596822 --- /dev/null +++ b/src/libWiiPy/nand/__init__.py @@ -0,0 +1,6 @@ +# "nand/__init__.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy + +from .emunand import * +from .setting import * +from .sys import * diff --git a/src/libWiiPy/title/emunand.py b/src/libWiiPy/nand/emunand.py similarity index 97% rename from src/libWiiPy/title/emunand.py rename to src/libWiiPy/nand/emunand.py index 7b89b6f..f0fb12d 100644 --- a/src/libWiiPy/title/emunand.py +++ b/src/libWiiPy/nand/emunand.py @@ -1,4 +1,4 @@ -# "title/emunand.py" from libWiiPy by NinjaCheetah & Contributors +# "nand/emunand.py" from libWiiPy by NinjaCheetah & Contributors # https://github.com/NinjaCheetah/libWiiPy # # Code for handling setting up and modifying a Wii EmuNAND. @@ -6,8 +6,8 @@ import os import pathlib import shutil -from .title import Title -from .content import SharedContentMap as _SharedContentMap +from ..title.title import Title +from ..title.content import SharedContentMap as _SharedContentMap from .sys import UidSys as _UidSys diff --git a/src/libWiiPy/nand/setting.py b/src/libWiiPy/nand/setting.py new file mode 100644 index 0000000..3c04f44 --- /dev/null +++ b/src/libWiiPy/nand/setting.py @@ -0,0 +1,135 @@ +# "nand/setting.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy +# +# See https://wiibrew.org/wiki//title/00000001/00000002/data/setting.txt for information about setting.txt. + +import io +from ..shared import _pad_bytes + + +_key = 0x73B5DBFA + +class SettingTxt: + """ + A SettingTxt object that allows for decrypting and then parsing a setting.txt file from the Wii. + + Attributes + ---------- + area : str + The region of the System Menu this file matches with. + model : str + The model of the console, usually RVL-001 or RVL-101. + dvd : int + Unknown, might have to do with indicating support for scrapped DVD playback capabilities. + mpch : str + Unknown, generally accepted value is "0x7FFE". + code : str + Unknown code, may match with manufacturer code in serial number? + serial_number : str + Serial number of the console. + video : str + Video mode, either NTSC or PAL. + game : str + Another region code, possibly set by the hidden region select channel. + """ + def __init__(self): + self.area: str = "" + self.model: str = "" + self.dvd: int = 0 + self.mpch: str = "" # What does this mean, Movie Player Channel? It's also a hex string, it seems. + self.code: str = "" + self.serial_number: str = "" + self.video: str = "" + self.game: str = "" + + def load(self, setting_txt: bytes) -> None: + """ + Loads the raw data of an encrypted setting.txt file and decrypts it to parse its arguments + + Parameters + ---------- + setting_txt : bytes + The data of an encrypted setting.txt file. + """ + with io.BytesIO(setting_txt) as setting_data: + global _key # I still don't actually know what *kind* of encryption this is. + setting_txt_dec: [int] = [] + for i in range(0, 256): + setting_txt_dec.append(int.from_bytes(setting_data.read(1)) ^ (_key & 0xff)) + _key = (_key << 1) | (_key >> 31) + setting_txt_dec = bytes(setting_txt_dec) + try: + setting_str = setting_txt_dec.decode('utf-8') + except UnicodeDecodeError: + last_newline_pos = setting_txt_dec.rfind(b'\n') # This makes sure we don't try to decode any garbage data. + setting_str = setting_txt_dec[:last_newline_pos + 1].decode('utf-8') + self.load_decrypted(setting_str) + + def load_decrypted(self, setting_txt: str) -> None: + """ + Loads the raw data of a decrypted setting.txt file and parses its arguments + + Parameters + ---------- + setting_txt : str + The data of a decrypted setting.txt file. + """ + setting_dict = {} + print(setting_txt) + # Iterate over every key in the file to create a dictionary. + for line in setting_txt.splitlines(): + line = line.strip() + if line is not None: + key, value = line.split('=', 1) + setting_dict[key.strip()] = value.strip() + # Load the values from the dictionary into the object. + self.area = setting_dict["AREA"] + self.model = setting_dict["MODEL"] + self.dvd = int(setting_dict["DVD"]) + self.mpch = setting_dict["MPCH"] + self.code = setting_dict["CODE"] + self.serial_number = setting_dict["SERNO"] + self.video = setting_dict["VIDEO"] + self.game = setting_dict["GAME"] + + def dump(self) -> bytes: + """ + Dumps the SettingTxt object back into an encrypted bytes that the Wii can load. + + Returns + ------- + bytes + The setting.txt file as encrypted bytes. + """ + setting_str = self.dump_decrypted() + setting_txt_dec = setting_str.encode() + global _key + # This could probably be made more efficient somehow. + setting_txt_enc: [int] = [] + with io.BytesIO(setting_txt_dec) as setting_data: + for i in range(0, len(setting_txt_dec)): + setting_txt_enc.append(int.from_bytes(setting_data.read(1)) ^ (_key & 0xff)) + _key = (_key << 1) | (_key >> 31) + setting_txt_enc = _pad_bytes(bytes(setting_txt_enc), 256) + return setting_txt_enc + + def dump_decrypted(self) -> str: + """ + Dumps the SettingTxt object into a decrypted string. + + Returns + ------- + str + The setting.txt file as decrypted text. + """ + # Write the keys back into a text file that can then be manually edited or re-encrypted. + setting_txt = "" + setting_txt += f"AREA={self.area}\n" + setting_txt += f"MODEL={self.model}\n" + setting_txt += f"DVD={self.dvd}\n" + setting_txt += f"MPCH={self.mpch}\n" + setting_txt += f"CODE={self.code}\n" + setting_txt += f"SERNO={self.serial_number}\n" + setting_txt += f"VIDEO={self.video}\n" + setting_txt += f"GAME={self.game}\n" + return setting_txt diff --git a/src/libWiiPy/title/sys.py b/src/libWiiPy/nand/sys.py similarity index 97% rename from src/libWiiPy/title/sys.py rename to src/libWiiPy/nand/sys.py index dfa5a69..54b2859 100644 --- a/src/libWiiPy/title/sys.py +++ b/src/libWiiPy/nand/sys.py @@ -1,4 +1,4 @@ -# "title/sys.py" from libWiiPy by NinjaCheetah & Contributors +# "nand/sys.py" from libWiiPy by NinjaCheetah & Contributors # https://github.com/NinjaCheetah/libWiiPy # # See https://wiibrew.org/wiki//sys/uid.sys for information about uid.sys. @@ -28,7 +28,7 @@ class _UidSysEntry: class UidSys: """ A UidSys object to parse and edit the uid.sys file stored in /sys/ on the Wii's NAND. This file is used to track all - the titles installed on the console. + the titles that have been launched on a console. Attributes ---------- diff --git a/src/libWiiPy/title/__init__.py b/src/libWiiPy/title/__init__.py index 31afc64..85c6b18 100644 --- a/src/libWiiPy/title/__init__.py +++ b/src/libWiiPy/title/__init__.py @@ -3,10 +3,8 @@ from .content import * from .crypto import * -from .emunand import * from .iospatcher import * from .nus import * -from .sys import * from .ticket import * from .title import * from .tmd import * diff --git a/src/libWiiPy/title/tmd.py b/src/libWiiPy/title/tmd.py index 65b4a47..9a2707d 100644 --- a/src/libWiiPy/title/tmd.py +++ b/src/libWiiPy/title/tmd.py @@ -37,7 +37,7 @@ class TMD: self.blob_header: bytes = b'' self.signature_type: int = 0 self.signature: bytes = b'' - self.issuer: bytes = b'' # Follows the format "Root-CA%08x-CP%08x" + self.signature_issuer: str = "" # Follows the format "Root-CA%08x-CP%08x" self.tmd_version: int = 0 # This seems to always be 0 no matter what? self.ca_crl_version: int = 0 # Certificate Authority Certificate Revocation List version self.signer_crl_version: int = 0 # Certificate Policy Certificate Revocation List version @@ -82,7 +82,7 @@ class TMD: self.signature = tmd_data.read(256) # Signing certificate issuer. tmd_data.seek(0x140) - self.issuer = tmd_data.read(64) + self.signature_issuer = str(tmd_data.read(64).decode()) # TMD version, seems to usually be 0, but I've seen references to other numbers. tmd_data.seek(0x180) self.tmd_version = int.from_bytes(tmd_data.read(1)) @@ -175,7 +175,7 @@ class TMD: # Padding to 64 bytes. tmd_data += b'\x00' * 60 # Signing certificate issuer. - tmd_data += self.issuer + tmd_data += str.encode(self.signature_issuer) # TMD version. tmd_data += int.to_bytes(self.tmd_version, 1) # Certificate Authority CRL version.