mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-29 06:21:02 -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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
.idea/
|
.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
|
*.tmd
|
||||||
*.wad
|
*.wad
|
||||||
|
*.arc
|
||||||
out_prod/
|
out_prod/
|
||||||
remakewad.pl
|
remakewad.pl
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "libWiiPy"
|
name = "libWiiPy"
|
||||||
version = "0.2.3"
|
version = "0.3.0"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" },
|
{ name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" },
|
||||||
{ name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" }
|
{ name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" }
|
||||||
|
@ -11,3 +11,4 @@ from .title import *
|
|||||||
from .tmd import *
|
from .tmd import *
|
||||||
from .wad import *
|
from .wad import *
|
||||||
from .nus import *
|
from .nus import *
|
||||||
|
from .u8 import *
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# "types.py" from libWiiPy by NinjaCheetah & Contributors
|
# "types.py" from libWiiPy by NinjaCheetah & Contributors
|
||||||
# https://github.com/NinjaCheetah/libWiiPy
|
# https://github.com/NinjaCheetah/libWiiPy
|
||||||
|
from builtins import type
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@ -14,21 +14,21 @@ class ContentRecord:
|
|||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
content_id : int
|
content_id : int
|
||||||
ID of the content.
|
The unique ID of the content.
|
||||||
index : int
|
index : int
|
||||||
Index of the content in the list of contents.
|
The index of this content in the content records.
|
||||||
content_type : int
|
content_type : int
|
||||||
The type of the content.
|
The type of the content.
|
||||||
content_size : int
|
content_size : int
|
||||||
The size of the content.
|
The size of the content when decrypted.
|
||||||
content_hash
|
content_hash
|
||||||
The SHA-1 hash of the decrypted content.
|
The SHA-1 hash of the decrypted content.
|
||||||
"""
|
"""
|
||||||
content_id: int # The unique ID of the content.
|
content_id: int
|
||||||
index: int # The index of this content in the content record.
|
index: int
|
||||||
content_type: int # Type of content, possible values of: 0x0001: Normal, 0x4001: DLC, 0x8001: Shared.
|
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_size: int
|
||||||
content_hash: bytes # SHA-1 hash of the content when decrypted.
|
content_hash: bytes
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -50,3 +50,27 @@ class TitleLimit:
|
|||||||
limit_type: int
|
limit_type: int
|
||||||
# The maximum value of the limit applied.
|
# The maximum value of the limit applied.
|
||||||
maximum_usage: int
|
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_content_data: bytes = b''
|
||||||
self.wad_meta_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
|
Loads raw WAD data and sets all attributes of the WAD object. This allows for manipulating an already
|
||||||
existing WAD file.
|
existing WAD file.
|
||||||
@ -57,7 +57,7 @@ class WAD:
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
wad_data : bytes
|
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:
|
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
|
# 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_hex = binascii.hexlify(wad_magic_bin)
|
||||||
wad_magic = str(wad_magic_hex.decode())
|
wad_magic = str(wad_magic_hex.decode())
|
||||||
if wad_magic != "0000002049730000" and wad_magic != "0000002069620000":
|
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.
|
# Get the sizes of each data region contained within the WAD.
|
||||||
# ====================================================================================
|
# ====================================================================================
|
||||||
|
Loading…
x
Reference in New Issue
Block a user