Compare commits

..

4 Commits

5 changed files with 122 additions and 46 deletions

View File

@ -43,8 +43,22 @@ class Certificate:
Attributes Attributes
---------- ----------
something : bool type: CertificateType
I'm a placeholder attribute for later. The type of the certificate, either RSA-2048, RSA-4096, or ECC.
signature: bytes
The signature data of the certificate.
issuer: str
The certificate that issued this certificate.
pub_key_type: CertificateKeyType
The type of public key contained in the certificate, either RSA-2048, RSA-4096, or ECC.
child_name: str
The name of this certificate.
pub_key_id: int
The ID of this certificate's public key.
pub_key_modulus: int
The modulus of this certificate's public key. Combined with the exponent to get the full key.
pub_key_exponent: int
The exponent of this certificate's public key. Combined with the modulus to get the full key.
""" """
def __init__(self): def __init__(self):
self.type: CertificateType | None = None self.type: CertificateType | None = None
@ -198,7 +212,8 @@ class CertificateChain:
def verify_ca_cert(ca_cert: Certificate) -> bool: def verify_ca_cert(ca_cert: Certificate) -> bool:
""" """
Verify a Wii CA certificate using the root public key. Verify a Wii CA certificate using the root public key. The retail or development root key will be automatically
selected based off of the name of the CA certificate provided.
Parameters Parameters
---------- ----------
@ -210,23 +225,44 @@ def verify_ca_cert(ca_cert: Certificate) -> bool:
bool bool
Whether the certificate is valid or not. Whether the certificate is valid or not.
""" """
root_key_modulus = (b'\xf8$lX\xba\xe7P\x03\x01\xfb\xb7\xc2\xeb\xe0\x01\x05q\xda\x92#x\xf0QN\xc0\x03\x1d\xd0\xd2\x1e' if ca_cert.issuer != "Root" or ca_cert.child_name.find("CA") == -1:
b'\xd3\xd0~\xfc\x85 i\xb5\xde\x9b\xb9Q\xa8\xbc\x90\xa2D\x92m7\x92\x95\xae\x946\xaa\xa6\xa3\x02Q' raise ValueError("The provided certificate is not a CA certificate!")
b'\x0c{\x1d\xed\xd5\xfb \x86\x9d\x7f0\x16\xf6\xbee\xd3\x83\xa1m\xb32\x1b\x955\x18\x90\xb1p\x02' if ca_cert.child_name == "CA00000001":
b'\x93~\xe1\x93\xf5~\x99\xa2GN\x9d8$\xc7\xae\xe3\x85A\xf5g\xe7Q\x8cz\x0e8\xe7\xeb\xafA\x19\x1b' root_key_modulus = \
b'\xcf\xf1{B\xa6\xb4\xed\xe6\xce\x8d\xe71\x8f\x7fR\x04\xb3\x99\x0e"gE\xaf\xd4\x85\xb2D\x93\x00' (b'\xf8$lX\xba\xe7P\x03\x01\xfb\xb7\xc2\xeb\xe0\x01\x05q\xda\x92#x\xf0QN\xc0\x03\x1d\xd0\xd2\x1e\xd3\xd0~'
b'\x8b\x08\xc7\xf6\xb7\xe5k\x02\xb3\xe8\xfe\x0c\x9d\x85\x9c\xb8\xb6\x82#\xb8\xab\'\xee_e8\x07' b'\xfc\x85 i\xb5\xde\x9b\xb9Q\xa8\xbc\x90\xa2D\x92m7\x92\x95\xae\x946\xaa\xa6\xa3\x02Q\x0c{\x1d\xed\xd5'
b'\x8b-\xb9\x1e*\x15>\x85\x81\x80r\xa2;m\xd92\x81\x05Oo\xb0\xf6\xf5\xad(>\xca\x0bz\xf3TU\xe0=' b'\xfb \x86\x9d\x7f0\x16\xf6\xbee\xd3\x83\xa1m\xb32\x1b\x955\x18\x90\xb1p\x02\x93~\xe1\x93\xf5~\x99\xa2GN'
b'\xa7\xb6\x83&\xf3\xec\x83J\xf3\x14\x04\x8a\xc6\xdf \xd2\x85\x08g<\xabb\xa2\xc7\xbc\x13\x1aS>' b'\x9d8$\xc7\xae\xe3\x85A\xf5g\xe7Q\x8cz\x0e8\xe7\xeb\xafA\x19\x1b\xcf\xf1{B\xa6\xb4\xed\xe6\xce\x8d\xe71'
b'\x0bf\x80k\x1c0fK7#1\xbd\xc4\xb0\xca\xd8\xd1\x1e\xe7\xbb\xd9(UH\xaa\xec\x1ff\xe8!\xb3\xc8' b'\x8f\x7fR\x04\xb3\x99\x0e"gE\xaf\xd4\x85\xb2D\x93\x00\x8b\x08\xc7\xf6\xb7\xe5k\x02\xb3\xe8\xfe\x0c\x9d'
b'\xa0Gi\x00\xc5\xe6\x88\xe8\x0c\xce<a\xd6\x9c\xbb\xa17\xc6`Ozr\xdd\x8c{>=Q)\r\xaajY{\x08\x1f' b'\x85\x9c\xb8\xb6\x82#\xb8\xab\'\xee_e8\x07\x8b-\xb9\x1e*\x15>\x85\x81\x80r\xa2;m\xd92\x81\x05Oo\xb0\xf6'
b'\x9d63\xa3Fz5a\t\xac\xa7\xdd}./\xb2\xc1\xae\xb8\xe2\x0fH\x92\xd8\xb9\xf8\xb4oN<\x11\xf4\xf4}' b'\xf5\xad(>\xca\x0bz\xf3TU\xe0=\xa7\xb6\x83&\xf3\xec\x83J\xf3\x14\x04\x8a\xc6\xdf \xd2\x85\x08g<\xabb\xa2'
b'\x8bu}\xfe\xfe\xa3\x89\x9c3Y\\^\xfd\xeb\xcb\xab\xe8A>:\x9a\x80<i5n\xb2\xb2\xad\\\xc4\xc8XE^' b'\xc7\xbc\x13\x1aS>\x0bf\x80k\x1c0fK7#1\xbd\xc4\xb0\xca\xd8\xd1\x1e\xe7\xbb\xd9(UH\xaa\xec\x1ff\xe8!\xb3'
b'\xf5\xf7\xb3\x06D\xb4|d\x06\x8c\xdf\x80\x9fv\x02Z-\xb4F\xe0=|\xf6/4\xe7\x02E{\x02\xa4\xcf]' b'\xc8\xa0Gi\x00\xc5\xe6\x88\xe8\x0c\xce<a\xd6\x9c\xbb\xa17\xc6`Ozr\xdd\x8c{>=Q)\r\xaajY{\x08\x1f\x9d63'
b'\x9d\xd5<\xa5:|\xa6)x\x8cg\xca\x08\xbf\xec\xcaC\xa9W\xad\x16\xc9N\x1c\xd8u\xca\x10}\xce~\x01' b'\xa3Fz5a\t\xac\xa7\xdd}./\xb2\xc1\xae\xb8\xe2\x0fH\x92\xd8\xb9\xf8\xb4oN<\x11\xf4\xf4}\x8bu}\xfe\xfe\xa3'
b'\x18\xf0\xdfk\xfe\xe5\x1d\xdb\xd9\x91\xc2n`\xcdHX\xaaY,\x82\x00u\xf2\x9fRl\x91|o\xe5@>\xa7' b'\x89\x9c3Y\\^\xfd\xeb\xcb\xab\xe8A>:\x9a\x80<i5n\xb2\xb2\xad\\\xc4\xc8XE^\xf5\xf7\xb3\x06D\xb4|d\x06\x8c'
b'\xd4\xa5\x0c\xec;s\x84\xde\x88n\x82\xd2\xebMNB\xb5\xf2\xb1I\xa8\x1e\xa7\xceqD\xdc)\x94\xcf' b'\xdf\x80\x9fv\x02Z-\xb4F\xe0=|\xf6/4\xe7\x02E{\x02\xa4\xcf]\x9d\xd5<\xa5:|\xa6)x\x8cg\xca\x08\xbf\xec'
b'\xc4N\x1f\x91\xcb\xd4\x95') b'\xcaC\xa9W\xad\x16\xc9N\x1c\xd8u\xca\x10}\xce~\x01\x18\xf0\xdfk\xfe\xe5\x1d\xdb\xd9\x91\xc2n`\xcdHX\xaa'
b'Y,\x82\x00u\xf2\x9fRl\x91|o\xe5@>\xa7\xd4\xa5\x0c\xec;s\x84\xde\x88n\x82\xd2\xebMNB\xb5\xf2\xb1I\xa8\x1e'
b'\xa7\xceqD\xdc)\x94\xcf\xc4N\x1f\x91\xcb\xd4\x95')
elif ca_cert.child_name == "CA00000002":
root_key_modulus = \
(b'\x00\xd0\x1f\xe1\x00\xd45V\xb2KV\xda\xe9q\xb5\xa5\xd3\x84\xb90\x03\xbe\x1b\xbf(\xa20[\x06\x06EF}[\x02Q'
b'\xd2V\x1a\'O\x9e\x9f\x9c\xecdaP\xab=*\xe36hf\xac\xa4\xba\xe8\x1a\xe3\xd7\x9a\xa6\xb0J\x8b\xcb\xa7\xe6'
b'\xfbd\x89E\xeb\xdf\xdb\x85\xba\t\x1f\xd7\xd1\x14\xb5\xa3\xa7\x80\xe3\xa2.n\xcd\x87\xb5\xa4\xc6\xf9\x10'
b'\xe4\x03"\x08\x81K\x0c\xee\xa1\xa1}\xf79i_a~\xf65(\xdb\x94\x967\xa0V\x03\x7f{2A8\x95\xc0\xa8\xf1\x98.'
b'\x15e\xe3\x8e\xed\xc2.Y\x0e\xe2g{\x86\t\xf4\x8c.0?\xbc@\\\xac\x18\x04/\x82 \x84\xe4\x93h\x03\xda\x7fA4'
b'\x92HV+\x8e\xe1/x\xf8\x03$c0\xbc{\xe7\xeerJ\xf4X\xa4r\xe7\xabF\xa1\xa7\xc1\x0c/\x18\xfa\x07\xc3\xdd\xd8'
b'\x98\x06\xa1\x1c\x9c\xc10\xb2G\xa3<\x8dG\xdeg\xf2\x9eUw\xb1\x1cCI=[\xbav4\xa7\xe4\xe7\x151\xb7\xdfY\x81'
b'\xfe$\xa1\x14UL\xbd\x8f\x00\\\xe1\xdb5\x08\\\xcf\xc7x\x06\xb6\xde%@h\xa2l\xb5I-E\x80C\x8f\xe1\xe5\xa9'
b'\xedu\xc5\xedE\x1d\xcex\x949\xcc\xc3\xba(\xa21*\x1b\x87\x19\xef\x0fs\xb7\x13\x95\x0c\x02Y\x1atb\xa6\x07'
b'\xf3|\n\xa7\xa1\x8f\xa9C\xa3mu*_A\x92\xf0\x13a\x00\xaa\x9c\xb4\x1b\xbe\x14\xbe\xb1\xf9\xfci/\xdf\xa0\x94'
b'F\xdeZ\x9d\xde,\xa5\xf6\x8c\x1c\x0c!B\x92\x87\xcb-\xaa\xa3\xd2cu/s\xe0\x9f\xafDy\xd2\x81t)\xf6\x98\x00'
b'\xaf\xdekY-\xc1\x98\x82\xbd\xf5\x81\xcc\xab\xf2\xcb\x91\x02\x9e\xf3\\L\xfd\xbb\xffI\xc1\xfa\x1b/\xe3\x1d'
b'\xe7\xa5`\xec\xb4~\xbc\xfe2B[\x95o\x81\xb6\x99\x17H~;x\x91Q\xdb.x\xb1\xfd.\xbe~bk>\xa1e\xb4\xfb\x00\xcc'
b'\xb7Q\xafPs)\xc4\xa3\x93\x9e\xa6\xdd\x9cP\xa0\xe78k\x01EykA\xafa\xf7\x85U\x94O;\xc2-\xc3\xbd\r\x00\xf8y'
b'\x8aB\xb1\xaa\xa0\x83 e\x9a\xc79Z\xb4\xf3)')
else:
raise ValueError("The provided CA certificate is not valid!")
root_key_exponent = 0x00010001 root_key_exponent = 0x00010001
cert_hash = SHA1.new(ca_cert.dump()[576:]) cert_hash = SHA1.new(ca_cert.dump()[576:])
public_key = RSA.construct((int.from_bytes(root_key_modulus), root_key_exponent)) public_key = RSA.construct((int.from_bytes(root_key_modulus), root_key_exponent))
@ -253,6 +289,12 @@ def verify_cert_sig(ca_cert: Certificate, target_cert: Certificate) -> bool:
bool bool
Whether the certificate's signature is valid or not. Whether the certificate's signature is valid or not.
""" """
if ca_cert.issuer != "Root" or ca_cert.child_name.find("CA") == -1:
raise ValueError("The provided certificate is not a CA certificate!")
# The issuer of the TMD/Ticket certs is Root-CA0000000X, so prepend "Root-" to the CA cert child name. If these
# don't match, then there's probably a mismatch between retail and development certs.
if f"Root-{ca_cert.child_name}" != target_cert.issuer:
raise ValueError("The certificate you are trying to verify does not match the provided CA certificate!")
cert_hash = SHA1.new(target_cert.dump()[320:]) cert_hash = SHA1.new(target_cert.dump()[320:])
public_key = RSA.construct((ca_cert.pub_key_modulus, ca_cert.pub_key_exponent)) public_key = RSA.construct((ca_cert.pub_key_modulus, ca_cert.pub_key_exponent))
try: try:
@ -278,6 +320,10 @@ def verify_tmd_sig(tmd_cert: Certificate, tmd: TMD) -> bool:
bool bool
Whether the TMD's signature is valid or not. Whether the TMD's signature is valid or not.
""" """
if tmd_cert.issuer.find("Root-CA") == -1 or tmd_cert.child_name.find("CP") == -1:
raise ValueError("The provided TMD certificate is not valid!")
if f"{tmd_cert.issuer}-{tmd_cert.child_name}" != tmd.signature_issuer:
raise ValueError("The signature you are trying to verify was not created with the provided TMD certificate!")
tmd_hash = SHA1.new(tmd.dump()[320:]) tmd_hash = SHA1.new(tmd.dump()[320:])
public_key = RSA.construct((tmd_cert.pub_key_modulus, tmd_cert.pub_key_exponent)) public_key = RSA.construct((tmd_cert.pub_key_modulus, tmd_cert.pub_key_exponent))
try: try:
@ -303,6 +349,10 @@ def verify_ticket_sig(ticket_cert: Certificate, ticket: Ticket) -> bool:
bool bool
Whether the Ticket's signature is valid or not. Whether the Ticket's signature is valid or not.
""" """
if ticket_cert.issuer.find("Root-CA") == -1 or ticket_cert.child_name.find("XS") == -1:
raise ValueError("The provided Ticket certificate is not valid!")
if f"{ticket_cert.issuer}-{ticket_cert.child_name}" != ticket.signature_issuer:
raise ValueError("The signature you are trying to verify was not created with the provided Ticket certificate!")
ticket_hash = SHA1.new(ticket.dump()[320:]) ticket_hash = SHA1.new(ticket.dump()[320:])
public_key = RSA.construct((ticket_cert.pub_key_modulus, ticket_cert.pub_key_exponent)) public_key = RSA.construct((ticket_cert.pub_key_modulus, ticket_cert.pub_key_exponent))
try: try:

