Add preliminary support for parsing 00000000.app

New module banner.py offers classes for IMD5 and IMET headers, U8 unpacker now supports U8 archives with IMET headers.
This commit is contained in:
Campbell 2024-11-18 16:59:46 -05:00
parent cfd105ba81
commit 855200bb98
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
4 changed files with 105 additions and 6 deletions

View File

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

View File

@ -66,7 +66,28 @@ class U8Archive:
u8_data.seek(0x0)
self.u8_magic = u8_data.read(4)
if self.u8_magic != b'\x55\xAA\x38\x2D':
raise TypeError("This is not a valid U8 archive!")
# Check for an IMET header, if the file doesn't start with the proper magic number. The header magic
# may be at either 0x40 or 0x80 depending on whether this title has a build tag at the start or not.
u8_data.seek(0x40)
self.u8_magic = u8_data.read(4)
if self.u8_magic == b'\x49\x4D\x45\x54':
# IMET with no build tag means the U8 archive should start at 0x600.
u8_data.seek(0x600)
self.u8_magic = u8_data.read(4)
if self.u8_magic != b'\x55\xAA\x38\x2D':
raise TypeError("This is not a valid U8 archive!")
else:
# This check will pass if the IMET comes after a build tag.
u8_data.seek(0x80)
self.u8_magic = u8_data.read(4)
if self.u8_magic == b'\x49\x4D\x45\x54':
# IMET with a build tag means the U8 archive should start at 0x640.
u8_data.seek(0x640)
self.u8_magic = u8_data.read(4)
if self.u8_magic != b'\x55\xAA\x38\x2D':
raise TypeError("This is not a valid U8 archive!")
else:
raise TypeError("This is not a valid U8 archive!")
# Offset of the root node, which will always be 0x20.
self.root_node_offset = int.from_bytes(u8_data.read(4))
# The size of the U8 header.
@ -257,7 +278,7 @@ def _pack_u8_dir(u8_archive: U8Archive, current_path, node_count, parent_node):
return u8_archive, node_count
def pack_u8(input_path) -> bytes:
def pack_u8(input_path, generate_imet=False, imet_titles:List[str]=None) -> bytes:
"""
Packs the provided file or folder into a new U8 archive, and returns the raw file data for it.
@ -265,6 +286,12 @@ def pack_u8(input_path) -> bytes:
----------
input_path
The path to the input file or folder.
generate_imet : bool, optional
Whether an IMET header should be generated for this U8 archive or not. IMET headers are only used for channel
banners (00000000.app). Defaults to False.
imet_titles : List[str], optional
A list of the channel title in different languages for the IMET header. If only one item is provided, that
item will be used for all entries in the header. Defaults to None, and is only used when generate_imet is True.
Returns
-------
@ -287,4 +314,4 @@ def pack_u8(input_path) -> bytes:
elif input_path.is_file():
raise ValueError("This does not appear to be a directory.")
else:
raise FileNotFoundError("Input directory: \"" + str(input_path) + "\" does not exist!")
raise FileNotFoundError(f"Input directory: \"{input_path}\" does not exist!")

View File

@ -0,0 +1,73 @@
# "title/banner.py" from libWiiPy by NinjaCheetah & Contributors
# https://github.com/NinjaCheetah/libWiiPy
#
# See https://wiibrew.org/wiki/Opening.bnr for details about the Wii's banner format
from dataclasses import dataclass as _dataclass
from typing import List
@_dataclass
class IMD5Header:
"""
An IMD5Header object that contains the properties of an IMD5 header. These headers precede the data of banner.bin
and icon.bin inside the banner (00000000.app) of a channel, and are used to verify the data of those files.
An IMD5 header is always 32 bytes long.
Attributes
----------
magic : str
Magic number for the header, should be "IMD5".
file_size : int
The size of the file this header precedes.
zeros : int
8 bytes of zero padding.
md5_hash : bytes
The MD5 hash of the file this header precedes.
"""
magic: str # Should always be "IMD5"
file_size: int
zeros: int
md5_hash: bytes
@_dataclass
class IMETHeader:
"""
An IMETHeader object that contains the properties of an IMET header. These headers precede the data of a channel
banner (00000000.app), and are used to store metadata about the banner and verify its data.
An IMET header is always 1,536 bytes long.
Attributes
----------
zeros : int
64 bytes of zero padding.
magic : str
Magic number for the header, should be "IMD5".
hash_size : int
Length of the MD5 hash.
imet_version : int
Version of the IMET header. Normally always 3.
sizes : List[int]
The file sizes of icon.bin, banner.bin, and sound.bin.
flag1 : int
Unknown.
channel_names : List[str]
The name of the channel this header is for in Japanese, English, German, French, Spanish, Italian, Dutch,
Simplified Chinese, Traditional Chinese, and Korean, in that order.
zeros2 : int
An additional 588 bytes of zero padding.
md5_hash : bytes
"MD5 of 0 to 'hashsize' in header. crypto should be all 0's when calculating final MD5" -WiiBrew
"""
zeros: int
magic: str # Should always be "IMET"
hash_size: int
imet_version: int # Always 3?
sizes: List[int] # Should only have 3 items
flag1: int # Unknown
channel_names: List[str] # Should have 10 items
zeros2: int
md5_hash: bytes

View File

@ -23,12 +23,11 @@ class _TitleLimit:
Attributes
----------
limit_type : int
The type of play limit applied.
The type of play limit applied. 0 and 3 are none, 1 is a time limit, and 4 is a launch count limit.
maximum_usage : int
The maximum value for the type of play limit applied.
"""
# The type of play limit applied.
# 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count
limit_type: int
# The maximum value of the limit applied.
maximum_usage: int