mirror of
				https://github.com/NinjaCheetah/libWiiPy.git
				synced 2025-11-03 16:06:19 -05:00 
			
		
		
		
	Merge pull request #4 from NinjaCheetah/breakdown_wad
Add wad.py, which handles the data contained in a WAD file
This commit is contained in:
		
						commit
						2bacc474f4
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -163,3 +163,4 @@ cython_debug/
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Allows me to keep TMD files in my repository folder for testing without accidentally publishing them
 | 
					# Allows me to keep TMD files in my repository folder for testing without accidentally publishing them
 | 
				
			||||||
*.tmd
 | 
					*.tmd
 | 
				
			||||||
 | 
					*.wad
 | 
				
			||||||
@ -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'
 | 
					default_key = 'ebe42a225e8593e448d9c5457381aaf7'
 | 
				
			||||||
korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e'
 | 
					korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e'
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +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
 | 
					# See https://wiibrew.org/wiki/Title_metadata for details about the TMD format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
import binascii
 | 
					import binascii
 | 
				
			||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
from typing import List
 | 
					from typing import List
 | 
				
			||||||
@ -17,7 +20,7 @@ class ContentRecord:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TMD:
 | 
					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):
 | 
					    def __init__(self, tmd):
 | 
				
			||||||
        self.tmd = tmd
 | 
					        self.tmd = tmd
 | 
				
			||||||
        self.sig_type: int
 | 
					        self.sig_type: int
 | 
				
			||||||
