mirror of
				https://github.com/NinjaCheetah/libWiiPy.git
				synced 2025-11-04 00:16:18 -05:00 
			
		
		
		
	Added new methods to TMD/Ticket/Title modules for changing title versions
This commit is contained in:
		
							parent
							
								
									e70b9570de
								
							
						
					
					
						commit
						5f4fa8827c
					
				@ -7,4 +7,5 @@ from .nus import *
 | 
			
		||||
from .ticket import *
 | 
			
		||||
from .title import *
 | 
			
		||||
from .tmd import *
 | 
			
		||||
from .util import *
 | 
			
		||||
from .wad import *
 | 
			
		||||
 | 
			
		||||
@ -357,7 +357,7 @@ class ContentRegion:
 | 
			
		||||
        # If the hash matches, encrypt the content and set it where it belongs.
 | 
			
		||||
        # This uses the index from the content records instead of just the index given, because there are some strange
 | 
			
		||||
        # circumstances where the actual index in the array and the assigned content index don't match up, and this
 | 
			
		||||
        # needs to accommodate that.
 | 
			
		||||
        # needs to accommodate that. Seems to only apply to cIOS WADs?
 | 
			
		||||
        enc_content = encrypt_content(dec_content, title_key, self.content_records[index].index)
 | 
			
		||||
        if (index + 1) > len(self.content_list):
 | 
			
		||||
            self.content_list.append(enc_content)
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ from .commonkeys import get_common_key
 | 
			
		||||
from Crypto.Cipher import AES as _AES
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _convert_tid_to_iv(title_id: str) -> bytes:
 | 
			
		||||
def _convert_tid_to_iv(title_id: str | bytes) -> bytes:
 | 
			
		||||
    # Converts a Title ID in various formats into the format required to act as an IV. Private function used by other
 | 
			
		||||
    # crypto functions.
 | 
			
		||||
    title_key_iv = b''
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import hashlib
 | 
			
		||||
from dataclasses import dataclass as _dataclass
 | 
			
		||||
from .crypto import decrypt_title_key
 | 
			
		||||
from typing import List
 | 
			
		||||
from .util import title_ver_standard_to_dec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@_dataclass
 | 
			
		||||
@ -66,7 +67,6 @@ class Ticket:
 | 
			
		||||
        self.ticket_id: bytes = b''  # Used as the IV when decrypting the title key for console-specific title installs.
 | 
			
		||||
        self.console_id: int = 0  # ID of the console that the ticket was issued for.
 | 
			
		||||
        self.title_id: bytes = b''  # TID/IV used for AES-CBC encryption.
 | 
			
		||||
        self.title_id_str: str = ""  # TID in string form for comparing against the TMD.
 | 
			
		||||
        self.unknown1: bytes = b''  # Some unknown data, not always the same so reading it just in case.
 | 
			
		||||
        self.title_version: int = 0  # Version of the ticket's associated title.
 | 
			
		||||
        self.permitted_titles: bytes = b''  # Permitted titles mask
 | 
			
		||||
@ -125,8 +125,6 @@ class Ticket:
 | 
			
		||||
            # Title ID.
 | 
			
		||||
            ticket_data.seek(0x1DC)
 | 
			
		||||
            self.title_id = binascii.hexlify(ticket_data.read(8))
 | 
			
		||||
            # Title ID (as a string).
 | 
			
		||||
            self.title_id_str = str(self.title_id.decode())
 | 
			
		||||
            # Unknown data 1.
 | 
			
		||||
            ticket_data.seek(0x1E4)
 | 
			
		||||
            self.unknown1 = ticket_data.read(2)
 | 
			
		||||
@ -307,7 +305,8 @@ class Ticket:
 | 
			
		||||
 | 
			
		||||
    def set_title_id(self, title_id) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Sets the Title ID of the title in the Ticket.
 | 
			
		||||
        Sets the Title ID property of the Ticket. Recommended over setting the property directly because of input
 | 
			
		||||
        validation.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
@ -316,5 +315,34 @@ class Ticket:
 | 
			
		||||
        """
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    def set_title_version(self, new_version: str | int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Sets the version of the title in the Ticket. Recommended over setting the data directly because of input
 | 
			
		||||
        validation.
 | 
			
		||||
 | 
			
		||||
        Accepts either standard form (vX.X) as a string or decimal form (vXXX) as an integer.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
        new_version : str, int
 | 
			
		||||
            The new version of the title. See description for valid formats.
 | 
			
		||||
        """
 | 
			
		||||
        if type(new_version) is str:
 | 
			
		||||
            # Validate string input is in the correct format, then validate that the version isn't higher than v255.0.
 | 
			
		||||
            # If checks pass, convert to decimal form and set that as the title version.
 | 
			
		||||
            version_str_split = new_version.split(".")
 | 
			
		||||
            if len(version_str_split) != 2:
 | 
			
		||||
                raise ValueError("Title version is not valid! String version must be entered in format \"X.X\".")
 | 
			
		||||
            if int(version_str_split[0]) > 255 or (int(version_str_split[0]) == 255 and int(version_str_split[1]) > 0):
 | 
			
		||||
                raise ValueError("Title version is not valid! String version number cannot exceed v255.0.")
 | 
			
		||||
            version_converted = title_ver_standard_to_dec(new_version, str(self.title_id.decode()))
 | 
			
		||||
            self.title_version = version_converted
 | 
			
		||||
        elif type(new_version) is int:
 | 
			
		||||
            # Validate that the version isn't higher than v65280. If the check passes, set that as the title version.
 | 
			
		||||
            if new_version > 65280:
 | 
			
		||||
                raise ValueError("Title version is not valid! Integer version number cannot exceed v65280.")
 | 
			
		||||
            self.title_version = new_version
 | 
			
		||||
        else:
 | 
			
		||||
            raise TypeError("Title version type is not valid! Type must be either integer or string.")
 | 
			
		||||
 | 
			
		||||
