mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-26 05:11:02 -04:00
Made wad.py able to export all data contained in a WAD
Also adjusted wad.py and tmd.py to both use byte streams so that the method of loading the WAD or TMD data is handled outside of the library. Comments are also more robust overall.
This commit is contained in:
parent
66e83ff3f8
commit
d7e45bb2a4
@ -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'
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user