From 7b6703cf3698af394eb7a1a5684ac418763aaa73 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:48:55 -0400 Subject: [PATCH] Created title.py to handle titles as a whole and allow for changing parts of them more easily --- src/libWiiPy/__init__.py | 8 +++--- src/libWiiPy/ticket.py | 18 +++++++++++++ src/libWiiPy/title.py | 58 ++++++++++++++++++++++++++++++++++++++++ src/libWiiPy/tmd.py | 12 +++++++++ src/libWiiPy/wad.py | 2 +- 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/libWiiPy/title.py diff --git a/src/libWiiPy/__init__.py b/src/libWiiPy/__init__.py index 33b8173..7530d43 100644 --- a/src/libWiiPy/__init__.py +++ b/src/libWiiPy/__init__.py @@ -1,7 +1,9 @@ # "__init__.py" from libWiiPy by NinjaCheetah & Contributors # https://github.com/NinjaCheetah/libWiiPy -from .wad import * -from .tmd import * -from .ticket import * +from .commonkeys import * from .content import * +from .ticket import * +from .title import * +from .tmd import * +from .wad import * diff --git a/src/libWiiPy/ticket.py b/src/libWiiPy/ticket.py index 3682d09..982922c 100644 --- a/src/libWiiPy/ticket.py +++ b/src/libWiiPy/ticket.py @@ -4,6 +4,7 @@ # See https://wiibrew.org/wiki/Ticket for details about the ticket format import io +import binascii from .crypto import decrypt_title_key from dataclasses import dataclass from typing import List @@ -66,6 +67,7 @@ class Ticket: self.ticket_id: bytes # Used as the IV when decrypting the title key for console-specific title installs. self.console_id: int # ID of the console that the ticket was issued for. self.title_id: bytes # TID/IV used for AES-CBC encryption. + self.title_id_str: str # TID in string form for comparing against the TMD. self.title_version: int # Version of the ticket's associated title. self.permitted_titles: bytes # Permitted titles mask self.permit_mask: bytes # "Permit mask. The current disc title is ANDed with the inverse of this mask to see if the result matches the Permitted Titles Mask." @@ -106,6 +108,9 @@ class Ticket: # Title ID ticket_data.seek(0x1DC) self.title_id = ticket_data.read(8) + # Title ID (as a string) + title_id_hex = binascii.hexlify(self.title_id) + self.title_id_str = str(title_id_hex.decode()) # Title version ticket_data.seek(0x1E6) title_version_high = int.from_bytes(ticket_data.read(1)) * 256 @@ -175,3 +180,16 @@ class Ticket: """ title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id) return title_key + + def set_title_id(self, title_id): + """Sets the Title ID of the title in the Ticket. + + Parameters + ---------- + title_id : str + The new Title ID of the title. + """ + if len(title_id) != 16: + raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.") + self.title_id_str = title_id + self.title_id = binascii.unhexlify(title_id) diff --git a/src/libWiiPy/title.py b/src/libWiiPy/title.py new file mode 100644 index 0000000..1463a08 --- /dev/null +++ b/src/libWiiPy/title.py @@ -0,0 +1,58 @@ +# "title.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy +# +# See https://wiibrew.org/wiki/Title for details about how titles are formatted + +from .content import ContentRegion +from .ticket import Ticket +from .tmd import TMD +from .wad import WAD + + +class Title: + """Creates a Title object that contains all components of a title, and allows altering them. + + Parameters + ---------- + wad : WAD + A WAD object to load data from. + + Attributes + ---------- + tmd : TMD + A TMD object of the title's TMD. + ticket : Ticket + A Ticket object of the title's Ticket. + content: ContentRegion + A ContentRegion object containing the title's contents. + """ + def __init__(self, wad: WAD): + self.wad = wad + self.tmd: TMD + self.ticket: Ticket + self.content: ContentRegion + # Load data from the WAD object, and generate all other objects from the data in it. + # Load the TMD. + self.tmd = TMD(self.wad.get_tmd_data()) + # Load the ticket. + self.ticket = Ticket(self.wad.get_ticket_data()) + # Load the content. + self.content = ContentRegion(self.wad.get_content_data(), self.tmd.content_records) + # Ensure that the Title IDs of the TMD and Ticket match before doing anything else. If they don't, throw an + # error because clearly something strange has gone on with the WAD and editing it probably won't work. + if self.tmd.title_id != self.ticket.title_id_str: + raise ValueError("The Title IDs of the TMD and Ticket in this WAD do not match. This WAD appears to be " + "invalid.") + + def set_title_id(self, title_id: str): + """Sets the Title ID of the title in both the TMD and Ticket. + + Parameters + ---------- + title_id : str + The new Title ID of the title. + """ + if len(title_id) != 16: + raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.") + self.tmd.set_title_id(title_id) + self.ticket.set_title_id(title_id) diff --git a/src/libWiiPy/tmd.py b/src/libWiiPy/tmd.py index a033706..bb82d2b 100644 --- a/src/libWiiPy/tmd.py +++ b/src/libWiiPy/tmd.py @@ -235,3 +235,15 @@ class TMD: else: raise IndexError("Invalid content record! TMD lists '" + str(self.num_contents - 1) + "' contents but index was '" + str(record) + "'!") + + def set_title_id(self, title_id): + """Sets the Title ID of the title in the ticket. + + Parameters + ---------- + title_id : str + The new Title ID of the title. + """ + if len(title_id) != 16: + raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.") + self.title_id = title_id diff --git a/src/libWiiPy/wad.py b/src/libWiiPy/wad.py index ad454ee..3b648c1 100644 --- a/src/libWiiPy/wad.py +++ b/src/libWiiPy/wad.py @@ -47,7 +47,7 @@ class WAD: wad_magic = str(wad_magic_hex.decode()) if wad_magic != "0000002049730000": raise TypeError("This does not appear to be a valid WAD file, or is a boot2 WAD, which is not currently" - "supported by this library.") + " supported by this library.") # ==================================================================================== # Get the sizes of each data region contained within the WAD. # ====================================================================================