mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-06-07 14:01:01 -04:00
Compare commits
4 Commits
3a44eaf2cf
...
04d17a58d2
Author | SHA1 | Date | |
---|---|---|---|
04d17a58d2 | |||
aa9e8fb4ea | |||
8a15b1e82e | |||
ece19177c4 |
@ -43,8 +43,22 @@ class Certificate:
|
||||
|
||||
Attributes
|
||||
----------
|
||||
something : bool
|
||||
I'm a placeholder attribute for later.
|
||||
type: CertificateType
|
||||
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):
|
||||
self.type: CertificateType | None = None
|
||||
@ -198,7 +212,8 @@ class CertificateChain:
|
||||
|
||||
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
|
||||
----------
|
||||
@ -210,23 +225,44 @@ def verify_ca_cert(ca_cert: Certificate) -> bool:
|
||||
bool
|
||||
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'
|
||||
b'\xd3\xd0~\xfc\x85 i\xb5\xde\x9b\xb9Q\xa8\xbc\x90\xa2D\x92m7\x92\x95\xae\x946\xaa\xa6\xa3\x02Q'
|
||||
b'\x0c{\x1d\xed\xd5\xfb \x86\x9d\x7f0\x16\xf6\xbee\xd3\x83\xa1m\xb32\x1b\x955\x18\x90\xb1p\x02'
|
||||
b'\x93~\xe1\x93\xf5~\x99\xa2GN\x9d8$\xc7\xae\xe3\x85A\xf5g\xe7Q\x8cz\x0e8\xe7\xeb\xafA\x19\x1b'
|
||||
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'\x8b\x08\xc7\xf6\xb7\xe5k\x02\xb3\xe8\xfe\x0c\x9d\x85\x9c\xb8\xb6\x82#\xb8\xab\'\xee_e8\x07'
|
||||
b'\x8b-\xb9\x1e*\x15>\x85\x81\x80r\xa2;m\xd92\x81\x05Oo\xb0\xf6\xf5\xad(>\xca\x0bz\xf3TU\xe0='
|
||||
b'\xa7\xb6\x83&\xf3\xec\x83J\xf3\x14\x04\x8a\xc6\xdf \xd2\x85\x08g<\xabb\xa2\xc7\xbc\x13\x1aS>'
|
||||
b'\x0bf\x80k\x1c0fK7#1\xbd\xc4\xb0\xca\xd8\xd1\x1e\xe7\xbb\xd9(UH\xaa\xec\x1ff\xe8!\xb3\xc8'
|
||||
b'\xa0Gi\x00\xc5\xe6\x88\xe8\x0c\xce<a\xd6\x9c\xbb\xa17\xc6`Ozr\xdd\x8c{>=Q)\r\xaajY{\x08\x1f'
|
||||
b'\x9d63\xa3Fz5a\t\xac\xa7\xdd}./\xb2\xc1\xae\xb8\xe2\x0fH\x92\xd8\xb9\xf8\xb4oN<\x11\xf4\xf4}'
|
||||
b'\x8bu}\xfe\xfe\xa3\x89\x9c3Y\\^\xfd\xeb\xcb\xab\xe8A>:\x9a\x80<i5n\xb2\xb2\xad\\\xc4\xc8XE^'
|
||||
b'\xf5\xf7\xb3\x06D\xb4|d\x06\x8c\xdf\x80\x9fv\x02Z-\xb4F\xe0=|\xf6/4\xe7\x02E{\x02\xa4\xcf]'
|
||||
b'\x9d\xd5<\xa5:|\xa6)x\x8cg\xca\x08\xbf\xec\xcaC\xa9W\xad\x16\xc9N\x1c\xd8u\xca\x10}\xce~\x01'
|
||||
b'\x18\xf0\xdfk\xfe\xe5\x1d\xdb\xd9\x91\xc2n`\xcdHX\xaaY,\x82\x00u\xf2\x9fRl\x91|o\xe5@>\xa7'
|
||||
b'\xd4\xa5\x0c\xec;s\x84\xde\x88n\x82\xd2\xebMNB\xb5\xf2\xb1I\xa8\x1e\xa7\xceqD\xdc)\x94\xcf'
|
||||
b'\xc4N\x1f\x91\xcb\xd4\x95')
|
||||
if ca_cert.issuer != "Root" or ca_cert.child_name.find("CA") == -1:
|
||||
raise ValueError("The provided certificate is not a CA certificate!")
|
||||
if ca_cert.child_name == "CA00000001":
|
||||
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\xd3\xd0~'
|
||||
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'\xfb \x86\x9d\x7f0\x16\xf6\xbee\xd3\x83\xa1m\xb32\x1b\x955\x18\x90\xb1p\x02\x93~\xe1\x93\xf5~\x99\xa2GN'
|
||||
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'\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'\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'\xf5\xad(>\xca\x0bz\xf3TU\xe0=\xa7\xb6\x83&\xf3\xec\x83J\xf3\x14\x04\x8a\xc6\xdf \xd2\x85\x08g<\xabb\xa2'
|
||||
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'\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'\xa3Fz5a\t\xac\xa7\xdd}./\xb2\xc1\xae\xb8\xe2\x0fH\x92\xd8\xb9\xf8\xb4oN<\x11\xf4\xf4}\x8bu}\xfe\xfe\xa3'
|
||||
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'\xdf\x80\x9fv\x02Z-\xb4F\xe0=|\xf6/4\xe7\x02E{\x02\xa4\xcf]\x9d\xd5<\xa5:|\xa6)x\x8cg\xca\x08\xbf\xec'
|
||||
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
|
||||
cert_hash = SHA1.new(ca_cert.dump()[576:])
|
||||
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
|
||||
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:])
|
||||
public_key = RSA.construct((ca_cert.pub_key_modulus, ca_cert.pub_key_exponent))
|
||||
try:
|
||||
@ -278,6 +320,10 @@ def verify_tmd_sig(tmd_cert: Certificate, tmd: TMD) -> bool:
|
||||
bool
|
||||
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:])
|
||||
public_key = RSA.construct((tmd_cert.pub_key_modulus, tmd_cert.pub_key_exponent))
|
||||
try:
|
||||
@ -303,6 +349,10 @@ def verify_ticket_sig(ticket_cert: Certificate, ticket: Ticket) -> bool:
|
||||
bool
|
||||
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:])
|
||||
public_key = RSA.construct((ticket_cert.pub_key_modulus, ticket_cert.pub_key_exponent))
|
||||
try:
|
||||
|
@ -40,10 +40,10 @@ def download_title(title_id: str, title_version: int = None, wiiu_endpoint: bool
|
||||
"""
|
||||
# First, create the new 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_ticket(download_ticket(title_id, wiiu_endpoint, endpoint_override))
|
||||
title.wad.set_cert_data(download_cert(wiiu_endpoint, endpoint_override))
|
||||
# Download all contents
|
||||
title.load_content_records()
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
----------
|
||||
@ -163,7 +163,7 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
|
||||
bytes
|
||||
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:
|
||||
endpoint_url = _validate_endpoint(endpoint_override)
|
||||
else:
|
||||
@ -175,18 +175,18 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
|
||||
cetk_url = endpoint_url + "0000000100000002/cetk"
|
||||
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
|
||||
# Assemble the certificate.
|
||||
cert = b''
|
||||
# Assemble the certificate chain.
|
||||
cert_chain = b''
|
||||
# Certificate Authority data.
|
||||
cert += cetk[0x2A4 + 768:]
|
||||
# Certificate Policy data.
|
||||
cert += tmd[0x328:0x328 + 768]
|
||||
# XS data.
|
||||
cert += cetk[0x2A4:0x2A4 + 768]
|
||||
# Since the cert is always the same, check the hash to make sure nothing went wildly wrong.
|
||||
if hashlib.sha1(cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
|
||||
cert_chain += cetk[0x2A4 + 768:]
|
||||
# Certificate Policy (TMD certificate) data.
|
||||
cert_chain += tmd[0x328:0x328 + 768]
|
||||
# XS (Ticket certificate) data.
|
||||
cert_chain += cetk[0x2A4:0x2A4 + 768]
|
||||
# Since the cert chain is always the same, check the hash to make sure nothing went wildly wrong.
|
||||
if hashlib.sha1(cert_chain).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
|
||||
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,
|
||||
|
@ -107,7 +107,7 @@ class Ticket:
|
||||
self.signature = ticket_data.read(256)
|
||||
# Signature issuer.
|
||||
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.
|
||||
ticket_data.seek(0x180)
|
||||
self.ecdh_data = ticket_data.read(60)
|
||||
@ -182,7 +182,10 @@ class Ticket:
|
||||
# Padding to 64 bytes.
|
||||
ticket_data += b'\x00' * 60
|
||||
# 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.
|
||||
ticket_data += self.ecdh_data
|
||||
# Ticket version.
|
||||
|
@ -4,6 +4,7 @@
|
||||
# See https://wiibrew.org/wiki/Title for details about how titles are formatted
|
||||
|
||||
import math
|
||||
from .cert import CertificateChain
|
||||
from .content import ContentRegion
|
||||
from .ticket import Ticket
|
||||
from .tmd import TMD
|
||||
@ -19,17 +20,20 @@ class Title:
|
||||
|
||||
Attributes
|
||||
----------
|
||||
wad : WAD
|
||||
wad: WAD
|
||||
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.
|
||||
ticket : Ticket
|
||||
ticket: Ticket
|
||||
A Ticket object of the title's Ticket.
|
||||
content: ContentRegion
|
||||
A ContentRegion object containing the title's contents.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.wad: WAD = WAD()
|
||||
self.cert_chain: CertificateChain = CertificateChain()
|
||||
self.tmd: TMD = TMD()
|
||||
self.ticket: Ticket = Ticket()
|
||||
self.content: ContentRegion = ContentRegion()
|
||||
@ -47,6 +51,9 @@ class Title:
|
||||
# Create a new WAD object based on the WAD data provided.
|
||||
self.wad = 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.
|
||||
self.tmd = TMD()
|
||||
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.
|
||||
if self.tmd.title_id == "0000000100000001":
|
||||
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.
|
||||
# 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
|
||||
@ -87,6 +96,19 @@ class Title:
|
||||
self.wad.set_content_data(content_data, content_size)
|
||||
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:
|
||||
"""
|
||||
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
|
||||
----------
|
||||
tmd : bytes
|
||||
The data for the WAD you wish to load.
|
||||
The data for the TMD to load.
|
||||
"""
|
||||
# Load TMD.
|
||||
self.tmd.load(tmd)
|
||||
|
||||
def load_ticket(self, ticket: bytes) -> None:
|
||||
@ -107,9 +128,8 @@ class Title:
|
||||
Parameters
|
||||
----------
|
||||
ticket : bytes
|
||||
The data for the WAD you wish to load.
|
||||
The data for the Ticket to load.
|
||||
"""
|
||||
# Load Ticket.
|
||||
self.ticket.load(ticket)
|
||||
|
||||
def load_content_records(self) -> None:
|
||||
|
@ -83,7 +83,7 @@ class TMD:
|
||||
self.signature = tmd_data.read(256)
|
||||
# Signing certificate issuer.
|
||||
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_data.seek(0x180)
|
||||
self.tmd_version = int.from_bytes(tmd_data.read(1))
|
||||
@ -175,7 +175,10 @@ class TMD:
|
||||
# Padding to 64 bytes.
|
||||
tmd_data += b'\x00' * 60
|
||||
# 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_data += int.to_bytes(self.tmd_version, 1)
|
||||
# Certificate Authority CRL version.
|
||||
|
Loading…
x
Reference in New Issue
Block a user