mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-25 21:01:01 -04:00
Add highly experimental U8 handling module
This commit is contained in:
parent
6a81722ec5
commit
ede33dc503
3
.gitignore
vendored
3
.gitignore
vendored
@ -161,9 +161,10 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
|
||||
# Allows me to keep TMD files in my repository folder for testing without accidentally publishing them
|
||||
# Relevant files that are used for testing libWiiPy's features.
|
||||
*.tmd
|
||||
*.wad
|
||||
*.arc
|
||||
out_prod/
|
||||
remakewad.pl
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "libWiiPy"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
authors = [
|
||||
{ name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" },
|
||||
{ name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" }
|
||||
|
@ -11,3 +11,4 @@ from .title import *
|
||||
from .tmd import *
|
||||
from .wad import *
|
||||
from .nus import *
|
||||
from .u8 import *
|
||||
|
@ -1,6 +1,6 @@
|
||||
# "types.py" from libWiiPy by NinjaCheetah & Contributors
|
||||
# https://github.com/NinjaCheetah/libWiiPy
|
||||
|
||||
from builtins import type
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@ -14,21 +14,21 @@ class ContentRecord:
|
||||
Attributes
|
||||
----------
|
||||
content_id : int
|
||||
ID of the content.
|
||||
The unique ID of the content.
|
||||
index : int
|
||||
Index of the content in the list of contents.
|
||||
The index of this content in the content records.
|
||||
content_type : int
|
||||
The type of the content.
|
||||
content_size : int
|
||||
The size of the content.
|
||||
The size of the content when decrypted.
|
||||
content_hash
|
||||
The SHA-1 hash of the decrypted content.
|
||||
"""
|
||||
content_id: int # The unique ID of the content.
|
||||
index: int # The index of this content in the content record.
|
||||
content_id: int
|
||||
index: int
|
||||
content_type: int # Type of content, possible values of: 0x0001: Normal, 0x4001: DLC, 0x8001: Shared.
|
||||
content_size: int # Size of the content when decrypted.
|
||||
content_hash: bytes # SHA-1 hash of the content when decrypted.
|
||||
content_size: int
|
||||
content_hash: bytes
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -50,3 +50,27 @@ class TitleLimit:
|
||||
limit_type: int
|
||||
# The maximum value of the limit applied.
|
||||
maximum_usage: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class U8Node:
|
||||
"""
|
||||
A U8Node object that contains the data of a single node in a U8 file header. Each node keeps track of whether this
|
||||
node is for a file or directory, the offset of the name of the file/directory, the offset of the data for the file/
|
||||
directory, and the size of the data.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
type : int
|
||||
Whether this node refers to a file or a directory. Either 0x0000 for files, or 0x0100 for directories.
|
||||
name_offset : int
|
||||
The offset of the name of the file/directory this node refers to.
|
||||
data_offset : int
|
||||
The offset of the data for the file/directory this node refers to.
|
||||
size : int
|
||||
The size of the data for this node.
|
||||
"""
|
||||
type: int
|
||||
name_offset: int
|
||||
data_offset: int
|
||||
size: int
|
||||
|
83
src/libWiiPy/u8.py
Normal file
83
src/libWiiPy/u8.py
Normal file
@ -0,0 +1,83 @@
|
||||
# "u8.py" from libWiiPy by NinjaCheetah & Contributors
|
||||
# https://github.com/NinjaCheetah/libWiiPy
|
||||
#
|
||||
# See https://wiibrew.org/wiki/U8_archive for details about the U8 archive format
|
||||
|
||||
import io
|
||||
import binascii
|
||||
from typing import List
|
||||
from .types import U8Node
|
||||
|
||||
|
||||
class U8Archive:
|
||||
def __init__(self):
|
||||
"""
|
||||
A U8 object that allows for extracting and packing U8 archives.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
"""
|
||||
self.u8_magic = b''
|
||||
self.root_node_offset = 0 # Offset of the root node, which will always be 0x20.
|
||||
self.header_size = 0 # The size of the U8 header.
|
||||
self.data_offset = 0 # The offset of the data, which is root_node_offset + header_size, aligned to 0x40.
|
||||
self.header_padding = b''
|
||||
self.root_node = U8Node
|
||||
self.u8_node_list: List[U8Node] = [] # All the nodes in the header of a U8 file.
|
||||
self.file_name_list: List[str] = []
|
||||
self.u8_file_data_list: List[bytes] = []
|
||||
self.u8_file_structure = dict
|
||||
|
||||
def load(self, u8_data: bytes) -> None:
|
||||
"""
|
||||
Loads raw U8 data into a new U8 object. This allows for extracting the file and updating its contents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u8_data : bytes
|
||||
The data for the U8 file to load.
|
||||
"""
|
||||
with io.BytesIO(u8_data) as u8_data:
|
||||
# Read the first 4 bytes of the file to ensure that it's a U8 archive.
|
||||
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!")
|
||||
self.root_node_offset = int(binascii.hexlify(u8_data.read(4)), 16)
|
||||
self.header_size = int(binascii.hexlify(u8_data.read(4)), 16)
|
||||
self.data_offset = int(binascii.hexlify(u8_data.read(4)), 16)
|
||||
self.header_padding = u8_data.read(16)
|
||||
root_node_type = int.from_bytes(u8_data.read(2))
|
||||
root_node_name_offset = int.from_bytes(u8_data.read(2))
|
||||
root_node_data_offset = int.from_bytes(u8_data.read(4))
|
||||
root_node_size = int.from_bytes(u8_data.read(4))
|
||||
self.root_node = U8Node(root_node_type, root_node_name_offset, root_node_data_offset, root_node_size)
|
||||
self.u8_node_list.append(self.root_node)
|
||||
# Iterate over the number of nodes that the root node lists, minus one since the count includes itself.
|
||||
for node in range(self.root_node.size - 1):
|
||||
node_type = int.from_bytes(u8_data.read(2))
|
||||
node_name_offset = int.from_bytes(u8_data.read(2))
|
||||
node_data_offset = int.from_bytes(u8_data.read(4))
|
||||
node_size = int.from_bytes(u8_data.read(4))
|
||||
self.u8_node_list.append(U8Node(node_type, node_name_offset, node_data_offset, node_size))
|
||||
# Iterate over all loaded nodes and create a list of file names.
|
||||
name_base_offset = u8_data.tell()
|
||||
for node in self.u8_node_list:
|
||||
u8_data.seek(name_base_offset + node.name_offset)
|
||||
name_bin = b''
|
||||
while name_bin[-1:] != b'\x00':
|
||||
name_bin += u8_data.read(1)
|
||||
name_bin = name_bin[:-1]
|
||||
name = str(name_bin.decode())
|
||||
self.file_name_list.append(name)
|
||||
if node.type == 0:
|
||||
u8_data.seek(node.data_offset)
|
||||
self.u8_file_data_list.append(u8_data.read(node.size))
|
||||
else:
|
||||
self.u8_file_data_list.append(b'')
|
||||
# This does nothing for now.
|
||||
next_dir = 0
|
||||
for node in range(len(self.u8_node_list)):
|
||||
if self.u8_node_list[node].type == 256 and node != 0:
|
||||
next_dir = self.u8_node_list[node].size
|
||||
|
@ -49,7 +49,7 @@ class WAD:
|
||||
self.wad_content_data: bytes = b''
|
||||
self.wad_meta_data: bytes = b''
|
||||
|
||||
def load(self, wad_data) -> None:
|
||||
def load(self, wad_data: bytes) -> None:
|
||||
"""
|
||||
Loads raw WAD data and sets all attributes of the WAD object. This allows for manipulating an already
|
||||
existing WAD file.
|
||||
@ -57,7 +57,7 @@ class WAD:
|
||||
Parameters
|
||||
----------
|
||||
wad_data : bytes
|
||||
The data for the WAD you wish to load.
|
||||
The data for the WAD file to load.
|
||||
"""
|
||||
with io.BytesIO(wad_data) as wad_data:
|
||||
# Read the first 8 bytes of the file to ensure that it's a WAD. Has two possible valid values for the two
|
||||
@ -67,7 +67,7 @@ class WAD:
|
||||
wad_magic_hex = binascii.hexlify(wad_magic_bin)
|
||||
wad_magic = str(wad_magic_hex.decode())
|
||||
if wad_magic != "0000002049730000" and wad_magic != "0000002069620000":
|
||||
raise TypeError("This does not appear to be a valid WAD file.")
|
||||
raise TypeError("This is not a valid WAD file!")
|
||||
# ====================================================================================
|
||||
# Get the sizes of each data region contained within the WAD.
|
||||
# ====================================================================================
|
||||
|
Loading…
x
Reference in New Issue
Block a user