diff --git a/src/libWiiPy/commonkeys.py b/src/libWiiPy/commonkeys.py index 6b8687e..f6eee50 100644 --- a/src/libWiiPy/commonkeys.py +++ b/src/libWiiPy/commonkeys.py @@ -1,4 +1,5 @@ -# "commonkeys.py" from libWiiPy by NinjaCheetah +# "commonkeys.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy default_key = 'ebe42a225e8593e448d9c5457381aaf7' korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e' diff --git a/src/libWiiPy/tmd.py b/src/libWiiPy/tmd.py index 69fee9a..31155b0 100644 --- a/src/libWiiPy/tmd.py +++ b/src/libWiiPy/tmd.py @@ -1,7 +1,9 @@ -# "tmd.py" from libWiiPy by NinjaCheetah +# "tmd.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy # # See https://wiibrew.org/wiki/Title_metadata for details about the TMD format +import io import binascii from dataclasses import dataclass from typing import List @@ -18,7 +20,7 @@ class ContentRecord: class TMD: - """Creates a TMD object that can be used to read all the data contained in a TMD.""" + """Creates a TMD object to parse a TMD file to retrieve information about a title.""" def __init__(self, tmd): self.tmd = tmd self.sig_type: int @@ -41,64 +43,64 @@ class TMD: self.boot_index: int self.content_record: List[ContentRecord] # Load data from TMD file - with open(tmd, "rb") as tmdfile: + with io.BytesIO(tmd) as tmddata: # Signing certificate issuer - tmdfile.seek(0x140) - self.issuer = tmdfile.read(64) + tmddata.seek(0x140) + self.issuer = tmddata.read(64) # TMD version, seems to usually be 0, but I've seen references to other numbers - tmdfile.seek(0x180) - self.version = int.from_bytes(tmdfile.read(1)) + tmddata.seek(0x180) + self.version = int.from_bytes(tmddata.read(1)) # TODO: label - tmdfile.seek(0x181) - self.ca_crl_version = tmdfile.read(1) + tmddata.seek(0x181) + self.ca_crl_version = tmddata.read(1) # TODO: label - tmdfile.seek(0x182) - self.signer_crl_version = tmdfile.read(1) + tmddata.seek(0x182) + self.signer_crl_version = tmddata.read(1) # If this is a vWii title or not - tmdfile.seek(0x183) - self.vwii = int.from_bytes(tmdfile.read(1)) + tmddata.seek(0x183) + self.vwii = int.from_bytes(tmddata.read(1)) # TID of the IOS to use for the title, set to 0 if this title is the IOS, set to boot2 version if boot2 - tmdfile.seek(0x184) - ios_version_bin = tmdfile.read(8) + tmddata.seek(0x184) + ios_version_bin = tmddata.read(8) ios_version_hex = binascii.hexlify(ios_version_bin) self.ios_tid = str(ios_version_hex.decode()) # Get IOS version based on TID self.ios_version = int(self.ios_tid[-2:], 16) # Title ID of the title - tmdfile.seek(0x18C) - title_id_bin = tmdfile.read(8) + tmddata.seek(0x18C) + title_id_bin = tmddata.read(8) title_id_hex = binascii.hexlify(title_id_bin) self.title_id = str(title_id_hex.decode()) # Type of content - tmdfile.seek(0x194) - content_type_bin = tmdfile.read(4) + tmddata.seek(0x194) + content_type_bin = tmddata.read(4) content_type_hex = binascii.hexlify(content_type_bin) self.content_type = str(content_type_hex.decode()) # Publisher of the title - tmdfile.seek(0x198) - self.group_id = tmdfile.read(2) + tmddata.seek(0x198) + self.group_id = tmddata.read(2) # Region of the title, 0 = JAP, 1 = USA, 2 = EUR, 3 = NONE, 4 = KOR - tmdfile.seek(0x19C) - region_hex = tmdfile.read(2) + tmddata.seek(0x19C) + region_hex = tmddata.read(2) self.region = int.from_bytes(region_hex) # TODO: figure this one out - tmdfile.seek(0x19E) - self.ratings = tmdfile.read(16) + tmddata.seek(0x19E) + self.ratings = tmddata.read(16) # Access rights of the title; DVD-video access and AHBPROT - tmdfile.seek(0x1D8) - self.access_rights = tmdfile.read(4) + tmddata.seek(0x1D8) + self.access_rights = tmddata.read(4) # Calculate the version number by multiplying 0x1DC by 256 and adding 0x1DD - tmdfile.seek(0x1DC) - title_version_high = int.from_bytes(tmdfile.read(1)) * 256 - tmdfile.seek(0x1DD) - title_version_low = int.from_bytes(tmdfile.read(1)) + tmddata.seek(0x1DC) + title_version_high = int.from_bytes(tmddata.read(1)) * 256 + tmddata.seek(0x1DD) + title_version_low = int.from_bytes(tmddata.read(1)) self.title_version = title_version_high + title_version_low # The number of contents listed in the TMD - tmdfile.seek(0x1DE) - self.num_contents = int.from_bytes(tmdfile.read(2)) + tmddata.seek(0x1DE) + self.num_contents = int.from_bytes(tmddata.read(2)) # Content index in content list that contains the boot file - tmdfile.seek(0x1E0) - self.boot_index = tmdfile.read(2) + tmddata.seek(0x1E0) + self.boot_index = tmddata.read(2) def get_title_id(self): """Returns the TID of the TMD's associated title.""" diff --git a/src/libWiiPy/wad.py b/src/libWiiPy/wad.py index 3dfcc07..19b52d8 100644 --- a/src/libWiiPy/wad.py +++ b/src/libWiiPy/wad.py @@ -1,105 +1,132 @@ -# Project: libWiiPy by NinjaCheetah -# File: wad.py by rmc +# "wad.py" from libWiiPy by NinjaCheetah & Contributors +# https://github.com/NinjaCheetah/libWiiPy # # See https://wiibrew.org/wiki/WAD_files for details about the WAD format +import io import binascii -from dataclasses import dataclass -from typing import List -@dataclass -class ContentRecord: - """Creates a content record object that contains the details of a content contained in a title.""" - cid: int # Content ID - index: int # Index in the list of contents - content_type: int # normal: 0x0001; dlc: 0x4001; shared: 0x8001 - content_size: int - content_hash: bytearray # SHA1 hash content - - -class wadHeader: - """Break down 32 byte WAD header.""" +class WAD: + """Creates a WAD object to parse the header of a WAD file and retrieve the data contained in it.""" def __init__(self, wad): self.wad = wad self.wad_hdr_size: int self.wad_type: str self.wad_version: int - # Sizes + # === Sizes === self.wad_cert_size: int self.wad_crl_size: int self.wad_tik_size: int self.wad_tmd_size: int - self.wad_app_size: int # Note that this is the size of the app region. This is each individual app file bundled together. + # This is the size of the content region, which contains all app files combined. + self.wad_content_size: int self.wad_meta_size: int - # Offsets + # === Offsets === self.wad_cert_offset: int self.wad_crl_offset: int self.wad_tik_offset: int self.wad_tmd_offset: int - self.wad_app_offset: int + self.wad_content_offset: int self.wad_meta_offset: int - #self.content_record: List[ContentRecord] - # Load header data from WAD file - with open(wad, "rb") as wadfile: - #==================================================================================== - # Get the sizes of each data region contained within the WAD. Sorry for mid code! - #==================================================================================== - # Header length. Always seems to be 32 so we'll ignore it for now. - wadfile.seek(0x0) - self.wad_hdr_size = wadfile.read(4) - # WAD type - wadfile.seek(0x04) - self.wad_type = str(wadfile.read(2).decode()) - # WAD version - wadfile.seek(0x06) - self.wad_version = wadfile.read(2) - # WAD cert size - wadfile.seek(0x08) - self.wad_cert_size = wadfile.read(4) - # WAD crl size - wadfile.seek(0x0c) - self.wad_crl_size = wadfile.read(4) - # WAD ticket size - wadfile.seek(0x10) - self.wad_tik_size = wadfile.read(4) - # WAD TMD size - wadfile.seek(0x14) - self.wad_tmd_size = wadfile.read(4) - # WAD app size - wadfile.seek(0x18) - self.wad_app_size = wadfile.read(4) - # Publisher of the title - wadfile.seek(0x1c) - self.wad_meta_size = wadfile.read(4) - #==================================================================================== - # Calculate file offsets from sizes - #==================================================================================== + # Load header data from WAD stream + with io.BytesIO(wad) as waddata: + # ==================================================================================== + # Get the sizes of each data region contained within the WAD. + # ==================================================================================== + # Header length, which will always be 64 bytes, as it is padded out if it is shorter. + self.wad_hdr_size = 64 + # WAD type, denoting whether this WAD contains boot2 ("ib"), or anything else ("Is"). + waddata.seek(0x04) + self.wad_type = str(waddata.read(2).decode()) + # WAD version, this is always 0. + waddata.seek(0x06) + self.wad_version = waddata.read(2) + # WAD cert size. + waddata.seek(0x08) + self.wad_cert_size = int(binascii.hexlify(waddata.read(4)), 16) + # WAD crl size. + waddata.seek(0x0c) + self.wad_crl_size = int(binascii.hexlify(waddata.read(4)), 16) + # WAD ticket size. + waddata.seek(0x10) + self.wad_tik_size = int(binascii.hexlify(waddata.read(4)), 16) + # WAD TMD size. + waddata.seek(0x14) + self.wad_tmd_size = int(binascii.hexlify(waddata.read(4)), 16) + # WAD content size. + waddata.seek(0x18) + self.wad_content_size = int(binascii.hexlify(waddata.read(4)), 16) + # Publisher of the title contained in the WAD. + waddata.seek(0x1c) + self.wad_meta_size = int(binascii.hexlify(waddata.read(4)), 16) + # ==================================================================================== + # Calculate file offsets from sizes. Every section of the WAD is padded out to a multiple of 0x40. + # ==================================================================================== self.wad_cert_offset = self.wad_hdr_size - # I've never seen crl used (don't even know what it's for) but still calculating in case... - self.wad_crl_offset = self.wad_cert_offset + self.wad_cert_size - self.wad_tik_offset = self.wad_crl_offset + self.wad_crl_size - self.wad_tmd_offset = self.wad_tik_offset + self.wad_tik_size - self.wad_app_offset = self.wad_tmd_offset + self.wad_tmd_size - # Same with meta. If private Nintendo tools calculate these then maaaaaybe we should too. - self.wad_meta_offset = self.wad_app_offset + self.wad_app_size + # crl isn't ever used, however an entry for its size exists in the header, so its calculated just in case. + self.wad_crl_offset = int(64 * round((self.wad_cert_offset + self.wad_cert_size) / 64)) + self.wad_tik_offset = int(64 * round((self.wad_crl_offset + self.wad_crl_size) / 64)) + self.wad_tmd_offset = int(64 * round((self.wad_tik_offset + self.wad_tik_size) / 64)) + self.wad_content_offset = int(64 * round((self.wad_tmd_offset + self.wad_tmd_size) / 64)) + # meta is also never used, but Nintendo's tools calculate it so we should too. + self.wad_meta_offset = int(64 * round((self.wad_content_offset + self.wad_content_size) / 64)) def get_cert_region(self): - """Returns the offset and size for the cert.""" + """Returns the offset and size for the cert data.""" return self.wad_cert_offset, self.wad_cert_size + def get_crl_region(self): + """Returns the offset and size for the crl data.""" + return self.wad_crl_offset, self.wad_crl_size + def get_ticket_region(self): - """Returns the offset and size for the ticket.""" + """Returns the offset and size for the ticket data.""" return self.wad_tik_offset, self.wad_tik_size def get_tmd_region(self): - """Returns the offset and size for the TMD.""" + """Returns the offset and size for the TMD data.""" return self.wad_tmd_offset, self.wad_tmd_size - def get_app_region(self): - """Returns the offset and size for the app.""" - return self.wad_app_offset, self.wad_tmd_size + def get_content_region(self): + """Returns the offset and size for the content of the WAD.""" + return self.wad_content_offset, self.wad_tmd_size def get_wad_type(self): """Returns the type of the WAD. This is 'Is' unless the WAD contains boot2 where it is 'ib'.""" return self.wad_type + + def get_cert_data(self): + """Returns the certificate data from the WAD.""" + waddata = io.BytesIO(self.wad) + waddata.seek(self.wad_cert_offset) + cert_data = waddata.read(self.wad_cert_size) + return cert_data + + def get_crl_data(self): + """Returns the crl data from the WAD, if it exists.""" + waddata = io.BytesIO(self.wad) + waddata.seek(self.wad_crl_offset) + crl_data = waddata.read(self.wad_crl_size) + return crl_data + + def get_ticket_data(self): + """Returns the ticket data from the WAD.""" + waddata = io.BytesIO(self.wad) + waddata.seek(self.wad_tik_offset) + ticket_data = waddata.read(self.wad_tik_size) + return ticket_data + + def get_tmd_data(self): + """Returns the TMD data from the WAD.""" + waddata = io.BytesIO(self.wad) + waddata.seek(self.wad_tmd_offset) + tmd_data = waddata.read(self.wad_tmd_size) + return tmd_data + + def get_content_data(self): + """Returns the content of the WAD.""" + waddata = io.BytesIO(self.wad) + waddata.seek(self.wad_content_offset) + content_data = waddata.read(self.wad_content_size) + return content_data