From 102da808e68ccae0e9655685f4d252d4132dd0a4 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Thu, 25 Jul 2024 21:14:12 -0400 Subject: [PATCH] Add option to skip hash checks when unpacking a WAD --- src/libWiiPy/title/content.py | 30 +++++++++++++++++++++--------- src/libWiiPy/title/title.py | 12 ++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/libWiiPy/title/content.py b/src/libWiiPy/title/content.py index 077abfd..db469ce 100644 --- a/src/libWiiPy/title/content.py +++ b/src/libWiiPy/title/content.py @@ -172,7 +172,7 @@ class ContentRegion: """ return self.content_list - def get_content_by_index(self, index: int, title_key: bytes) -> bytes: + def get_content_by_index(self, index: int, title_key: bytes, skip_hash=False) -> bytes: """ Gets an individual content from the content region based on the provided index, in decrypted form. @@ -186,6 +186,8 @@ class ContentRegion: The content index of the content you want to get. title_key : bytes The Title Key for the title the content is from. + skip_hash : bool, optional + Skip the hash check and return the content regardless of its hash. Defaults to false. Returns ------- @@ -207,13 +209,16 @@ class ContentRegion: content_record_hash = str(self.content_records[target_index].content_hash.decode()) # Compare the hash and throw a ValueError if the hash doesn't match. if content_dec_hash != content_record_hash: - raise ValueError("Content hash did not match the expected hash in its record! The incorrect Title Key may " - "have been used!\n" - "Expected hash is: {}\n".format(content_record_hash) + - "Actual hash is: {}".format(content_dec_hash)) + if skip_hash: + print("Ignoring hash mismatch for content index " + str(index)) + else: + raise ValueError("Content hash did not match the expected hash in its record! The incorrect Title Key " + "may have been used!\n" + "Expected hash is: {}\n".format(content_record_hash) + + "Actual hash is: {}".format(content_dec_hash)) return content_dec - def get_content_by_cid(self, cid: int, title_key: bytes) -> bytes: + def get_content_by_cid(self, cid: int, title_key: bytes, skip_hash=False) -> bytes: """ Gets an individual content from the content region based on the provided Content ID, in decrypted form. @@ -223,6 +228,8 @@ class ContentRegion: The Content ID of the content you want to get. Expected to be in decimal form, not hex. title_key : bytes The Title Key for the title the content is from. + skip_hash : bool, optional + Skip the hash check and return the content regardless of its hash. Defaults to false. Returns ------- @@ -239,10 +246,10 @@ class ContentRegion: # Get the content index associated with the CID we now know exists. target_index = content_ids.index(cid) content_index = self.content_records[target_index].index - content_dec = self.get_content_by_index(content_index, title_key) + content_dec = self.get_content_by_index(content_index, title_key, skip_hash) return content_dec - def get_contents(self, title_key: bytes) -> List[bytes]: + def get_contents(self, title_key: bytes, skip_hash=False) -> List[bytes]: """ Gets a list of all contents from the content region, in decrypted form. @@ -250,6 +257,8 @@ class ContentRegion: ---------- title_key : bytes The Title Key for the title the content is from. + skip_hash : bool, optional + Skip the hash check and return the content regardless of its hash. Defaults to false. Returns ------- @@ -259,7 +268,7 @@ class ContentRegion: dec_contents: List[bytes] = [] # Iterate over every content, get the decrypted version of it, then add it to a list and return it. for content in range(self.num_contents): - dec_contents.append(self.get_content_by_index(content, title_key)) + dec_contents.append(self.get_content_by_index(content, title_key, skip_hash)) return dec_contents def add_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int, @@ -362,6 +371,9 @@ class ContentRegion: self.content_records[target_index].content_id = cid if content_type is not None: self.content_records[target_index].content_type = content_type + # Add blank entries to the list to ensure that its length matches the length of the content record list. + while len(self.content_list) < len(self.content_records): + self.content_list.append(b'') self.content_list[target_index] = enc_content def set_content(self, dec_content: bytes, index: int, title_key: bytes, cid: int = None, diff --git a/src/libWiiPy/title/title.py b/src/libWiiPy/title/title.py index 3462b16..4761e7c 100644 --- a/src/libWiiPy/title/title.py +++ b/src/libWiiPy/title/title.py @@ -145,7 +145,7 @@ class Title: self.tmd.set_title_version(title_version) self.ticket.set_title_version(title_version) - def get_content_by_index(self, index: id) -> bytes: + def get_content_by_index(self, index: id, skip_hash=False) -> bytes: """ Gets an individual content from the content region based on the provided index, in decrypted form. @@ -153,16 +153,18 @@ class Title: ---------- index : int The index of the content you want to get. + skip_hash : bool, optional + Skip the hash check and return the content regardless of its hash. Defaults to false. Returns ------- bytes The decrypted content listed in the content record. """ - dec_content = self.content.get_content_by_index(index, self.ticket.get_title_key()) + dec_content = self.content.get_content_by_index(index, self.ticket.get_title_key(), skip_hash) return dec_content - def get_content_by_cid(self, cid: int) -> bytes: + def get_content_by_cid(self, cid: int, skip_hash=False) -> bytes: """ Gets an individual content from the content region based on the provided Content ID, in decrypted form. @@ -170,13 +172,15 @@ class Title: ---------- cid : int The Content ID of the content you want to get. Expected to be in decimal form. + skip_hash : bool, optional + Skip the hash check and return the content regardless of its hash. Defaults to false. Returns ------- bytes The decrypted content listed in the content record. """ - dec_content = self.content.get_content_by_cid(cid, self.ticket.get_title_key()) + dec_content = self.content.get_content_by_cid(cid, self.ticket.get_title_key(), skip_hash) return dec_content def set_enc_content(self, enc_content: bytes, index: int, content_size: int, content_hash: bytes, cid: int = None,