Added methods to wad.py to allow changing a WAD's data

Also added methods to title.py to allow dumping a full WAD. Some attributes were also removed from wad.py, because the offsets would become poisoned if any changes were made to the content sizes, and they therefore should not be read.
This commit is contained in:
Campbell 2024-03-30 02:13:34 -04:00
parent 8026fc4fa3
commit 142a121fa9
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
2 changed files with 166 additions and 117 deletions

View File

@ -45,7 +45,7 @@ class Title:
"invalid.") "invalid.")
def dump(self) -> bytes: def dump(self) -> bytes:
"""Dumps all title components (TMD, ticket, and content) back into the WAD object, and then dumps the WAD back """Dumps all title components (TMD, Ticket, and contents) back into the WAD object, and then dumps the WAD back
into raw data and returns it. into raw data and returns it.
Returns Returns
@ -53,7 +53,15 @@ class Title:
wad_data : bytes wad_data : bytes
The raw data of the WAD. The raw data of the WAD.
""" """
# Dump the TMD. # Dump the TMD and set it in the WAD.
self.wad.set_tmd_data(self.tmd.dump())
# Dump the Ticket and set it in the WAD.
self.wad.set_ticket_data(self.ticket.dump())
# Dump the ContentRegion and set it in the WAD.
self.wad.set_content_data(self.content.dump())
# Dump the WAD with the new regions back into raw data and return it.
wad_data = self.wad.dump()
return wad_data
def set_title_id(self, title_id: str) -> None: def set_title_id(self, title_id: str) -> None:
"""Sets the Title ID of the title in both the TMD and Ticket. """Sets the Title ID of the title in both the TMD and Ticket.
@ -68,6 +76,44 @@ class Title:
self.tmd.set_title_id(title_id) self.tmd.set_title_id(title_id)
self.ticket.set_title_id(title_id) self.ticket.set_title_id(title_id)
def get_content_by_index(self, index: id) -> bytes:
"""Gets an individual content from the content region based on the provided index, in decrypted form.
Parameters
----------
index : int
The index of the content you want to get.
Returns
-------
bytes
The decrypted content listed in the content record.
"""
# Load the Title Key from the Ticket.
title_key = self.ticket.get_title_key()
# Get the decrypted content and return it.
dec_content = self.content.get_content_by_index(index, title_key)
return dec_content
def get_content_by_cid(self, cid: int) -> bytes:
"""Gets an individual content from the content region based on the provided Content ID, in decrypted form.
Parameters
----------
cid : int
The Content ID of the content you want to get. Expected to be in decimal form.
Returns
-------
bytes
The decrypted content listed in the content record.
"""
# Load the Title Key from the Ticket.
title_key = self.ticket.get_title_key()
# Get the decrypted content and return it.
dec_content = self.content.get_content_by_cid(cid, title_key)
return dec_content
def set_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int, def set_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int,
content_hash: bytes) -> None: content_hash: bytes) -> None:
"""Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are """Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are

View File