@ -40,64 +43,64 @@ class TMD:
 | 
				
			|||||||
        self.boot_index: int
 | 
					        self.boot_index: int
 | 
				
			||||||
        self.content_record: List[ContentRecord]
 | 
					        self.content_record: List[ContentRecord]
 | 
				
			||||||
        # Load data from TMD file
 | 
					        # Load data from TMD file
 | 
				
			||||||
        with open(tmd, "rb") as tmdfile:
 | 
					        with io.BytesIO(tmd) as tmddata:
 | 
				
			||||||
            # Signing certificate issuer
 | 
					            # Signing certificate issuer
 | 
				
			||||||
            tmdfile.seek(0x140)
 | 
					            tmddata.seek(0x140)
 | 
				
			||||||
            self.issuer = tmdfile.read(64)
 | 
					            self.issuer = tmddata.read(64)
 | 
				
			||||||
            # 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
 | 
				
			||||||
            tmdfile.seek(0x180)
 | 
					            tmddata.seek(0x180)
 | 
				
			||||||
            self.version = int.from_bytes(tmdfile.read(1))
 | 
					            self.version = int.from_bytes(tmddata.read(1))
 | 
				
			||||||
            # TODO: label
 | 
					            # TODO: label
 | 
				
			||||||
            tmdfile.seek(0x181)
 | 
					            tmddata.seek(0x181)
 | 
				
			||||||
            self.ca_crl_version = tmdfile.read(1)
 | 
					            self.ca_crl_version = tmddata.read(1)
 | 
				
			||||||
            # TODO: label
 | 
					            # TODO: label
 | 
				
			||||||
            tmdfile.seek(0x182)
 | 
					            tmddata.seek(0x182)
 | 
				
			||||||
            self.signer_crl_version = tmdfile.read(1)
 | 
					            self.signer_crl_version = tmddata.read(1)
 | 
				
			||||||
            # If this is a vWii title or not
 | 
					            # If this is a vWii title or not
 | 
				
			||||||
            tmdfile.seek(0x183)
 | 
					            tmddata.seek(0x183)
 | 
				
			||||||
            self.vwii = int.from_bytes(tmdfile.read(1))
 | 
					            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
 | 
					            # 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)
 | 
					            tmddata.seek(0x184)
 | 
				
			||||||
            ios_version_bin = tmdfile.read(8)
 | 
					            ios_version_bin = tmddata.read(8)
 | 
				
			||||||
            ios_version_hex = binascii.hexlify(ios_version_bin)
 | 
					            ios_version_hex = binascii.hexlify(ios_version_bin)
 | 
				
			||||||
            self.ios_tid = str(ios_version_hex.decode())
 | 
					            self.ios_tid = str(ios_version_hex.decode())
 | 
				
			||||||
            # Get IOS version based on TID
 | 
					            # Get IOS version based on TID
 | 
				
			||||||
            self.ios_version = int(self.ios_tid[-2:], 16)
 | 
					            self.ios_version = int(self.ios_tid[-2:], 16)
 | 
				
			||||||
            # Title ID of the title
 | 
					            # Title ID of the title
 | 
				
			||||||
            tmdfile.seek(0x18C)
 | 
					            tmddata.seek(0x18C)
 | 
				
			||||||
            title_id_bin = tmdfile.read(8)
 | 
					            title_id_bin = tmddata.read(8)
 | 
				
			||||||
            title_id_hex = binascii.hexlify(title_id_bin)
 | 
					            title_id_hex = binascii.hexlify(title_id_bin)
 | 
				
			||||||
            self.title_id = str(title_id_hex.decode())
 | 
					            self.title_id = str(title_id_hex.decode())
 | 
				
			||||||
            # Type of content
 | 
					            # Type of content
 | 
				
			||||||
            tmdfile.seek(0x194)
 | 
					            tmddata.seek(0x194)
 | 
				
			||||||
            content_type_bin = tmdfile.read(4)
 | 
					            content_type_bin = tmddata.read(4)
 | 
				
			||||||
            content_type_hex = binascii.hexlify(content_type_bin)
 | 
					            content_type_hex = binascii.hexlify(content_type_bin)
 | 
				
			||||||
            self.content_type = str(content_type_hex.decode())
 | 
					            self.content_type = str(content_type_hex.decode())
 | 
				
			||||||
            # Publisher of the title
 | 
					            # Publisher of the title
 | 
				
			||||||
            tmdfile.seek(0x198)
 | 
					            tmddata.seek(0x198)
 | 
				
			||||||
            self.group_id = tmdfile.read(2)
 | 
					            self.group_id = tmddata.read(2)
 | 
				
			||||||
            # Region of the title, 0 = JAP, 1 = USA, 2 = EUR, 3 = NONE, 4 = KOR
 | 
					            # Region of the title, 0 = JAP, 1 = USA, 2 = EUR, 3 = NONE, 4 = KOR
 | 
				
			||||||
            tmdfile.seek(0x19C)
 | 
					            tmddata.seek(0x19C)
 | 
				
			||||||
            region_hex = tmdfile.read(2)
 | 
					            region_hex = tmddata.read(2)
 | 
				
			||||||
            self.region = int.from_bytes(region_hex)
 | 
					            self.region = int.from_bytes(region_hex)
 | 
				
			||||||
            # TODO: figure this one out
 | 
					            # TODO: figure this one out
 | 
				
			||||||
            tmdfile.seek(0x19E)
 | 
					            tmddata.seek(0x19E)
 | 
				
			||||||
            self.ratings = tmdfile.read(16)
 | 
					            self.ratings = tmddata.read(16)
 | 
				
			||||||
            # Access rights of the title; DVD-video access and AHBPROT
 | 
					            # Access rights of the title; DVD-video access and AHBPROT
 | 
				
			||||||
            tmdfile.seek(0x1D8)
 | 
					            tmddata.seek(0x1D8)
 | 
				
			||||||
            self.access_rights = tmdfile.read(4)
 | 
					            self.access_rights = tmddata.read(4)
 | 
				
			||||||
            # Calculate the version number by multiplying 0x1DC by 256 and adding 0x1DD
 | 
					            # Calculate the version number by multiplying 0x1DC by 256 and adding 0x1DD
 | 
				
			||||||
            tmdfile.seek(0x1DC)
 | 
					            tmddata.seek(0x1DC)
 | 
				
			||||||
            title_version_high = int.from_bytes(tmdfile.read(1)) * 256
 | 
					            title_version_high = int.from_bytes(tmddata.read(1)) * 256
 | 
				
			||||||
            tmdfile.seek(0x1DD)
 | 
					            tmddata.seek(0x1DD)
 | 
				
			||||||
            title_version_low = int.from_bytes(tmdfile.read(1))
 | 
					            title_version_low = int.from_bytes(tmddata.read(1))
 | 
				
			||||||
            self.title_version = title_version_high + title_version_low
 | 
					            self.title_version = title_version_high + title_version_low
 | 
				
			||||||
            # The number of contents listed in the TMD
 | 
					            # The number of contents listed in the TMD
 | 
				
			||||||
            tmdfile.seek(0x1DE)
 | 
					            tmddata.seek(0x1DE)
 | 
				
			||||||
            self.num_contents = int.from_bytes(tmdfile.read(2))
 | 
					            self.num_contents = int.from_bytes(tmddata.read(2))
 | 
				
			||||||
            # Content index in content list that contains the boot file
 | 
					            # Content index in content list that contains the boot file
 | 
				
			||||||
            tmdfile.seek(0x1E0)
 | 
					            tmddata.seek(0x1E0)
 | 
				
			||||||
            self.boot_index = tmdfile.read(2)
 | 
					            self.boot_index = tmddata.read(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_title_id(self):
 | 
					    def get_title_id(self):
 | 
				
			||||||
        """Returns the TID of the TMD's associated title."""
 | 
					        """Returns the TID of the TMD's associated title."""
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										132
									
								
								src/libWiiPy/wad.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/libWiiPy/wad.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					# "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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 ===
 | 
				
			||||||
 | 
					        self.wad_cert_size: int
 | 
				
			||||||
 | 
					        self.wad_crl_size: int
 | 
				
			||||||
 | 
					        self.wad_tik_size: int
 | 
				
			||||||
 | 
					        self.wad_tmd_size: int
 | 
				
			||||||
 | 
					        # 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 ===
 | 
				
			||||||
 | 
					        self.wad_cert_offset: int
 | 
				
			||||||
 | 
					        self.wad_crl_offset: int
 | 
				
			||||||
 | 
					        self.wad_tik_offset: int
 | 
				
			||||||
 | 
					        self.wad_tmd_offset: int
 | 
				
			||||||
 | 
					        self.wad_content_offset: int
 | 
				
			||||||
 | 
					        self.wad_meta_offset: int
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					            # 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 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 data."""
 | 
				
			||||||
 | 
					        return self.wad_tik_offset, self.wad_tik_size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_tmd_region(self):
 | 
				
			||||||
 | 
					        """Returns the offset and size for the TMD data."""
 | 
				
			||||||
 | 
					        return self.wad_tmd_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
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user