View File

@ -40,10 +40,10 @@ def download_title(title_id: str, title_version: int = None, wiiu_endpoint: bool
""" """
# First, create the new title. # First, create the new title.
title = Title() title = Title()
# Download and load the TMD, Ticket, and certs. # Download and load the certificate chain, TMD, and Ticket.
title.load_cert_chain(download_cert_chain(wiiu_endpoint, endpoint_override))
title.load_tmd(download_tmd(title_id, title_version, wiiu_endpoint, endpoint_override)) title.load_tmd(download_tmd(title_id, title_version, wiiu_endpoint, endpoint_override))
title.load_ticket(download_ticket(title_id, wiiu_endpoint, endpoint_override)) title.load_ticket(download_ticket(title_id, wiiu_endpoint, endpoint_override))
title.wad.set_cert_data(download_cert(wiiu_endpoint, endpoint_override))
# Download all contents # Download all contents
title.load_content_records() title.load_content_records()
title.content.content_list = download_contents(title_id, title.tmd, wiiu_endpoint, endpoint_override) title.content.content_list = download_contents(title_id, title.tmd, wiiu_endpoint, endpoint_override)
@ -146,9 +146,9 @@ def download_ticket(title_id: str, wiiu_endpoint: bool = False, endpoint_overrid
return ticket return ticket
def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) -> bytes: def download_cert_chain(wiiu_endpoint: bool = False, endpoint_override: str = None) -> bytes:
""" """
Downloads the signing certificate used by all WADs. This uses System Menu 4.3U as the source. Downloads the signing certificate chain used by all WADs. This uses System Menu 4.3U as the source.
Parameters Parameters
---------- ----------
@ -163,7 +163,7 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
bytes bytes
The cert file. The cert file.
""" """
# Download the TMD and cetk for the System Menu 4.3U. # Download the TMD and cetk for System Menu 4.3U (v513).
if endpoint_override is not None: if endpoint_override is not None:
endpoint_url = _validate_endpoint(endpoint_override) endpoint_url = _validate_endpoint(endpoint_override)
else: else:
@ -175,18 +175,18 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
cetk_url = endpoint_url + "0000000100000002/cetk" cetk_url = endpoint_url + "0000000100000002/cetk"
tmd = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content tmd = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content
cetk = requests.get(url=cetk_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content cetk = requests.get(url=cetk_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content
# Assemble the certificate. # Assemble the certificate chain.
cert = b'' cert_chain = b''
# Certificate Authority data. # Certificate Authority data.
cert += cetk[0x2A4 + 768:] cert_chain += cetk[0x2A4 + 768:]
# Certificate Policy data. # Certificate Policy (TMD certificate) data.
cert += tmd[0x328:0x328 + 768] cert_chain += tmd[0x328:0x328 + 768]
# XS data. # XS (Ticket certificate) data.
cert += cetk[0x2A4:0x2A4 + 768] cert_chain += cetk[0x2A4:0x2A4 + 768]
# Since the cert is always the same, check the hash to make sure nothing went wildly wrong. # Since the cert chain is always the same, check the hash to make sure nothing went wildly wrong.
if hashlib.sha1(cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0": if hashlib.sha1(cert_chain).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
raise Exception("An unknown error has occurred downloading and creating the certificate.") raise Exception("An unknown error has occurred downloading and creating the certificate.")
return cert return cert_chain
def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False, def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False,

View File

@ -107,7 +107,7 @@ class Ticket:
self.signature = ticket_data.read(256) self.signature = ticket_data.read(256)
# Signature issuer. # Signature issuer.
ticket_data.seek(0x140) ticket_data.seek(0x140)
self.signature_issuer = str(ticket_data.read(64).decode()) self.signature_issuer = str(ticket_data.read(64).replace(b'\x00', b'').decode())
# ECDH data. # ECDH data.
ticket_data.seek(0x180) ticket_data.seek(0x180)
self.ecdh_data = ticket_data.read(60) self.ecdh_data = ticket_data.read(60)
@ -182,7 +182,10 @@ class Ticket:
# Padding to 64 bytes. # Padding to 64 bytes.
ticket_data += b'\x00' * 60 ticket_data += b'\x00' * 60
# Signature issuer. # Signature issuer.
ticket_data += str.encode(self.signature_issuer) signature_issuer = self.signature_issuer.encode()
while len(signature_issuer) < 0x40:
signature_issuer += b'\x00'
ticket_data += signature_issuer
# ECDH data. # ECDH data.
ticket_data += self.ecdh_data ticket_data += self.ecdh_data
# Ticket version. # Ticket version.

View File

@ -4,6 +4,7 @@
# See https://wiibrew.org/wiki/Title for details about how titles are formatted # See https://wiibrew.org/wiki/Title for details about how titles are formatted
import math import math
from .cert import CertificateChain
from .content import ContentRegion from .content import ContentRegion
from .ticket import Ticket from .ticket import Ticket
from .tmd import TMD from .tmd import TMD
@ -19,17 +20,20 @@ class Title:
Attributes Attributes
---------- ----------
wad : WAD wad: WAD
A WAD object of a WAD containing the title's data. A WAD object of a WAD containing the title's data.
tmd : TMD cert_chain: CertificateChain
The chain of certificates used to verify the contents of a title.
tmd: TMD
A TMD object of the title's TMD. A TMD object of the title's TMD.
ticket : Ticket ticket: Ticket
A Ticket object of the title's Ticket. A Ticket object of the title's Ticket.
content: ContentRegion content: ContentRegion
A ContentRegion object containing the title's contents. A ContentRegion object containing the title's contents.
""" """
def __init__(self): def __init__(self):
self.wad: WAD = WAD() self.wad: WAD = WAD()
self.cert_chain: CertificateChain = CertificateChain()
self.tmd: TMD = TMD() self.tmd: TMD = TMD()
self.ticket: Ticket = Ticket() self.ticket: Ticket = Ticket()
self.content: ContentRegion = ContentRegion() self.content: ContentRegion = ContentRegion()
@ -47,6 +51,9 @@ class Title:
# Create a new WAD object based on the WAD data provided. # Create a new WAD object based on the WAD data provided.
self.wad = WAD() self.wad = WAD()
self.wad.load(wad) self.wad.load(wad)
# Load the certificate chain.
self.cert_chain = CertificateChain()
self.cert_chain.load(self.wad.get_cert_data())
# Load the TMD. # Load the TMD.
self.tmd = TMD() self.tmd = TMD()
self.tmd.load(self.wad.get_tmd_data()) self.tmd.load(self.wad.get_tmd_data())
@ -75,6 +82,8 @@ class Title:
# Set WAD type to ib if the title being packed is boot2. # Set WAD type to ib if the title being packed is boot2.
if self.tmd.title_id == "0000000100000001": if self.tmd.title_id == "0000000100000001":
self.wad.wad_type = "ib" self.wad.wad_type = "ib"
# Dump the certificate chain and set it in the WAD.
self.wad.set_cert_data(self.cert_chain.dump())
# Dump the TMD and set it in the WAD. # Dump the TMD and set it in the WAD.
# This requires updating the content records and number of contents in the TMD first. # This requires updating the content records and number of contents in the TMD first.
self.tmd.content_records = self.content.content_records # This may not be needed because it's a ref already self.tmd.content_records = self.content.content_records # This may not be needed because it's a ref already
@ -87,6 +96,19 @@ class Title:
self.wad.set_content_data(content_data, content_size) self.wad.set_content_data(content_data, content_size)
return self.wad.dump() return self.wad.dump()
def load_cert_chain(self, cert_chain: bytes) -> None:
"""
Load an existing certificate chain into the title. Note that this will overwrite any existing certificate chain
data for this title.
Parameters
----------
cert_chain: bytes
The data for the certificate chain to load.
"""
self.cert_chain.load(cert_chain)
def load_tmd(self, tmd: bytes) -> None: def load_tmd(self, tmd: bytes) -> None:
""" """
Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title. Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title.
@ -94,9 +116,8 @@ class Title:
Parameters Parameters
---------- ----------
tmd : bytes tmd : bytes
The data for the WAD you wish to load. The data for the TMD to load.
""" """
# Load TMD.
self.tmd.load(tmd) self.tmd.load(tmd)
def load_ticket(self, ticket: bytes) -> None: def load_ticket(self, ticket: bytes) -> None:
@ -107,9 +128,8 @@ class Title:
Parameters Parameters
---------- ----------
ticket : bytes ticket : bytes
The data for the WAD you wish to load. The data for the Ticket to load.
""" """
# Load Ticket.
self.ticket.load(ticket) self.ticket.load(ticket)
def load_content_records(self) -> None: def load_content_records(self) -> None:

View File

@ -83,7 +83,7 @@ class TMD:
self.signature = tmd_data.read(256) self.signature = tmd_data.read(256)
# Signing certificate issuer. # Signing certificate issuer.
tmd_data.seek(0x140) tmd_data.seek(0x140)
self.signature_issuer = str(tmd_data.read(64).decode()) self.signature_issuer = str(tmd_data.read(64).replace(b'\x00', b'').decode())
# TMD version, seems to usually be 0, but I've seen references to other numbers. # TMD version, seems to usually be 0, but I've seen references to other numbers.
tmd_data.seek(0x180) tmd_data.seek(0x180)
self.tmd_version = int.from_bytes(tmd_data.read(1)) self.tmd_version = int.from_bytes(tmd_data.read(1))
@ -175,7 +175,10 @@ class TMD:
# Padding to 64 bytes. # Padding to 64 bytes.
tmd_data += b'\x00' * 60 tmd_data += b'\x00' * 60
# Signing certificate issuer. # Signing certificate issuer.
tmd_data += str.encode(self.signature_issuer) signature_issuer = self.signature_issuer.encode()
while len(signature_issuer) < 0x40:
signature_issuer += b'\x00'
tmd_data += signature_issuer
# TMD version. # TMD version.
tmd_data += int.to_bytes(self.tmd_version, 1) tmd_data += int.to_bytes(self.tmd_version, 1)
# Certificate Authority CRL version. # Certificate Authority CRL version.