@ -30,13 +30,13 @@ class WAD:
# This is the size of the content region, which contains all app files combined. # This is the size of the content region, which contains all app files combined.
self.wad_content_size: int = 0 self.wad_content_size: int = 0
self.wad_meta_size: int = 0 self.wad_meta_size: int = 0
# === Offsets === # === Data ===
self.wad_cert_offset: int = 0 self.wad_cert_data: bytes = b''
self.wad_crl_offset: int = 0 self.wad_crl_data: bytes = b''
self.wad_tik_offset: int = 0 self.wad_tik_data: bytes = b''
self.wad_tmd_offset: int = 0 self.wad_tmd_data: bytes = b''
self.wad_content_offset: int = 0 self.wad_content_data: bytes = b''
self.wad_meta_offset: int = 0 self.wad_meta_data: bytes = b''
# Call load() to set all of the attributes from the raw WAD data provided. # Call load() to set all of the attributes from the raw WAD data provided.
self.load() self.load()
@ -89,14 +89,35 @@ class WAD:
# ==================================================================================== # ====================================================================================
# Calculate file offsets from sizes. Every section of the WAD is padded out to a multiple of 0x40. # Calculate file offsets from sizes. Every section of the WAD is padded out to a multiple of 0x40.
# ==================================================================================== # ====================================================================================
self.wad_cert_offset = self.wad_hdr_size wad_cert_offset = self.wad_hdr_size
# crl isn't ever used, however an entry for its size exists in the header, so its calculated just in case. # crl isn't ever used, however an entry for its size exists in the header, so its calculated just in case.
self.wad_crl_offset = align_value(self.wad_cert_offset + self.wad_cert_size) wad_crl_offset = align_value(wad_cert_offset + self.wad_cert_size)
self.wad_tik_offset = align_value(self.wad_crl_offset + self.wad_crl_size) wad_tik_offset = align_value(wad_crl_offset + self.wad_crl_size)
self.wad_tmd_offset = align_value(self.wad_tik_offset + self.wad_tik_size) wad_tmd_offset = align_value(wad_tik_offset + self.wad_tik_size)
# meta isn't guaranteed to be used, but some older SDK titles use it, and not reading it breaks things. # meta isn't guaranteed to be used, but some older SDK titles use it, and not reading it breaks things.
self.wad_meta_offset = align_value(self.wad_tmd_offset + self.wad_tmd_size) wad_meta_offset = align_value(wad_tmd_offset + self.wad_tmd_size)
self.wad_content_offset = align_value(self.wad_meta_offset + self.wad_meta_size) wad_content_offset = align_value(wad_meta_offset + self.wad_meta_size)
# ====================================================================================
# Load data for each WAD section based on the previously calculated offsets.
# ====================================================================================
# Cert data.
wad_data.seek(wad_cert_offset)
self.wad_cert_data = wad_data.read(self.wad_cert_size)
# Crl data.
wad_data.seek(wad_crl_offset)
self.wad_crl_data = wad_data.read(self.wad_crl_size)
# Ticket data.
wad_data.seek(wad_tik_offset)
self.wad_tik_data = wad_data.read(self.wad_tik_size)
# TMD data.
wad_data.seek(wad_tmd_offset)
self.wad_tmd_data = wad_data.read(self.wad_tmd_size)
# Content data.
wad_data.seek(wad_content_offset)
self.wad_content_data = wad_data.read(self.wad_content_size)
# Meta data.
wad_data.seek(wad_meta_offset)
self.wad_meta_data = wad_data.read(self.wad_meta_size)
def dump(self) -> bytes: 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, """Dumps the WAD object back into bytes. This also sets the raw WAD attribute of WAD object to the dumped data,
@ -153,78 +174,6 @@ class WAD:
self.load() self.load()
return self.wad return self.wad
def get_cert_region(self):
"""Gets the offset and size of the certificate data.
Returns
-------
int
The offset of the certificate data in the WAD.
int
The size of the certificate data in the WAD.
"""
return self.wad_cert_offset, self.wad_cert_size
def get_crl_region(self):
"""Gets the offset and size of the crl data.
Returns
-------
int
The offset of the crl data in the WAD.
int
The size of the crl data in the WAD.
"""
return self.wad_crl_offset, self.wad_crl_size
def get_ticket_region(self):
"""Gets the offset and size of the ticket data.
Returns
-------
int
The offset of the ticket data in the WAD.
int
The size of the ticket data in the WAD.
"""
return self.wad_tik_offset, self.wad_tik_size
def get_tmd_region(self):
"""Gets the offset and size of the TMD data.
Returns
-------
int
The offset of the TMD data in the WAD.
int
The size of the TMD data in the WAD.
"""
return self.wad_tmd_offset, self.wad_tmd_size
def get_content_region(self):
"""Gets the offset and size of the content of the WAD.
Returns
-------
int
The offset of the content data in the WAD.
int
The size of the content data in the WAD.
"""
return self.wad_content_offset, self.wad_content_size
def get_meta_region(self):
"""Gets the offset and size of the meta region of the WAD, which is typically unused.
Returns
-------
int
The offset of the meta region in the WAD.
int
The size of the meta region in the WAD.
"""
return self.wad_meta_offset, self.wad_meta_size
def get_wad_type(self): def get_wad_type(self):
"""Gets the type of the WAD. """Gets the type of the WAD.
@ -235,7 +184,7 @@ class WAD:
""" """
return self.wad_type return self.wad_type
def get_cert_data(self): def get_cert_data(self) -> bytes:
"""Gets the certificate data from the WAD. """Gets the certificate data from the WAD.
Returns Returns
@ -243,12 +192,9 @@ class WAD:
bytes bytes
The certificate data. The certificate data.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_cert_data
wad_data.seek(self.wad_cert_offset)
cert_data = wad_data.read(self.wad_cert_size)
return cert_data
def get_crl_data(self): def get_crl_data(self) -> bytes:
"""Gets the crl data from the WAD, if it exists. """Gets the crl data from the WAD, if it exists.
Returns Returns
@ -256,12 +202,9 @@ class WAD:
bytes bytes
The crl data. The crl data.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_crl_data
wad_data.seek(self.wad_crl_offset)
crl_data = wad_data.read(self.wad_crl_size)
return crl_data
def get_ticket_data(self): def get_ticket_data(self) -> bytes:
"""Gets the ticket data from the WAD. """Gets the ticket data from the WAD.
Returns Returns
@ -269,12 +212,9 @@ class WAD:
bytes bytes
The ticket data. The ticket data.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_tik_data
wad_data.seek(self.wad_tik_offset)
ticket_data = wad_data.read(self.wad_tik_size)
return ticket_data
def get_tmd_data(self): def get_tmd_data(self) -> bytes:
"""Returns the TMD data from the WAD. """Returns the TMD data from the WAD.
Returns Returns
@ -282,12 +222,9 @@ class WAD:
bytes bytes
The TMD data. The TMD data.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_tmd_data
wad_data.seek(self.wad_tmd_offset)
tmd_data = wad_data.read(self.wad_tmd_size)
return tmd_data
def get_content_data(self): def get_content_data(self) -> bytes:
"""Gets the content of the WAD. """Gets the content of the WAD.
Returns Returns
@ -295,12 +232,9 @@ class WAD:
bytes bytes
The content data. The content data.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_content_data
wad_data.seek(self.wad_content_offset)
content_data = wad_data.read(self.wad_content_size)
return content_data
def get_meta_data(self): def get_meta_data(self) -> bytes:
"""Gets the meta region of the WAD, which is typically unused. """Gets the meta region of the WAD, which is typically unused.
Returns Returns
@ -308,7 +242,76 @@ class WAD:
bytes bytes
The meta region. The meta region.
""" """
wad_data = io.BytesIO(self.wad) return self.wad_meta_data
wad_data.seek(self.wad_meta_offset)
meta_data = wad_data.read(self.wad_meta_size) def set_cert_data(self, cert_data) -> None:
return meta_data """Sets the certificate data of the WAD. Also calculates the new size.
Parameters
----------
cert_data : bytes
The new certificate data.
"""
self.wad_cert_data = cert_data
# Calculate the size of the new cert data.
self.wad_cert_size = len(cert_data)
def set_crl_data(self, crl_data) -> None:
"""Sets the crl data of the WAD. Also calculates the new size.
Parameters
----------
crl_data : bytes
The new crl data.
"""
self.wad_crl_data = crl_data
# Calculate the size of the new crl data.
self.wad_crl_size = len(crl_data)
def set_tmd_data(self, tmd_data) -> None:
"""Sets the TMD data of the WAD. Also calculates the new size.
Parameters
----------
tmd_data : bytes
The new TMD data.
"""
self.wad_tmd_data = tmd_data
# Calculate the size of the new TMD data.
self.wad_tmd_size = len(tmd_data)
def set_ticket_data(self, tik_data) -> None:
"""Sets the Ticket data of the WAD. Also calculates the new size.
Parameters
----------
tik_data : bytes
The new TMD data.
"""
self.wad_tik_data = tik_data
# Calculate the size of the new Ticket data.
self.wad_tik_size = len(tik_data)
def set_content_data(self, content_data) -> None:
"""Sets the content data of the WAD. Also calculates the new size.
Parameters
----------
content_data : bytes
The new content data.
"""
self.wad_content_data = content_data
# Calculate the size of the new content data.
self.wad_content_size = len(content_data)
def set_meta_data(self, meta_data) -> None:
"""Sets the meta data of the WAD. Also calculates the new size.
Parameters
----------
meta_data : bytes
The new meta data.
"""
self.wad_meta_data = meta_data
# Calculate the size of the new meta data.
self.wad_meta_size = len(meta_data)