mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-06-07 06:01:00 -04:00
Compare commits
4 Commits
3a44eaf2cf
...
04d17a58d2
Author | SHA1 | Date | |
---|---|---|---|
04d17a58d2 | |||
aa9e8fb4ea | |||
8a15b1e82e | |||
ece19177c4 |
@ -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:
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user