mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-25 12:51:01 -04: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