mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-26 05:11:02 -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 .ticket import *
|
||||||
from .title import *
|
from .title import *
|
||||||
from .tmd import *
|
from .tmd import *
|
||||||
|
from .util import *
|
||||||
from .wad import *
|
from .wad import *
|
||||||
|
@ -357,7 +357,7 @@ class ContentRegion:
|
|||||||
# If the hash matches, encrypt the content and set it where it belongs.
|
# 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
|
# 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
|
# 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)
|
enc_content = encrypt_content(dec_content, title_key, self.content_records[index].index)
|
||||||
if (index + 1) > len(self.content_list):
|
if (index + 1) > len(self.content_list):
|
||||||
self.content_list.append(enc_content)
|
self.content_list.append(enc_content)
|
||||||
|
@ -7,7 +7,7 @@ from .commonkeys import get_common_key
|
|||||||
from Crypto.Cipher import AES as _AES
|
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
|
# Converts a Title ID in various formats into the format required to act as an IV. Private function used by other
|
||||||
# crypto functions.
|
# crypto functions.
|
||||||
title_key_iv = b''
|
title_key_iv = b''
|
||||||
|
@ -9,6 +9,7 @@ import hashlib
|
|||||||
from dataclasses import dataclass as _dataclass
|
from dataclasses import dataclass as _dataclass
|
||||||
from .crypto import decrypt_title_key
|
from .crypto import decrypt_title_key
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from .util import title_ver_standard_to_dec
|
||||||
|
|
||||||
|
|
||||||
@_dataclass
|
@_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.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.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: 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.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.title_version: int = 0 # Version of the ticket's associated title.
|
||||||
self.permitted_titles: bytes = b'' # Permitted titles mask
|
self.permitted_titles: bytes = b'' # Permitted titles mask
|
||||||
@ -125,8 +125,6 @@ class Ticket:
|
|||||||
# Title ID.
|
# Title ID.
|
||||||
ticket_data.seek(0x1DC)
|
ticket_data.seek(0x1DC)
|
||||||
self.title_id = binascii.hexlify(ticket_data.read(8))
|
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.
|
# Unknown data 1.
|
||||||
ticket_data.seek(0x1E4)
|
ticket_data.seek(0x1E4)
|
||||||
self.unknown1 = ticket_data.read(2)
|
self.unknown1 = ticket_data.read(2)
|
||||||
@ -307,7 +305,8 @@ class Ticket:
|
|||||||
|
|
||||||
def set_title_id(self, title_id) -> None:
|
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
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -316,5 +315,34 @@ class Ticket:
|
|||||||
"""
|
"""
|
||||||
if len(title_id) != 16:
|
if len(title_id) != 16:
|
||||||
raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.")
|
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)
|
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)
|
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
|
# 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.
|
# 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 "
|
raise ValueError("The Title IDs of the TMD and Ticket in this WAD do not match. This WAD appears to be "
|
||||||
"invalid.")
|
"invalid.")
|
||||||
|
|
||||||
@ -131,6 +131,20 @@ class Title:
|
|||||||
self.tmd.set_title_id(title_id)
|
self.tmd.set_title_id(title_id)
|
||||||
self.ticket.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:
|
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.
|
Gets an individual content from the content region based on the provided index, in decrypted form.
|
||||||
|
@ -9,6 +9,7 @@ import hashlib
|
|||||||
import struct
|
import struct
|
||||||
from typing import List
|
from typing import List
|
||||||
from ..types import _ContentRecord
|
from ..types import _ContentRecord
|
||||||
|
from .util import title_ver_dec_to_standard, title_ver_standard_to_dec
|
||||||
|
|
||||||
|
|
||||||
class TMD:
|
class TMD:
|
||||||
@ -134,11 +135,11 @@ class TMD:
|
|||||||
# Version number straight from the TMD.
|
# Version number straight from the TMD.
|
||||||
tmd_data.seek(0x1DC)
|
tmd_data.seek(0x1DC)
|
||||||
self.title_version = int.from_bytes(tmd_data.read(2))
|
self.title_version = int.from_bytes(tmd_data.read(2))
|
||||||
# Calculate the converted version number by multiplying 0x1DC by 256 and adding 0x1DD.
|
# Calculate the converted version number via util module.
|
||||||
tmd_data.seek(0x1DC)
|
try:
|
||||||
title_version_high = int.from_bytes(tmd_data.read(1)) * 256
|
self.title_version_converted = title_ver_dec_to_standard(self.title_version, self.title_id)
|
||||||
title_version_low = int.from_bytes(tmd_data.read(1))
|
except ValueError:
|
||||||
self.title_version_converted = title_version_high + title_version_low
|
self.title_version_converted = ""
|
||||||
# The number of contents listed in the TMD.
|
# The number of contents listed in the TMD.
|
||||||
tmd_data.seek(0x1DE)
|
tmd_data.seek(0x1DE)
|
||||||
self.num_contents = int.from_bytes(tmd_data.read(2))
|
self.num_contents = int.from_bytes(tmd_data.read(2))
|
||||||
@ -374,7 +375,8 @@ class TMD:
|
|||||||
|
|
||||||
def set_title_id(self, title_id) -> None:
|
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
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -384,3 +386,37 @@ class TMD:
|
|||||||
if len(title_id) != 16:
|
if len(title_id) != 16:
|
||||||
raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.")
|
raise ValueError("Invalid Title ID! Title IDs must be 8 bytes long.")
|
||||||
self.title_id = title_id
|
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