mirror of
https://github.com/NinjaCheetah/WiiPy.git
synced 2025-06-29 07:31:02 -04:00
Added tmd edit command to change properties of a TMD file, same options as wad edit
Stubbed theme command temporarily as it will not be part of WiiPy v1.4.0. Workflow also adjusted to build for x86_64 and arm64 on macOS
This commit is contained in:
parent
554bbfb7cb
commit
76c78e6d85
37
.github/workflows/python-build.yaml
vendored
37
.github/workflows/python-build.yaml
vendored
@ -39,9 +39,9 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4.3.0
|
uses: actions/upload-artifact@v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/WiiPy.tar
|
path: ~/WiiPy.tar
|
||||||
name: WiiPy-linux-bin
|
name: WiiPy-Linux-bin
|
||||||
|
|
||||||
build-macos:
|
build-macos-x86_64:
|
||||||
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ jobs:
|
|||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
- name: Build Application
|
- name: Build Application
|
||||||
run: make all
|
run: ARCH_FLAGS=--macos-target-arch=x86_64 make all
|
||||||
- name: Prepare Package for Upload
|
- name: Prepare Package for Upload
|
||||||
run: |
|
run: |
|
||||||
mv wiipy ~/wiipy
|
mv wiipy ~/wiipy
|
||||||
@ -66,7 +66,34 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4.3.0
|
uses: actions/upload-artifact@v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/WiiPy.tar
|
path: ~/WiiPy.tar
|
||||||
name: WiiPy-macos-bin
|
name: WiiPy-macOS-x86_64-bin
|
||||||
|
|
||||||
|
build-macos-arm64:
|
||||||
|
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python 3.12
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
- name: Build Application
|
||||||
|
run: ARCH_FLAGS=--macos-target-arch=arm64 make all
|
||||||
|
- name: Prepare Package for Upload
|
||||||
|
run: |
|
||||||
|
mv wiipy ~/wiipy
|
||||||
|
cd ~
|
||||||
|
tar cvf WiiPy.tar wiipy
|
||||||
|
- name: Upload Application
|
||||||
|
uses: actions/upload-artifact@v4.3.0
|
||||||
|
with:
|
||||||
|
path: ~/WiiPy.tar
|
||||||
|
name: WiiPy-macOS-arm64-bin
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
|
|
||||||
@ -90,4 +117,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4.3.0
|
uses: actions/upload-artifact@v4.3.0
|
||||||
with:
|
with:
|
||||||
path: D:\a\WiiPy\WiiPy\wiipy.exe
|
path: D:\a\WiiPy\WiiPy\wiipy.exe
|
||||||
name: WiiPy-windows-bin
|
name: WiiPy-Windows-bin
|
||||||
|
3
Makefile
3
Makefile
@ -1,7 +1,8 @@
|
|||||||
CC=python -m nuitka
|
CC=python -m nuitka
|
||||||
|
ARCH_FLAGS?=
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(CC) --show-progress --assume-yes-for-downloads --onefile wiipy.py --onefile-tempdir-spec="{CACHE_DIR}/NinjaCheetah/WiiPy" -o wiipy
|
$(CC) --show-progress --assume-yes-for-downloads --onefile wiipy.py --onefile-tempdir-spec="{CACHE_DIR}/NinjaCheetah/WiiPy" $(ARCH_FLAGS) -o wiipy
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install wiipy /usr/bin/
|
install wiipy /usr/bin/
|
||||||
|
@ -4,6 +4,40 @@
|
|||||||
import pathlib
|
import pathlib
|
||||||
import libWiiPy
|
import libWiiPy
|
||||||
from modules.core import fatal_error
|
from modules.core import fatal_error
|
||||||
|
from modules.title import tmd_edit_ios, tmd_edit_tid, tmd_edit_type
|
||||||
|
|
||||||
|
|
||||||
|
def handle_tmd_edit(args):
|
||||||
|
input_path = pathlib.Path(args.input)
|
||||||
|
if args.output is not None:
|
||||||
|
output_path = pathlib.Path(args.output)
|
||||||
|
else:
|
||||||
|
output_path = pathlib.Path(args.input)
|
||||||
|
|
||||||
|
tmd = libWiiPy.title.TMD()
|
||||||
|
tmd.load(input_path.read_bytes())
|
||||||
|
|
||||||
|
# State variable to make sure that changes are made.
|
||||||
|
edits_made = False
|
||||||
|
# Go over every possible change, and apply them if they were specified.
|
||||||
|
if args.tid is not None:
|
||||||
|
tmd = tmd_edit_tid(tmd, args.tid)
|
||||||
|
edits_made = True
|
||||||
|
if args.ios is not None:
|
||||||
|
tmd = tmd_edit_ios(tmd, args.ios)
|
||||||
|
edits_made = True
|
||||||
|
if args.type is not None:
|
||||||
|
tmd = tmd_edit_type(tmd, args.type)
|
||||||
|
edits_made = True
|
||||||
|
|
||||||
|
if not edits_made:
|
||||||
|
fatal_error("You must specify at least one change to make!")
|
||||||
|
|
||||||
|
# Fakesign the title since any changes have already invalidated the signature.
|
||||||
|
tmd.fakesign()
|
||||||
|
output_path.write_bytes(tmd.dump())
|
||||||
|
|
||||||
|
print("Successfully edited TMD file!")
|
||||||
|
|
||||||
|
|
||||||
def handle_tmd_remove(args):
|
def handle_tmd_remove(args):
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# "commands/title/wad.py" from WiiPy by NinjaCheetah
|
# "commands/title/wad.py" from WiiPy by NinjaCheetah
|
||||||
# https://github.com/NinjaCheetah/WiiPy
|
# https://github.com/NinjaCheetah/WiiPy
|
||||||
|
|
||||||
import binascii
|
|
||||||
import pathlib
|
import pathlib
|
||||||
from random import randint
|
from random import randint
|
||||||
import libWiiPy
|
import libWiiPy
|
||||||
|
|
||||||
from modules.core import fatal_error
|
from modules.core import fatal_error
|
||||||
|
from modules.title import tmd_edit_ios, tmd_edit_tid, tmd_edit_type
|
||||||
|
|
||||||
|
|
||||||
def handle_wad_add(args):
|
def handle_wad_add(args):
|
||||||
@ -138,6 +137,39 @@ def handle_wad_convert(args):
|
|||||||
print(f"Successfully converted {source} WAD to {target} WAD \"{output_path.name}\"!")
|
print(f"Successfully converted {source} WAD to {target} WAD \"{output_path.name}\"!")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_wad_edit(args):
|
||||||
|
input_path = pathlib.Path(args.input)
|
||||||
|
if args.output is not None:
|
||||||
|
output_path = pathlib.Path(args.output)
|
||||||
|
else:
|
||||||
|
output_path = pathlib.Path(args.input)
|
||||||
|
|
||||||
|
title = libWiiPy.title.Title()
|
||||||
|
title.load_wad(input_path.read_bytes())
|
||||||
|
|
||||||
|
# State variable to make sure that changes are made.
|
||||||
|
edits_made = False
|
||||||
|
# Go over every possible change, and apply them if they were specified.
|
||||||
|
if args.tid is not None:
|
||||||
|
title.tmd = tmd_edit_tid(title.tmd, args.tid)
|
||||||
|
edits_made = True
|
||||||
|
if args.ios is not None:
|
||||||
|
title.tmd = tmd_edit_ios(title.tmd, args.ios)
|
||||||
|
edits_made = True
|
||||||
|
if args.type is not None:
|
||||||
|
title.tmd = tmd_edit_type(title.tmd, args.type)
|
||||||
|
edits_made = True
|
||||||
|
|
||||||
|
if not edits_made:
|
||||||
|
fatal_error("You must specify at least one change to make!")
|
||||||
|
|
||||||
|
# Fakesign the title since any changes have already invalidated the signature.
|
||||||
|
title.fakesign()
|
||||||
|
output_path.write_bytes(title.dump_wad())
|
||||||
|
|
||||||
|
print("Successfully edited WAD file!")
|
||||||
|
|
||||||
|
|
||||||
def handle_wad_pack(args):
|
def handle_wad_pack(args):
|
||||||
input_path = pathlib.Path(args.input)
|
input_path = pathlib.Path(args.input)
|
||||||
output_path = pathlib.Path(args.output)
|
output_path = pathlib.Path(args.output)
|
||||||
@ -368,75 +400,3 @@ def handle_wad_unpack(args):
|
|||||||
output_path.joinpath(content_file_name).write_bytes(title.get_content_by_index(content_file, skip_hash))
|
output_path.joinpath(content_file_name).write_bytes(title.get_content_by_index(content_file, skip_hash))
|
||||||
|
|
||||||
print("WAD file unpacked!")
|
print("WAD file unpacked!")
|
||||||
|
|
||||||
|
|
||||||
def handle_wad_edit(args):
|
|
||||||
input_path = pathlib.Path(args.input)
|
|
||||||
if args.output is not None:
|
|
||||||
output_path = pathlib.Path(args.output)
|
|
||||||
else:
|
|
||||||
output_path = pathlib.Path(args.input)
|
|
||||||
|
|
||||||
title = libWiiPy.title.Title()
|
|
||||||
title.load_wad(input_path.read_bytes())
|
|
||||||
|
|
||||||
# State variable to make sure that changes are made.
|
|
||||||
edits_made = False
|
|
||||||
# Go over every possible change, and apply them if they were specified.
|
|
||||||
if args.tid is not None:
|
|
||||||
# Setting a new TID, only changing TID low since this expects a 4 character ASCII input.
|
|
||||||
new_tid = args.tid
|
|
||||||
if len(new_tid) != 4:
|
|
||||||
fatal_error(f"The specified Title ID is not valid! The new Title ID should be 4 characters long.")
|
|
||||||
if not new_tid.isalnum():
|
|
||||||
fatal_error(f"The specified Title ID is not valid! The new Title ID should be alphanumeric.")
|
|
||||||
# Get the current TID high, because we want to preserve the title type while only changing the TID low.
|
|
||||||
tid_high = title.tmd.title_id[:8]
|
|
||||||
new_tid = f"{tid_high}{str(binascii.hexlify(new_tid.encode()), 'ascii')}"
|
|
||||||
title.set_title_id(new_tid)
|
|
||||||
edits_made = True
|
|
||||||
if args.ios is not None:
|
|
||||||
# Setting a new required IOS.
|
|
||||||
new_ios = None
|
|
||||||
try:
|
|
||||||
new_ios = int(args.ios)
|
|
||||||
except ValueError:
|
|
||||||
fatal_error("The specified IOS is not valid! The new IOS should be a valid integer.")
|
|
||||||
if new_ios < 3 or new_ios > 255:
|
|
||||||
fatal_error("The specified IOS is not valid! The new IOS version should be between 3 and 255.")
|
|
||||||
new_ios_tid = f"00000001{new_ios:08X}"
|
|
||||||
title.tmd.ios_tid = new_ios_tid
|
|
||||||
edits_made = True
|
|
||||||
if args.type is not None:
|
|
||||||
# Setting a new title type.
|
|
||||||
new_type = args.type
|
|
||||||
new_tid_high = None
|
|
||||||
match new_type:
|
|
||||||
case "System":
|
|
||||||
new_tid_high = "00000001"
|
|
||||||
case "Channel":
|
|
||||||
new_tid_high = "00010001"
|
|
||||||
case "SystemChannel":
|
|
||||||
new_tid_high = "00010002"
|
|
||||||
case "GameChannel":
|
|
||||||
new_tid_high = "00010004"
|
|
||||||
case "DLC":
|
|
||||||
new_tid_high = "00010005"
|
|
||||||
case "HiddenChannel":
|
|
||||||
new_tid_high = "00010008"
|
|
||||||
case _:
|
|
||||||
fatal_error("The specified type is not valid! The new type must be one of: System, Channel, "
|
|
||||||
"SystemChannel, GameChannel, DLC, HiddenChannel.")
|
|
||||||
tid_low = title.tmd.title_id[8:]
|
|
||||||
new_tid = f"{new_tid_high}{tid_low}"
|
|
||||||
title.set_title_id(new_tid)
|
|
||||||
edits_made = True
|
|
||||||
|
|
||||||
if not edits_made:
|
|
||||||
fatal_error("You must specify at least one change to make!")
|
|
||||||
|
|
||||||
# Fakesign the title since any changes have already invalidated the signature.
|
|
||||||
title.fakesign()
|
|
||||||
output_path.write_bytes(title.dump_wad())
|
|
||||||
|
|
||||||
print("Successfully edited WAD file!")
|
|
||||||
|
57
modules/title.py
Normal file
57
modules/title.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# "modules/title.py" from WiiPy by NinjaCheetah
|
||||||
|
# https://github.com/NinjaCheetah/WiiPy
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import libWiiPy
|
||||||
|
from modules.core import fatal_error
|
||||||
|
|
||||||
|
|
||||||
|
def tmd_edit_ios(tmd: libWiiPy.title.TMD, new_ios: str) -> libWiiPy.title.TMD:
|
||||||
|
# Setting a new required IOS.
|
||||||
|
try:
|
||||||
|
new_ios = int(new_ios)
|
||||||
|
except ValueError:
|
||||||
|
fatal_error("The specified IOS is not valid! The new IOS should be a valid integer.")
|
||||||
|
if new_ios < 3 or new_ios > 255:
|
||||||
|
fatal_error("The specified IOS is not valid! The new IOS version should be between 3 and 255.")
|
||||||
|
new_ios_tid = f"00000001{new_ios:08X}"
|
||||||
|
tmd.ios_tid = new_ios_tid
|
||||||
|
return tmd
|
||||||
|
|
||||||
|
|
||||||
|
def tmd_edit_tid(tmd: libWiiPy.title.TMD, new_tid: str) -> libWiiPy.title.TMD:
|
||||||
|
# Setting a new TID, only changing TID low since this expects a 4 character ASCII input.
|
||||||
|
if len(new_tid) != 4:
|
||||||
|
fatal_error(f"The specified Title ID is not valid! The new Title ID should be 4 characters long.")
|
||||||
|
if not new_tid.isalnum():
|
||||||
|
fatal_error(f"The specified Title ID is not valid! The new Title ID should be alphanumeric.")
|
||||||
|
# Get the current TID high, because we want to preserve the title type while only changing the TID low.
|
||||||
|
tid_high = tmd.title_id[:8]
|
||||||
|
new_tid = f"{tid_high}{str(binascii.hexlify(new_tid.encode()), 'ascii')}"
|
||||||
|
tmd.set_title_id(new_tid)
|
||||||
|
return tmd
|
||||||
|
|
||||||
|
|
||||||
|
def tmd_edit_type(tmd: libWiiPy.title.TMD, new_type: str) -> libWiiPy.title.TMD:
|
||||||
|
# Setting a new title type.
|
||||||
|
new_tid_high = None
|
||||||
|
match new_type:
|
||||||
|
case "System":
|
||||||
|
new_tid_high = "00000001"
|
||||||
|
case "Channel":
|
||||||
|
new_tid_high = "00010001"
|
||||||
|
case "SystemChannel":
|
||||||
|
new_tid_high = "00010002"
|
||||||
|
case "GameChannel":
|
||||||
|
new_tid_high = "00010004"
|
||||||
|
case "DLC":
|
||||||
|
new_tid_high = "00010005"
|
||||||
|
case "HiddenChannel":
|
||||||
|
new_tid_high = "00010008"
|
||||||
|
case _:
|
||||||
|
fatal_error("The specified type is not valid! The new type must be one of: System, Channel, "
|
||||||
|
"SystemChannel, GameChannel, DLC, HiddenChannel.")
|
||||||
|
tid_low = tmd.title_id[8:]
|
||||||
|
new_tid = f"{new_tid_high}{tid_low}"
|
||||||
|
tmd.set_title_id(new_tid)
|
||||||
|
return tmd
|
47
wiipy.py
47
wiipy.py
@ -5,7 +5,7 @@ import argparse
|
|||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
from commands.archive.ash import *
|
from commands.archive.ash import *
|
||||||
from commands.archive.theme import *
|
#from commands.archive.theme import *
|
||||||
from commands.archive.u8 import *
|
from commands.archive.u8 import *
|
||||||
from commands.nand.emunand import *
|
from commands.nand.emunand import *
|
||||||
from commands.nand.setting import *
|
from commands.nand.setting import *
|
||||||
@ -197,24 +197,39 @@ if __name__ == "__main__":
|
|||||||
setting_gen_parser.add_argument("region", metavar="REGION", type=str,
|
setting_gen_parser.add_argument("region", metavar="REGION", type=str,
|
||||||
help="region of the console these settings are for (USA, EUR, JPN, or KOR)")
|
help="region of the console these settings are for (USA, EUR, JPN, or KOR)")
|
||||||
|
|
||||||
# Argument parser for the theme subcommand.
|
# # Argument parser for the theme subcommand.
|
||||||
theme_parser = subparsers.add_parser("theme", help="apply custom themes to the Wii Menu",
|
# theme_parser = subparsers.add_parser("theme", help="apply custom themes to the Wii Menu",
|
||||||
description="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)
|
# theme_subparsers = theme_parser.add_subparsers(dest="subcommand", required=True)
|
||||||
# MYM theme subcommand.
|
# # MYM theme subcommand.
|
||||||
theme_mym_parser = theme_subparsers.add_parser("mym", help="apply an MYM theme to the Wii Menu",
|
# 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")
|
# description="apply an MYM theme to the Wii Menu")
|
||||||
theme_mym_parser.set_defaults(func=handle_apply_mym)
|
# 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("mym", metavar="MYM", type=str, help="MYM theme to apply")
|
||||||
theme_mym_parser.add_argument("base", metavar="BASE", type=str,
|
# theme_mym_parser.add_argument("base", metavar="BASE", type=str,
|
||||||
help="base Wii Menu assets to apply the theme to (000000xx.app)")
|
# help="base Wii Menu assets to apply the theme to (000000xx.app)")
|
||||||
theme_mym_parser.add_argument("output", metavar="OUT", type=str,
|
# theme_mym_parser.add_argument("output", metavar="OUT", type=str,
|
||||||
help="path to output the finished theme to (<filename>.csm)")
|
# help="path to output the finished theme to (<filename>.csm)")
|
||||||
|
|
||||||
# Argument parser for the TMD subcommand.
|
# Argument parser for the TMD subcommand.
|
||||||
tmd_parser = subparsers.add_parser("tmd", help="edit a TMD file",
|
tmd_parser = subparsers.add_parser("tmd", help="edit a TMD file",
|
||||||
description="edit a TMD file")
|
description="edit a TMD file")
|
||||||
tmd_subparsers = tmd_parser.add_subparsers(dest="subcommand", required=True)
|
tmd_subparsers = tmd_parser.add_subparsers(dest="subcommand", required=True)
|
||||||
|
# Edit TMD subcommand.
|
||||||
|
tmd_edit_parser = tmd_subparsers.add_parser("edit", help="edit the properties of a TMD file",
|
||||||
|
description="edit the properties of a TMD file; by default, this will "
|
||||||
|
"overwrite the input file unless an output is specified")
|
||||||
|
tmd_edit_parser.set_defaults(func=handle_tmd_edit)
|
||||||
|
tmd_edit_parser.add_argument("input", metavar="IN", type=str, help="TMD file to edit")
|
||||||
|
tmd_edit_parser.add_argument("--tid", metavar="TID", type=str,
|
||||||
|
help="a new Title ID for this title (formatted as 4 ASCII characters)")
|
||||||
|
tmd_edit_parser.add_argument("--ios", metavar="IOS", type=str,
|
||||||
|
help="a new IOS version for this title (formatted as the decimal IOS version, eg. 58)")
|
||||||
|
tmd_edit_parser.add_argument("--type", metavar="TYPE", type=str,
|
||||||
|
help="a new type for this title (valid options: System, Channel, SystemChannel, "
|
||||||
|
"GameChannel, DLC, HiddenChannel)")
|
||||||
|
tmd_edit_parser.add_argument("-o", "--output", metavar="OUT", type=str,
|
||||||
|
help="file to output the updated TMD to (optional)")
|
||||||
# Remove TMD subcommand.
|
# Remove TMD subcommand.
|
||||||
tmd_remove_parser = tmd_subparsers.add_parser("remove", help="remove a content record from a TMD file",
|
tmd_remove_parser = tmd_subparsers.add_parser("remove", help="remove a content record from a TMD file",
|
||||||
description="remove a content record from a TMD file, either by its "
|
description="remove a content record from a TMD file, either by its "
|
||||||
@ -248,8 +263,8 @@ if __name__ == "__main__":
|
|||||||
u8_unpack_parser.add_argument("output", metavar="OUT", type=str, help="folder to output to")
|
u8_unpack_parser.add_argument("output", metavar="OUT", type=str, help="folder to output to")
|
||||||
|
|
||||||
# Argument parser for the WAD subcommand.
|
# Argument parser for the WAD subcommand.
|
||||||
wad_parser = subparsers.add_parser("wad", help="pack/unpack a WAD file",
|
wad_parser = subparsers.add_parser("wad", help="pack/unpack/edit a WAD file",
|
||||||
description="pack/unpack a WAD file")
|
description="pack/unpack/edit a WAD file")
|
||||||
wad_subparsers = wad_parser.add_subparsers(dest="subcommand", required=True)
|
wad_subparsers = wad_parser.add_subparsers(dest="subcommand", required=True)
|
||||||
# Add WAD subcommand.
|
# Add WAD subcommand.
|
||||||
wad_add_parser = wad_subparsers.add_parser("add", help="add decrypted content to a WAD file",
|
wad_add_parser = wad_subparsers.add_parser("add", help="add decrypted content to a WAD file",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user