mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-26 13:21:01 -04:00
Allow creating blank objects for WADs, TMDs, Tickets, ContentRegions, and Titles to make WAD packing possible
This commit is contained in:
parent
142a121fa9
commit
640ca91716
@ -13,33 +13,27 @@ from .crypto import decrypt_content, encrypt_content
|
||||
|
||||
class ContentRegion:
|
||||
"""Creates a ContentRegion object to parse the continuous content region of a WAD.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
content_region : bytes
|
||||
A bytes object containing the content region of a WAD file.
|
||||
content_records : list[ContentRecord]
|
||||
A list of ContentRecord objects detailing all contents contained in the region.
|
||||
"""
|
||||
|
||||
def __init__(self, content_region, content_records: List[ContentRecord]):
|
||||
self.content_region = content_region
|
||||
self.content_records = content_records
|
||||
def __init__(self):
|
||||
self.content_records: List[ContentRecord] = []
|
||||
self.content_region_size: int = 0 # Size of the content region.
|
||||
self.num_contents: int = 0 # Number of contents in the content region.
|
||||
self.content_start_offsets: List[int] = [0] # The start offsets of each content in the content region.
|
||||
self.content_list: List[bytes] = []
|
||||
# Call load() to set all of the attributes from the raw content region provided.
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
def load(self, content_region: bytes, content_records: List[ContentRecord]) -> None:
|
||||
"""Loads the raw content region and builds a list of all the contents.
|
||||
|
||||
Returns
|
||||
-------
|
||||
none
|
||||
Parameters
|
||||
----------
|
||||
content_region : bytes
|
||||
The raw data for the content region being loaded.
|
||||
content_records : list[ContentRecord]
|
||||
A list of ContentRecord objects detailing all contents contained in the region.
|
||||
"""
|
||||
with io.BytesIO(self.content_region) as content_region_data:
|
||||
self.content_records = content_records
|
||||
with io.BytesIO(content_region) as content_region_data:
|
||||
# Get the total size of the content region.
|
||||
self.content_region_size = sys.getsizeof(content_region_data)
|
||||
self.num_contents = len(self.content_records)
|
||||
@ -86,13 +80,9 @@ class ContentRegion:
|
||||
if padding_bytes > 0:
|
||||
content_region_data.write(b'\x00' * padding_bytes)
|
||||
content_region_data.seek(0x0)
|
||||
self.content_region = content_region_data.read()
|
||||
# Clear existing lists.
|
||||
self.content_start_offsets = [0]
|
||||
self.content_list = []
|
||||
# Reload object's attributes to ensure the raw data and object match.
|
||||
self.load()
|
||||
return self.content_region
|
||||
content_region_raw = content_region_data.read()
|
||||
# Return the raw ContentRegion for the data contained in the object.
|
||||
return content_region_raw
|
||||
|
||||
def get_enc_content_by_index(self, index: int) -> bytes:
|
||||
"""Gets an individual content from the content region based on the provided index, in encrypted form.
|
||||
@ -291,3 +281,11 @@ class ContentRegion:
|
||||
enc_content = encrypt_content(dec_content, title_key, index)
|
||||
# Pass values to set_enc_content()
|
||||
self.set_enc_content(enc_content, cid, index, content_type, dec_content_size, dec_content_hash)
|
||||
|
||||
def load_enc_content(self, enc_content: bytes, index: int) -> bytes:
|
||||
"""Loads the provided encrypted content into the content region at the specified index, with the assumption that
|
||||
it matches the record at that index.
|
||||
|
||||
:param index:
|
||||
:return:
|
||||
"""
|
||||
|
@ -11,12 +11,9 @@ from typing import List
|
||||
|
||||
|
||||
class Ticket:
|
||||
"""Creates a Ticket object to parse a Ticket file to retrieve the Title Key needed to decrypt it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticket : bytes
|
||||
A bytes object containing the contents of a ticket file.
|
||||
"""
|
||||
Creates a Ticket object that allows for either loading and editing an existing Ticket or creating one manually if
|
||||
desired.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@ -35,8 +32,7 @@ class Ticket:
|
||||
common_key_index : int
|
||||
The index of the common key required to decrypt this ticket's Title Key.
|
||||
"""
|
||||
def __init__(self, ticket):
|
||||
self.ticket = ticket
|
||||
def __init__(self):
|
||||
# Signature blob header
|
||||
self.signature_type: bytes = b'' # Type of signature, always 0x10001 for RSA-2048
|
||||
self.signature: bytes = b'' # Actual signature data
|
||||
@ -60,17 +56,17 @@ class Ticket:
|
||||
self.title_limits_list: List[TitleLimit] = [] # List of play limits applied to the title.
|
||||
# v1 ticket data
|
||||
# TODO: Write in v1 ticket attributes here. This code can currently only handle v0 tickets, and will reject v1.
|
||||
# Call load() to set all of the attributes from the raw Ticket data provided.
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
"""Loads the raw Ticket data and sets all attributes of the Ticket object.
|
||||
def load(self, ticket: bytes) -> None:
|
||||
"""Loads raw Ticket data and sets all attributes of the WAD object. This allows for manipulating an already
|
||||
existing Ticket.
|
||||
|
||||
Returns
|
||||
-------
|
||||
none
|
||||
Parameters
|
||||
----------
|
||||
ticket : bytes
|
||||
The data for the Ticket you wish to load.
|
||||
"""
|
||||
with io.BytesIO(self.ticket) as ticket_data:
|
||||
with io.BytesIO(ticket) as ticket_data:
|
||||
# ====================================================================================
|
||||
# Parses each of the keys contained in the Ticket.
|
||||
# ====================================================================================
|
||||
@ -209,10 +205,9 @@ class Ticket:
|
||||
title_limit_data.close()
|
||||
# Set the Ticket attribute of the object to the new raw Ticket.
|
||||
ticket_data.seek(0x0)
|
||||
self.ticket = ticket_data.read()
|
||||
# Reload object's attributes to ensure the raw data and object match.
|
||||
self.load()
|
||||
return self.ticket
|
||||
ticket_data_raw = ticket_data.read()
|
||||
# Return the raw TMD for the data contained in the object.
|
||||
return ticket_data_raw
|
||||
|
||||
def get_title_id(self):
|
||||
"""Gets the Title ID of the ticket's associated title.
|
||||
|
@ -12,11 +12,6 @@ from .wad import WAD
|
||||
class Title:
|
||||
"""Creates a Title object that contains all components of a title, and allows altering them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wad : WAD
|
||||
A WAD object to load data from.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
tmd : TMD
|
||||
@ -26,18 +21,33 @@ class Title:
|
||||
content: ContentRegion
|
||||
A ContentRegion object containing the title's contents.
|
||||
"""
|
||||
def __init__(self, wad: WAD):
|
||||
self.wad = wad
|
||||
self.tmd: TMD
|
||||
self.ticket: Ticket
|
||||
self.content: ContentRegion
|
||||
# Load data from the WAD object, and generate all other objects from the data in it.
|
||||
def __init__(self):
|
||||
self.wad: WAD = WAD()
|
||||
self.tmd: TMD = TMD()
|
||||
self.ticket: Ticket = Ticket()
|
||||
self.content: ContentRegion = ContentRegion()
|
||||
|
||||
def set_wad(self, wad: bytes) -> None:
|
||||
"""Load existing WAD data into the title and create WAD, TMD, Ticket, and ContentRegion objects based off of it
|
||||
to allow you to modify that data. Note that this will overwrite any existing data for this title.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wad : bytes
|
||||
The data for the WAD you wish to load.
|
||||
"""
|
||||
# Create a new WAD object based on the WAD data provided.
|
||||
self.wad = WAD()
|
||||
self.wad.load(wad)
|
||||
# Load the TMD.
|
||||
self.tmd = TMD(self.wad.get_tmd_data())
|
||||
self.tmd = TMD()
|
||||
self.tmd.load(self.wad.get_tmd_data())
|
||||
# Load the ticket.
|
||||
self.ticket = Ticket(self.wad.get_ticket_data())
|
||||
self.ticket = Ticket()
|
||||
self.ticket.load(self.wad.get_ticket_data())
|
||||
# Load the content.
|
||||
self.content = ContentRegion(self.wad.get_content_data(), self.tmd.content_records)
|
||||
self.content = ContentRegion()
|
||||
self.content.load(self.wad.get_content_data(), self.tmd.content_records)
|
||||
# Ensure that the Title IDs of the TMD and Ticket match before doing anything else. If they don't, throw an
|
||||
# error because clearly something strange has gone on with the WAD and editing it probably won't work.
|
||||
if self.tmd.title_id != self.ticket.title_id_str:
|
||||
|
@ -12,12 +12,7 @@ from .types import ContentRecord
|
||||
|
||||
class TMD:
|
||||
"""
|
||||
Creates a TMD object to parse a TMD file to retrieve information about a title.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tmd : bytes
|
||||
A bytes object containing the contents of a TMD file.
|
||||
Creates a TMD object that allows for either loading and editing an existing TMD or creating one manually if desired.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@ -34,8 +29,7 @@ class TMD:
|
||||
num_contents : int
|
||||
The number of contents listed in the TMD.
|
||||
"""
|
||||
def __init__(self, tmd):
|
||||
self.tmd = tmd
|
||||
def __init__(self):
|
||||
self.blob_header: bytes = b''
|
||||
self.sig_type: int = 0
|
||||
self.sig: bytes = b''
|
||||
@ -57,17 +51,17 @@ class TMD:
|
||||
self.num_contents: int = 0 # The number of contents contained in the associated title.
|
||||
self.boot_index: int = 0
|
||||
self.content_records: List[ContentRecord] = []
|
||||
# Call load() to set all of the attributes from the raw TMD data provided.
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
"""Loads the raw TMD data and sets all attributes of the TMD object.
|
||||
def load(self, tmd: bytes) -> None:
|
||||
"""Loads raw TMD data and sets all attributes of the WAD object. This allows for manipulating an already
|
||||
existing TMD.
|
||||
|
||||
Returns
|
||||
-------
|
||||
none
|
||||
Parameters
|
||||
----------
|
||||
tmd : bytes
|
||||
The data for the TMD you wish to load.
|
||||
"""
|
||||
with io.BytesIO(self.tmd) as tmd_data:
|
||||
with io.BytesIO(tmd) as tmd_data:
|
||||
# ====================================================================================
|
||||
# Parses each of the keys contained in the TMD.
|
||||
# ====================================================================================
|
||||
@ -214,12 +208,9 @@ class TMD:
|
||||
content_data.close()
|
||||
# Set the TMD attribute of the object to the new raw TMD.
|
||||
tmd_data.seek(0x0)
|
||||
self.tmd = tmd_data.read()
|
||||
# Clear existing lists.
|
||||
self.content_records = []
|
||||
# Reload object's attributes to ensure the raw data and object match.
|
||||
self.load()
|
||||
return self.tmd
|
||||
tmd_data_raw = tmd_data.read()
|
||||
# Return the raw TMD for the data contained in the object.
|
||||
return tmd_data_raw
|
||||
|
||||
def get_title_region(self):
|
||||
"""Gets the region of the TMD's associated title.
|
||||
|
@ -10,15 +10,9 @@ from .shared import align_value, pad_bytes_stream
|
||||
|
||||
class WAD:
|
||||
"""
|
||||
Creates a WAD object to parse the header of a WAD file and retrieve the data contained in it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wad : bytes
|
||||
A bytes object containing the contents of a WAD file.
|
||||
Creates a WAD object that allows for either loading and editing an existing WAD or creating a new WAD from raw data.
|
||||
"""
|
||||
def __init__(self, wad):
|
||||
self.wad = wad
|
||||
def __init__(self):
|
||||
self.wad_hdr_size: int = 64
|
||||
self.wad_type: str = ""
|
||||
self.wad_version: bytes = b''
|
||||
@ -37,17 +31,17 @@ class WAD:
|
||||
self.wad_tmd_data: bytes = b''
|
||||
self.wad_content_data: bytes = b''
|
||||
self.wad_meta_data: bytes = b''
|
||||
# Call load() to set all of the attributes from the raw WAD data provided.
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
"""Loads the raw WAD data and sets all attributes of the WAD object.
|
||||
def load(self, wad_data) -> None:
|
||||
"""Loads raw WAD data and sets all attributes of the WAD object. This allows for manipulating an already
|
||||
existing WAD file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
none
|
||||
Parameters
|
||||
----------
|
||||
wad_data : bytes
|
||||
The data for the WAD you wish to load.
|
||||
"""
|
||||
with io.BytesIO(self.wad) 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. This will currently reject boot2 WADs, but
|
||||
# this tool cannot handle them correctly right now anyway.
|
||||
wad_data.seek(0x0)
|
||||
@ -120,8 +114,8 @@ class WAD:
|
||||
self.wad_meta_data = wad_data.read(self.wad_meta_size)
|
||||
|
||||
def dump(self) -> bytes:
|
||||
"""Dumps the WAD object back into bytes. This also sets the raw WAD attribute of WAD object to the dumped data,
|
||||
and triggers load() again to ensure that the raw data and object match.
|
||||
"""Dumps the WAD object into the raw WAD file. This allows for creating a WAD file from the data contained in
|
||||
the WAD object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -169,10 +163,9 @@ class WAD:
|
||||
wad_data = pad_bytes_stream(wad_data)
|
||||
# Seek to the beginning and save this as the WAD data for the object.
|
||||
wad_data.seek(0x0)
|
||||
self.wad = wad_data.read()
|
||||
# Reload object's attributes to ensure the raw data and object match.
|
||||
self.load()
|
||||
return self.wad
|
||||
wad_data_raw = wad_data.read()
|
||||
# Return the raw WAD file for the data contained in the object.
|
||||
return wad_data_raw
|
||||
|
||||
def get_wad_type(self):
|
||||
"""Gets the type of the WAD.
|
||||
|
Loading…
x
Reference in New Issue
Block a user