@ -56,7 +56,7 @@ class Title:
 | 
			
		||||
        self.content.load(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:
 | 
			
		||||
        if self.tmd.title_id != str(self.ticket.title_id.decode()):
 | 
			
		||||
            raise ValueError("The Title IDs of the TMD and Ticket in this WAD do not match. This WAD appears to be "
 | 
			
		||||
                             "invalid.")
 | 
			
		||||
 | 
			
		||||
@ -131,6 +131,20 @@ class Title:
 | 
			
		||||
        self.tmd.set_title_id(title_id)
 | 
			
		||||
        self.ticket.set_title_id(title_id)
 | 
			
		||||
 | 
			
		||||
    def set_title_version(self, title_version: str | int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Sets the version of the title in both the TMD and Ticket.
 | 
			
		||||
 | 
			
		||||
        Accepts either standard form (vX.X) as a string or decimal form (vXXX) as an integer.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
        title_version : str, int
 | 
			
		||||
            The new version of the title. See description for valid formats.
 | 
			
		||||
        """
 | 
			
		||||
        self.tmd.set_title_version(title_version)
 | 
			
		||||
        self.ticket.set_title_version(title_version)
 | 
			
		||||
 | 
			
		||||
    def get_content_by_index(self, index: id) -> bytes:
 | 
			
		||||
        """
 | 
			
		||||
        Gets an individual content from the content region based on the provided index, in decrypted form.
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import hashlib
 | 
			
		||||
import struct
 | 
			
		||||
from typing import List
 | 
			
		||||
from ..types import _ContentRecord
 | 
			
		||||
from .util import title_ver_dec_to_standard, title_ver_standard_to_dec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TMD:
 | 
			
		||||
@ -134,11 +135,11 @@ class TMD:
 | 
			
		||||
            # Version number straight from the TMD.
 | 
			
		||||
            tmd_data.seek(0x1DC)
 | 
			
		||||
            self.title_version = int.from_bytes(tmd_data.read(2))
 | 
			
		||||
            # Calculate the converted version number by multiplying 0x1DC by 256 and adding 0x1DD.
 | 
			
		||||
            tmd_data.seek(0x1DC)
 | 
			
		||||
            title_version_high = int.from_bytes(tmd_data.read(1)) * 256
 | 
			
		||||
            title_version_low = int.from_bytes(tmd_data.read(1))
 | 
			
		||||
            self.title_version_converted = title_version_high + title_version_low
 | 
			
		||||
            # Calculate the converted version number via util module.
 | 
			
		||||
            try:
 | 
			
		||||
                self.title_version_converted = title_ver_dec_to_standard(self.title_version, self.title_id)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                self.title_version_converted = ""
 | 
			
		||||
            # The number of contents listed in the TMD.
 | 
			
		||||
            tmd_data.seek(0x1DE)
 | 
			
		||||
            self.num_contents = int.from_bytes(tmd_data.read(2))
 | 
			
		||||
@ -374,7 +375,8 @@ class TMD:
 | 
			
		||||
 | 
			
		||||
    def set_title_id(self, title_id) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Sets the Title ID of the title in the ticket.
 | 
			
		||||
        Sets the Title ID property of the TMD. Recommended over setting the property directly because of input
 | 
			
		||||
        validation.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
@ -384,3 +386,37 @@ class TMD:
 | 
			
		||||
        if len(title_id) != 16:
 | 
			
		||||
            raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.")
 | 
			
		||||
        self.title_id = title_id
 | 
			
		||||
 | 
			
		||||
    def set_title_version(self, new_version: str | int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Sets the version of the title in the TMD. Recommended over setting the data directly because of input
 | 
			
		||||
        validation.
 | 
			
		||||
 | 
			
		||||
        Accepts either standard form (vX.X) as a string or decimal form (vXXX) as an integer.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
        new_version : str, int
 | 
			
		||||
            The new version of the title. See description for valid formats.
 | 
			
		||||
        """
 | 
			
		||||
        if type(new_version) is str:
 | 
			
		||||
            # Validate string input is in the correct format, then validate that the version isn't higher than v255.0.
 | 
			
		||||
            # If checks pass, set that as the converted version, then convert to decimal form and set that as well.
 | 
			
		||||
            version_str_split = new_version.split(".")
 | 
			
		||||
            if len(version_str_split) != 2:
 | 
			
		||||
                raise ValueError("Title version is not valid! String version must be entered in format \"X.X\".")
 | 
			
		||||
            if int(version_str_split[0]) > 255 or (int(version_str_split[0]) == 255 and int(version_str_split[1]) > 0):
 | 
			
		||||
                raise ValueError("Title version is not valid! String version number cannot exceed v255.0.")
 | 
			
		||||
            self.title_version_converted = new_version
 | 
			
		||||
            version_converted = title_ver_standard_to_dec(new_version, self.title_id)
 | 
			
		||||
            self.title_version = version_converted
 | 
			
		||||
        elif type(new_version) is int:
 | 
			
		||||
            # Validate that the version isn't higher than v65280. If the check passes, set that as the title version,
 | 
			
		||||
            # then convert to standard form and set that as well.
 | 
			
		||||
            if new_version > 65280:
 | 
			
		||||
                raise ValueError("Title version is not valid! Integer version number cannot exceed v65280.")
 | 
			
		||||
            self.title_version = new_version
 | 
			
		||||
            version_converted = title_ver_dec_to_standard(new_version, self.title_id)
 | 
			
		||||
            self.title_version_converted = version_converted
 | 
			
		||||
        else:
 | 
			
		||||
            raise TypeError("Title version type is not valid! Type must be either integer or string.")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										68
									
								
								src/libWiiPy/title/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/libWiiPy/title/util.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
# "title/util.py" from libWiiPy by NinjaCheetah & Contributors
 | 
			
		||||
# https://github.com/NinjaCheetah/libWiiPy
 | 
			
		||||
#
 | 
			
		||||
# General title-related utilities that don't fit within a specific module.
 | 
			
		||||
 | 
			
		||||
import math
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def title_ver_dec_to_standard(version: int, title_id: str) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    Converts a title's version from decimal form (vXXX, the way the version is stored in the TMD/Ticket) to its standard
 | 
			
		||||
    and human-readable form (vX.X). The Title ID is required as some titles handle this version differently from others.
 | 
			
		||||
    For the System Menu, the returned version will include the region code (ex. 4.3U).
 | 
			
		||||
 | 
			
		||||
    Parameters
 | 
			
		||||
    ----------
 | 
			
		||||
    version : int
 | 
			
		||||
        The version of the title, in decimal form.
 | 
			
		||||
    title_id : str
 | 
			
		||||
        The Title ID that the version is associated with.
 | 
			
		||||
 | 
			
		||||
    Returns
 | 
			
		||||
    -------
 | 
			
		||||
    str
 | 
			
		||||
        The version of the title, in standard form.
 | 
			
		||||
    """
 | 
			
		||||
    version_out = ""
 | 
			
		||||
    if title_id == "0000000100000002":
 | 
			
		||||
        raise ValueError("The System Menu's version cannot currently be converted.")
 | 
			
		||||
    else:
 | 
			
		||||
        # For most channels, we need to get the floored value of version / 256 for the major version, and the version %
 | 
			
		||||
        # 256 as the minor version. Minor versions > 9 are intended, as Nintendo themselves frequently used them.
 | 
			
		||||
        version_upper = math.floor(version / 256)
 | 
			
		||||
        version_lower = version % 256
 | 
			
		||||
        version_out = f"{version_upper}.{version_lower}"
 | 
			
		||||
 | 
			
		||||
    return version_out
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def title_ver_standard_to_dec(version: str, title_id: str) -> int:
 | 
			
		||||
    """
 | 
			
		||||
    Converts a title's version from its standard and human-readable form (vX.X) to its decimal form (vXXX, the way the
 | 
			
		||||
    version is stored in the TMD/Ticket). The Title ID is required as some titles handle this version differently from
 | 
			
		||||
    others. For the System Menu, the supplied version must include the region code (ex. 4.3U) for the conversion to
 | 
			
		||||
    work correctly.
 | 
			
		||||
 | 
			
		||||
    Parameters
 | 
			
		||||
    ----------
 | 
			
		||||
    version : str
 | 
			
		||||
        The version of the title, in standard form.
 | 
			
		||||
    title_id : str
 | 
			
		||||
        The Title ID that the version is associated with.
 | 
			
		||||
 | 
			
		||||
    Returns
 | 
			
		||||
    -------
 | 
			
		||||
    int
 | 
			
		||||
        The version of the title, in decimal form.
 | 
			
		||||
    """
 | 
			
		||||
    version_out = 0
 | 
			
		||||
    if title_id == "0000000100000002":
 | 
			
		||||
        raise ValueError("The System Menu's version cannot currently be converted.")
 | 
			
		||||
    else:
 | 
			
		||||
        version_str_split = version.split(".")
 | 
			
		||||
        version_upper = int(version_str_split[0]) * 256
 | 
			
		||||
        version_lower = int(version_str_split[1])
 | 
			
		||||
        version_out = version_upper + version_lower
 | 
			
		||||
 | 
			
		||||
    return version_out
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user