diff --git a/.github/workflows/python-build.yaml b/.github/workflows/python-build.yaml index b000923..1d0b845 100644 --- a/.github/workflows/python-build.yaml +++ b/.github/workflows/python-build.yaml @@ -21,10 +21,10 @@ jobs: - uses: actions/checkout@v4 - name: Install ccache for Nuitka run: sudo apt update && sudo apt install -y ccache libicu70 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install Dependencies run: | python -m pip install --upgrade pip @@ -49,10 +49,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install Dependencies run: | python -m pip install --upgrade pip @@ -79,10 +79,10 @@ jobs: - uses: actions/checkout@v4 - name: Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@v1.13.0 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install Dependencies run: | python -m pip install --upgrade pip diff --git a/commands/archive/theme.py b/commands/archive/theme.py index 612dd6b..bbe29fe 100644 --- a/commands/archive/theme.py +++ b/commands/archive/theme.py @@ -1,4 +1,58 @@ # "commands/archive/theme.py" from WiiPy by NinjaCheetah # https://github.com/NinjaCheetah/WiiPy +import configparser +import pathlib +import shutil +import tempfile +import zipfile +import libWiiPy +import time + +def handle_apply_mym(args): + mym_path = pathlib.Path(args.mym) + base_path = pathlib.Path(args.base) + output_path = pathlib.Path(args.output) + + if not mym_path.exists(): + raise FileNotFoundError(mym_path) + if not base_path.exists(): + raise FileNotFoundError(base_path) + if output_path.suffix != ".csm": + output_path = output_path.with_suffix(".csm") + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_path = pathlib.Path(tmp_dir) + # Extract the MYM file into the temp directory. + # MYM files are just ZIP files, so if zipfile doesn't accept it then it can't be valid. + try: + with zipfile.ZipFile(mym_path) as mym: + mym.extractall(tmp_path.joinpath("mym_out")) + except zipfile.BadZipfile: + print("Error: The provided MYM theme is not valid!") + exit(1) + mym_tmp_path = pathlib.Path(tmp_path.joinpath("mym_out")) + # Extract the asset archive into the temp directory. + try: + libWiiPy.archive.extract_u8(base_path.read_bytes(), str(tmp_path.joinpath("base_out"))) + except ValueError: + print("Error: The provided base assets are not valid!") + exit(1) + base_temp_path = pathlib.Path(tmp_path.joinpath("base_out")) + # Parse the mym.ini file in the root of the extracted MYM file. + mym_ini = configparser.ConfigParser() + mym_ini.read(mym_tmp_path.joinpath("mym.ini")) + # Iterate over every key in the ini file and apply the theme based the source and target of each key. + for section in mym_ini.sections(): + source_file = mym_tmp_path + for piece in mym_ini[section]["source"].replace("\\", "/").split("/"): + source_file = source_file.joinpath(piece) + target_file = base_temp_path + for piece in mym_ini[section]["file"].replace("\\", "/").split("/"): + target_file = target_file.joinpath(piece) + shutil.move(source_file, target_file) + # Repack the now-themed asset archive and write it out. + output_path.write_bytes(libWiiPy.archive.pack_u8(base_temp_path)) + + print(f"Applied theme \"{mym_path.name}\" to \"{output_path.name}\"!") diff --git a/wiipy.py b/wiipy.py index 99520da..5aacf8f 100644 --- a/wiipy.py +++ b/wiipy.py @@ -5,6 +5,7 @@ import argparse from importlib.metadata import version from commands.archive.ash import * +from commands.archive.theme import * from commands.archive.u8 import * from commands.nand.emunand import * from commands.nand.setting import * @@ -196,6 +197,20 @@ if __name__ == "__main__": setting_gen_parser.add_argument("region", metavar="REGION", type=str, help="region of the console these settings are for (USA, EUR, JPN, or KOR)") + # Argument parser for the theme subcommand. + theme_parser = subparsers.add_parser("theme", help="apply custom themes to the Wii Menu", + description="apply custom themes to the Wii Menu") + theme_subparsers = theme_parser.add_subparsers(dest="subcommand", required=True) + # MYM theme subcommand. + theme_mym_parser = theme_subparsers.add_parser("mym", help="apply an MYM theme to the Wii Menu", + description="apply an MYM theme to the Wii Menu") + theme_mym_parser.set_defaults(func=handle_apply_mym) + theme_mym_parser.add_argument("mym", metavar="MYM", type=str, help="MYM theme to apply") + theme_mym_parser.add_argument("base", metavar="BASE", type=str, + help="base Wii Menu assets to apply the theme to (000000xx.app)") + theme_mym_parser.add_argument("output", metavar="OUT", type=str, + help="path to output the finished theme to (.csm)") + # Argument parser for the TMD subcommand. tmd_parser = subparsers.add_parser("tmd", help="edit a TMD file", description="edit a TMD file")