Add support for extracting/packing/otherwise handling dev WADs

This commit is contained in:
Campbell 2024-10-13 21:39:52 -04:00
parent c604c195d2
commit 9ae059b797
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
5 changed files with 33 additions and 10 deletions

View File

@ -1,6 +1,6 @@
[project]
name = "libWiiPy"
version = "0.5.1"
version = "0.5.2"
authors = [
{ name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" },
{ name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" }

View File

@ -7,11 +7,14 @@ common_key = 'ebe42a225e8593e448d9c5457381aaf7'
korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e'
vwii_key = '30bfc76e7c19afbb23163330ced7c28d'
development_key = 'a1604a6a7123b529ae8bec32c816fcaa'
def get_common_key(common_key_index) -> bytes:
def get_common_key(common_key_index, dev=False) -> bytes:
"""
Gets the specified Wii Common Key based on the index provided. If an invalid common key index is provided, this
function falls back on always returning key 0 (the Common Key).
function falls back on always returning key 0 (the Common Key). If the kwarg "dev" is specified, then key 0 will
point to the development common key rather than the retail one. Keys 1 and 2 are unaffected by this argument.
Possible values for common_key_index: 0: Common Key, 1: Korean Key, 2: vWii Key
@ -19,6 +22,8 @@ def get_common_key(common_key_index) -> bytes:
----------
common_key_index : int
The index of the common key to be returned.
dev : bool
If the dev keys should be used in place of the retail keys. Only affects key 0.
Returns
-------
@ -27,7 +32,10 @@ def get_common_key(common_key_index) -> bytes:
"""
match common_key_index:
case 0:
common_key_bin = binascii.unhexlify(common_key)
if dev:
common_key_bin = binascii.unhexlify(development_key)
else:
common_key_bin = binascii.unhexlify(common_key)
case 1:
common_key_bin = binascii.unhexlify(korean_key)
case 2:

View File

@ -30,7 +30,7 @@ def _convert_tid_to_iv(title_id: str | bytes) -> bytes:
return title_key_iv
def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: bytes | str) -> bytes:
def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: bytes | str, dev=False) -> bytes:
"""
Gets the decrypted version of the encrypted Title Key provided.
@ -44,6 +44,8 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt
The index of the common key used to encrypt the Title Key.
title_id : bytes, str
The Title ID of the title that the key is for.
dev : bool
Whether the Title Key is encrypted with the development key or not.
Returns
-------
@ -51,7 +53,7 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt
The decrypted Title Key.
"""
# Load the correct common key for the title.
common_key = get_common_key(common_key_index)
common_key = get_common_key(common_key_index, dev)
# Convert the IV into the correct format based on the type provided.
title_key_iv = _convert_tid_to_iv(title_id)
# The IV will always be in the same format by this point, so add the last 8 bytes.
@ -63,7 +65,7 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt
return title_key
def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: bytes | str) -> bytes:
def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: bytes | str, dev=False) -> bytes:
"""
Encrypts the provided Title Key with the selected common key.
@ -77,6 +79,8 @@ def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: byt
The index of the common key used to encrypt the Title Key.
title_id : bytes, str
The Title ID of the title that the key is for.
dev : bool
Whether the Title Key is encrypted with the development key or not.
Returns
-------
@ -84,7 +88,7 @@ def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: byt
An encrypted Title Key.
"""
# Load the correct common key for the title.
common_key = get_common_key(common_key_index)
common_key = get_common_key(common_key_index, dev)
# Convert the IV into the correct format based on the type provided.
title_key_iv = _convert_tid_to_iv(title_id)
# The IV will always be in the same format by this point, so add the last 8 bytes.

View File

@ -40,6 +40,9 @@ class Ticket:
Attributes
----------
is_dev : bool
Whether this Ticket is signed for development or not, and whether the Title Key is encrypted for development
or not.
signature : bytes
The signature applied to the ticket.
ticket_version : int
@ -56,6 +59,8 @@ class Ticket:
The index of the common key required to decrypt this ticket's Title Key.
"""
def __init__(self):
# If this is a dev ticket
self.is_dev: bool = False # Defaults to false, set to true during load if this ticket is using dev certs.
# Signature blob header
self.signature_type: bytes = b'' # Type of signature, always 0x10001 for RSA-2048
self.signature: bytes = b'' # Actual signature data
@ -155,6 +160,11 @@ class Ticket:
limit_type = int.from_bytes(ticket_data.read(4))
limit_value = int.from_bytes(ticket_data.read(4))
self.title_limits_list.append(_TitleLimit(limit_type, limit_value))
# Check certs to see if this is a retail or dev ticket. Treats unknown certs as being retail for now.
if self.signature_issuer.find("Root-CA00000002-XS00000006") != -1:
self.is_dev = True
else:
self.is_dev = False
def dump(self) -> bytes:
"""
@ -315,7 +325,7 @@ class Ticket:
bytes
The decrypted title key.
"""
title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id)
title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id, self.is_dev)
return title_key
def set_title_id(self, title_id) -> None:

View File

@ -137,7 +137,8 @@ class Title:
self.tmd.set_title_id(title_id)
title_key_decrypted = self.ticket.get_title_key()
self.ticket.set_title_id(title_id)
title_key_encrypted = encrypt_title_key(title_key_decrypted, self.ticket.common_key_index, title_id)
title_key_encrypted = encrypt_title_key(title_key_decrypted, self.ticket.common_key_index, title_id,
self.ticket.is_dev)
self.ticket.title_key_enc = title_key_encrypted
def set_title_version(self, title_version: str | int) -> None: