mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-27 22:01:01 -04:00
Finished IMETHeader class, can now load, dump, create, and get/set channel names
This commit is contained in:
parent
57b2ed63d4
commit
e96f6d9f13
@ -3,8 +3,9 @@
|
|||||||
#
|
#
|
||||||
# These are the essential submodules from libWiiPy that you'd probably want imported by default.
|
# These are the essential submodules from libWiiPy that you'd probably want imported by default.
|
||||||
|
|
||||||
__all__ = ["archive", "nand", "title"]
|
__all__ = ["archive", "media", "nand", "title"]
|
||||||
|
|
||||||
from . import archive
|
from . import archive
|
||||||
|
from . import media
|
||||||
from . import nand
|
from . import nand
|
||||||
from . import title
|
from . import title
|
||||||
|
@ -8,6 +8,7 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
from dataclasses import dataclass as _dataclass
|
from dataclasses import dataclass as _dataclass
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from ..media.banner import IMETHeader as _IMETHeader
|
||||||
from ..shared import _align_value, _pad_bytes
|
from ..shared import _align_value, _pad_bytes
|
||||||
|
|
||||||
|
|
||||||
@ -36,13 +37,25 @@ class _U8Node:
|
|||||||
|
|
||||||
|
|
||||||
class U8Archive:
|
class U8Archive:
|
||||||
def __init__(self):
|
"""
|
||||||
"""
|
A U8 object that allows for parsing and editing the contents of a U8 archive.
|
||||||
A U8 object that allows for managing the contents of a U8 archive.
|
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
"""
|
u8_node_list : List[_U8Node]
|
||||||
|
A list of U8Node objects representing the nodes of the U8 archive.
|
||||||
|
file_name_list : List[str]
|
||||||
|
A list of the names of the files in the U8 archive.
|
||||||
|
file_data_list : List[bytes]
|
||||||
|
A list of file data for the files in the U8 archive; corresponds with file_name_list.
|
||||||
|
header_size : int
|
||||||
|
The size of the U8 archive header.
|
||||||
|
data_offset : int
|
||||||
|
The offset of the data region of the U8 archive.
|
||||||
|
imet_header: IMETHeader
|
||||||
|
The IMET header of the U8 archive, if one exists. Otherwise, an empty IMETHeader object.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
self.u8_magic = b''
|
self.u8_magic = b''
|
||||||
self.u8_node_list: List[_U8Node] = [] # All the nodes in the header of a U8 file.
|
self.u8_node_list: List[_U8Node] = [] # All the nodes in the header of a U8 file.
|
||||||
self.file_name_list: List[str] = []
|
self.file_name_list: List[str] = []
|
||||||
@ -51,6 +64,7 @@ class U8Archive:
|
|||||||
self.header_size: int = 0
|
self.header_size: int = 0
|
||||||
self.data_offset: int = 0
|
self.data_offset: int = 0
|
||||||
self.root_node: _U8Node = _U8Node(0, 0, 0, 0)
|
self.root_node: _U8Node = _U8Node(0, 0, 0, 0)
|
||||||
|
self.imet_header: _IMETHeader = _IMETHeader()
|
||||||
|
|
||||||
def load(self, u8_data: bytes) -> None:
|
def load(self, u8_data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
@ -76,6 +90,9 @@ class U8Archive:
|
|||||||
self.u8_magic = u8_data.read(4)
|
self.u8_magic = u8_data.read(4)
|
||||||
if self.u8_magic != b'\x55\xAA\x38\x2D':
|
if self.u8_magic != b'\x55\xAA\x38\x2D':
|
||||||
raise TypeError("This is not a valid U8 archive!")
|
raise TypeError("This is not a valid U8 archive!")
|
||||||
|
# Parse the IMET header, then continue parsing the U8 archive.
|
||||||
|
u8_data.seek(0x0)
|
||||||
|
self.imet_header.load(u8_data.read(0x600))
|
||||||
else:
|
else:
|
||||||
# This check will pass if the IMET comes after a build tag.
|
# This check will pass if the IMET comes after a build tag.
|
||||||
u8_data.seek(0x80)
|
u8_data.seek(0x80)
|
||||||
@ -86,6 +103,9 @@ class U8Archive:
|
|||||||
self.u8_magic = u8_data.read(4)
|
self.u8_magic = u8_data.read(4)
|
||||||
if self.u8_magic != b'\x55\xAA\x38\x2D':
|
if self.u8_magic != b'\x55\xAA\x38\x2D':
|
||||||
raise TypeError("This is not a valid U8 archive!")
|
raise TypeError("This is not a valid U8 archive!")
|
||||||
|
# Parse the IMET header, then continue parsing the U8 archive.
|
||||||
|
u8_data.seek(0x40)
|
||||||
|
self.imet_header.load(u8_data.read(0x600))
|
||||||
else:
|
else:
|
||||||
raise TypeError("This is not a valid U8 archive!")
|
raise TypeError("This is not a valid U8 archive!")
|
||||||
# Offset of the root node, which will always be 0x20.
|
# Offset of the root node, which will always be 0x20.
|
||||||
@ -236,7 +256,7 @@ def extract_u8(u8_data, output_folder) -> None:
|
|||||||
open(current_dir.joinpath(u8_archive.file_name_list[node]), "wb").write(u8_archive.file_data_list[node])
|
open(current_dir.joinpath(u8_archive.file_name_list[node]), "wb").write(u8_archive.file_data_list[node])
|
||||||
# Handle an invalid node type.
|
# Handle an invalid node type.
|
||||||
elif u8_archive.u8_node_list[node].type != 0 and u8_archive.u8_node_list[node].type != 1:
|
elif u8_archive.u8_node_list[node].type != 0 and u8_archive.u8_node_list[node].type != 1:
|
||||||
raise ValueError("A node with an invalid type (" + str(u8_archive.u8_node_list[node].type) + ") was found!")
|
raise ValueError(f"A node with an invalid type ({str(u8_archive.u8_node_list[node].type)}) was found!")
|
||||||
|
|
||||||
|
|
||||||
def _pack_u8_dir(u8_archive: U8Archive, current_path, node_count, parent_node):
|
def _pack_u8_dir(u8_archive: U8Archive, current_path, node_count, parent_node):
|
||||||
@ -282,13 +302,16 @@ def pack_u8(input_path, generate_imet=False, imet_titles:List[str]=None) -> byte
|
|||||||
"""
|
"""
|
||||||
Packs the provided file or folder into a new U8 archive, and returns the raw file data for it.
|
Packs the provided file or folder into a new U8 archive, and returns the raw file data for it.
|
||||||
|
|
||||||
|
To generate an IMET header for this U8 archive, the archive must contain the required banner files "icon.bin",
|
||||||
|
"banner.bin", and "sound.bin", because the sizes of these files are stored in the header.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_path
|
input_path
|
||||||
The path to the input file or folder.
|
The path to the input file or folder.
|
||||||
generate_imet : bool, optional
|
generate_imet : bool, optional
|
||||||
Whether an IMET header should be generated for this U8 archive or not. IMET headers are only used for channel
|
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.
|
banners (00000000.app), and required banner files must exist to generate this header. Defaults to False.
|
||||||
imet_titles : List[str], optional
|
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
|
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.
|
item will be used for all entries in the header. Defaults to None, and is only used when generate_imet is True.
|
||||||
@ -310,6 +333,8 @@ def pack_u8(input_path, generate_imet=False, imet_titles:List[str]=None) -> byte
|
|||||||
# subdirectory and file. Discard node_count and name_offset since we don't care about them here, as they're
|
# subdirectory and file. Discard node_count and name_offset since we don't care about them here, as they're
|
||||||
# really only necessary for the directory recursion.
|
# really only necessary for the directory recursion.
|
||||||
u8_archive, _ = _pack_u8_dir(u8_archive, input_path, node_count=1, parent_node=0)
|
u8_archive, _ = _pack_u8_dir(u8_archive, input_path, node_count=1, parent_node=0)
|
||||||
|
if generate_imet:
|
||||||
|
print("gen imet")
|
||||||
return u8_archive.dump()
|
return u8_archive.dump()
|
||||||
elif input_path.is_file():
|
elif input_path.is_file():
|
||||||
raise ValueError("This does not appear to be a directory.")
|
raise ValueError("This does not appear to be a directory.")
|
||||||
|
4
src/libWiiPy/media/__init__.py
Normal file
4
src/libWiiPy/media/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# "media/__init__.py" from libWiiPy by NinjaCheetah & Contributors
|
||||||
|
# https://github.com/NinjaCheetah/libWiiPy
|
||||||
|
|
||||||
|
from .banner import *
|
247
src/libWiiPy/media/banner.py
Normal file
247
src/libWiiPy/media/banner.py
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# "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
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
from dataclasses import dataclass as _dataclass
|
||||||
|
from enum import IntEnum as _IntEnum
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
@_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
|
||||||
|
|
||||||
|
|
||||||
|
class IMETHeader:
|
||||||
|
"""
|
||||||
|
An IMETHeader object that allows for parsing, editing, and generating 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 (0x600) bytes long.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
magic : str
|
||||||
|
Magic number for the header, should be "IMD5".
|
||||||
|
header_size : int
|
||||||
|
Length of the M
|
||||||
|
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.
|
||||||
|
md5_hash : bytes
|
||||||
|
MD5 sum of the entire header, with this field being all zeros during the hashing.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.magic: str = "" # Should always be "IMET"
|
||||||
|
self.header_size: int = 0 # Always 1536? I assumed this would mean something, but it's just the header length.
|
||||||
|
self.imet_version: int = 0 # Always 3?
|
||||||
|
self.sizes: List[int] = [] # Should only have 3 items
|
||||||
|
self.flag1: int = 0 # Unknown
|
||||||
|
self.channel_names: List[str] = [] # Should have 10 items
|
||||||
|
self.md5_hash: bytes = b''
|
||||||
|
|
||||||
|
class LocalizedTitles(_IntEnum):
|
||||||
|
TITLE_JAPANESE = 0
|
||||||
|
TITLE_ENGLISH = 1
|
||||||
|
TITLE_GERMAN = 2
|
||||||
|
TITLE_FRENCH = 3
|
||||||
|
TITLE_SPANISH = 4
|
||||||
|
TITLE_ITALIAN = 5
|
||||||
|
TITLE_DUTCH = 6
|
||||||
|
TITLE_CHINESE_SIMPLIFIED = 7
|
||||||
|
TITLE_CHINESE_TRADITIONAL = 8
|
||||||
|
TITLE_KOREAN = 9
|
||||||
|
|
||||||
|
def load(self, imet_data: bytes) -> None:
|
||||||
|
"""
|
||||||
|
Loads the raw data of an IMET header.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
imet_data : bytes
|
||||||
|
The data for the IMET header to load.
|
||||||
|
"""
|
||||||
|
with io.BytesIO(imet_data) as data:
|
||||||
|
data.seek(0x40)
|
||||||
|
self.magic = str(data.read(4).decode())
|
||||||
|
self.header_size = int.from_bytes(data.read(4))
|
||||||
|
self.imet_version = int.from_bytes(data.read(4))
|
||||||
|
self.sizes = []
|
||||||
|
for _ in range(0, 3):
|
||||||
|
self.sizes.append(int.from_bytes(data.read(4)))
|
||||||
|
self.flag1 = int.from_bytes(data.read(4))
|
||||||
|
self.channel_names = []
|
||||||
|
for _ in range(0, 10):
|
||||||
|
# Read the translated channel name from the header, then drop all trailing null bytes. The encoding
|
||||||
|
# used here is UTF-16 Big Endian.
|
||||||
|
new_channel_name = data.read(84)
|
||||||
|
self.channel_names.append(str(new_channel_name.decode('utf-16-be')).replace('\x00', ''))
|
||||||
|
data.seek(data.tell() + 588)
|
||||||
|
self.md5_hash = binascii.hexlify(data.read(16))
|
||||||
|
|
||||||
|
def dump(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Dump the IMETHeader back into raw bytes.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bytes
|
||||||
|
The IMET header as bytes.
|
||||||
|
"""
|
||||||
|
imet_data = b''
|
||||||
|
# 64 bytes of padding.
|
||||||
|
imet_data += b'\x00' * 64
|
||||||
|
# "IMET" magic number.
|
||||||
|
imet_data += str.encode("IMET")
|
||||||
|
# IMET header size. TODO: check if this is actually always 1536
|
||||||
|
imet_data += int.to_bytes(1536, 4)
|
||||||
|
# IMET header version.
|
||||||
|
imet_data += int.to_bytes(self.imet_version, 4)
|
||||||
|
# Banner component sizes.
|
||||||
|
for size in self.sizes:
|
||||||
|
imet_data += int.to_bytes(size, 4)
|
||||||
|
# flag1.
|
||||||
|
imet_data += int.to_bytes(self.flag1, 4)
|
||||||
|
# Channel names.
|
||||||
|
for channel_name in self.channel_names:
|
||||||
|
new_channel_name = channel_name.encode('utf-16-be')
|
||||||
|
while len(new_channel_name) < 84:
|
||||||
|
new_channel_name += b'\x00'
|
||||||
|
imet_data += new_channel_name
|
||||||
|
# 588 (WHY??) bytes of padding.
|
||||||
|
imet_data += b'\x00' * 588
|
||||||
|
# MD5 hash. To calculate the real one, we need to write all zeros to it first, then hash the entire header with
|
||||||
|
# the zero hash. After that we'll replace this hash with the calculated one.
|
||||||
|
imet_data += b'\x00' * 16
|
||||||
|
imet_hash = hashlib.md5(imet_data)
|
||||||
|
imet_data = imet_data[:-16] + imet_hash.digest()
|
||||||
|
return imet_data
|
||||||
|
|
||||||
|
def create(self, sizes: List[int], channel_names: Tuple[int, str] | List[Tuple[int, str]]) -> None:
|
||||||
|
"""
|
||||||
|
Create a new IMET header, specifying the sizes of the banner components and one or more localized channel names.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
sizes : List[int]
|
||||||
|
The size in bytes of icon.bin, banner.bin, and sound.bin, in that order.
|
||||||
|
channel_names : Tuple(int, str), List[Tuple[int, str]]
|
||||||
|
A pair or list of pairs of the target language and channel name for that language. Target languages are
|
||||||
|
defined in LocalizedTitles.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
libWiiPy.title.banner.IMETHeader.LocalizedTitles
|
||||||
|
"""
|
||||||
|
# Begin by setting the constant values before we parse the input.
|
||||||
|
self.magic = "IMET"
|
||||||
|
self.header_size = 1536
|
||||||
|
self.imet_version = 3
|
||||||
|
self.flag1 = 0 # Still not really sure about this one.
|
||||||
|
# Validate the number of entries, then set the provided file sizes.
|
||||||
|
if len(sizes) != 3:
|
||||||
|
raise ValueError("You must supply 3 file sizes to generate an IMET header!")
|
||||||
|
self.sizes = sizes
|
||||||
|
# Now we can parse the channel names. This functions the same as setting them later, so just calling
|
||||||
|
# set_channel_names() is the most practical.
|
||||||
|
self.channel_names = ["" for _ in range(0, 10)]
|
||||||
|
self.set_channel_names(channel_names)
|
||||||
|
|
||||||
|
def get_channel_names(self, target_languages: int | List[int]) -> str | List[str]:
|
||||||
|
"""
|
||||||
|
Get one or more channel names from the IMET header based on the specified languages.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
target_languages : int, List[int, str]
|
||||||
|
One or more target languages. Target languages are defined in LocalizedTitles.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str, List[str]
|
||||||
|
The channel name for the specified language, or a list of channel names in the same order as the specified
|
||||||
|
languages.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
libWiiPy.title.banner.IMETHeader.LocalizedTitles
|
||||||
|
"""
|
||||||
|
# Flatten single instance of LocalizedTitles being passed to a proper int.
|
||||||
|
if isinstance(target_languages, self.LocalizedTitles):
|
||||||
|
target_languages = int(target_languages)
|
||||||
|
# If only one channel name was requested.
|
||||||
|
if type(target_languages) == int:
|
||||||
|
if target_languages not in self.LocalizedTitles:
|
||||||
|
raise ValueError(f"The specified language is not valid!")
|
||||||
|
return self.channel_names[target_languages]
|
||||||
|
# If multiple channel names were requested.
|
||||||
|
else:
|
||||||
|
channel_names = []
|
||||||
|
for lang in target_languages:
|
||||||
|
if lang not in self.LocalizedTitles:
|
||||||
|
raise ValueError(f"The specified language at index {target_languages.index(lang)} is not valid!")
|
||||||
|
channel_names.append(self.channel_names[lang])
|
||||||
|
return channel_names
|
||||||
|
|
||||||
|
def set_channel_names(self, channel_names: Tuple[int, str] | List[Tuple[int, str]]) -> None:
|
||||||
|
"""
|
||||||
|
Specify one or more new channel names to set in the IMET header.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
channel_names : Tuple(int, str), List[Tuple[int, str]]
|
||||||
|
A pair or list of pairs of the target language and channel name for that language. Target languages are
|
||||||
|
defined in LocalizedTitles.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
libWiiPy.title.banner.IMETHeader.LocalizedTitles
|
||||||
|
"""
|
||||||
|
# If only one channel name was provided.
|
||||||
|
if type(channel_names) == tuple:
|
||||||
|
if channel_names[0] not in self.LocalizedTitles:
|
||||||
|
raise ValueError(f"The target language \"{channel_names[0]}\" is not valid!")
|
||||||
|
if len(channel_names[1]) > 42:
|
||||||
|
raise ValueError(f"The channel name \"{channel_names[1]}\" is too long! Channel names cannot exceed "
|
||||||
|
f"42 characters!")
|
||||||
|
self.channel_names[channel_names[0]] = channel_names[1]
|
||||||
|
# If a list of channel names was provided.
|
||||||
|
else:
|
||||||
|
for name in channel_names:
|
||||||
|
if name[0] not in self.LocalizedTitles:
|
||||||
|
raise ValueError(f"The target language \"{name[0]}\" for the name at index {channel_names.index(name)} "
|
||||||
|
f"is not valid!")
|
||||||
|
if len(name[1]) > 42:
|
||||||
|
raise ValueError(f"The channel name \"{name[1]}\" at index {channel_names.index(name)} is too long! "
|
||||||
|
f"Channel names cannot exceed 42 characters!")
|
||||||
|
self.channel_names[name[0]] = name[1]
|
@ -1,73 +0,0 @@
|
|||||||
# "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
|
|
@ -9,6 +9,7 @@ from .ticket import Ticket
|
|||||||
from .tmd import TMD
|
from .tmd import TMD
|
||||||
from .wad import WAD
|
from .wad import WAD
|
||||||
from .crypto import encrypt_title_key
|
from .crypto import encrypt_title_key
|
||||||
|
from ..archive.u8 import U8Archive as _U8Archive
|
||||||
|
|
||||||
|
|
||||||
class Title:
|
class Title:
|
||||||
@ -398,3 +399,25 @@ class Title:
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_channel_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Gets the English localization of this title's name, if this title is a channel.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The English channel name.
|
||||||
|
"""
|
||||||
|
# First, we need to get the banner (00000000.app) from the title and load it into a U8Archive() object, which
|
||||||
|
# will expose the IMET header, if one exists. If it isn't a U8 archive, then this title has no banner.
|
||||||
|
banner_data = self.get_content_by_index(0)
|
||||||
|
banner_u8 = _U8Archive()
|
||||||
|
try:
|
||||||
|
banner_u8.load(banner_data)
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError("This Title is not a channel and does not have a channel name!")
|
||||||
|
# Check to see if the IMETHeader() has any content by checking its magic property.
|
||||||
|
if banner_u8.imet_header.magic == "":
|
||||||
|
raise ValueError("This Title is not a channel and does not have a channel name!")
|
||||||
|
return banner_u8.imet_header.get_channel_names(banner_u8.imet_header.LocalizedTitles.TITLE_ENGLISH)
|
||||||
|
@ -396,8 +396,8 @@ class TMD:
|
|||||||
|
|
||||||
def get_access_right(self, flag: int) -> bool:
|
def get_access_right(self, flag: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Gets whether an access rights flag is enabled or not. This is done by checking the specified bit. Possible flags
|
Gets whether the specified access rights flag is enabled or not. This is done by checking the specified bit.
|
||||||
and their corresponding bits are defined in the AccessFlags enum.
|
Possible flags and their corresponding bits are defined in AccessFlags.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -408,6 +408,10 @@ class TMD:
|
|||||||
-------
|
-------
|
||||||
bool
|
bool
|
||||||
True if the flag is enabled, False otherwise.
|
True if the flag is enabled, False otherwise.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
libWiiPy.title.tmd.TMD.AccessFlags
|
||||||
"""
|
"""
|
||||||
return bool(self.access_rights & _bitmask(flag))
|
return bool(self.access_rights & _bitmask(flag))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user