diff --git a/src/libWiiPy/content.py b/src/libWiiPy/content.py index ddb6476..0029759 100644 --- a/src/libWiiPy/content.py +++ b/src/libWiiPy/content.py @@ -79,21 +79,18 @@ class ContentRegion: bytes The full WAD file as bytes. """ - # Open the stream and begin writing data to it. - with io.BytesIO() as content_region_data: - for content in self.content_list: - # Calculate padding after this content before the next one. - padding_bytes = 0 - if (len(content) % 64) != 0: - padding_bytes = 64 - (len(content) % 64) - # Write content data, then the padding afterward if necessary. - content_region_data.write(content) - if padding_bytes > 0: - content_region_data.write(b'\x00' * padding_bytes) - content_region_data.seek(0x0) - content_region_raw = content_region_data.read() + content_region_data = b'' + for content in self.content_list: + # Calculate padding after this content before the next one. + padding_bytes = 0 + if (len(content) % 64) != 0: + padding_bytes = 64 - (len(content) % 64) + # Write content data, then the padding afterward if necessary. + content_region_data += content + if padding_bytes > 0: + content_region_data += b'\x00' * padding_bytes # Return the raw ContentRegion for the data contained in the object. - return content_region_raw + return content_region_data def get_enc_content_by_index(self, index: int) -> bytes: """ diff --git a/src/libWiiPy/crypto.py b/src/libWiiPy/crypto.py index 066cb7b..734f0bf 100644 --- a/src/libWiiPy/crypto.py +++ b/src/libWiiPy/crypto.py @@ -1,5 +1,6 @@ # "crypto.py" from libWiiPy by NinjaCheetah & Contributors # https://github.com/NinjaCheetah/libWiiPy + import struct from .commonkeys import get_common_key from .shared import convert_tid_to_iv diff --git a/src/libWiiPy/nus.py b/src/libWiiPy/nus.py index 7c580ec..4282860 100644 --- a/src/libWiiPy/nus.py +++ b/src/libWiiPy/nus.py @@ -3,7 +3,6 @@ # # See https://wiibrew.org/wiki/NUS for details about the NUS -import io import requests import hashlib from typing import List @@ -151,15 +150,13 @@ def download_cert(wiiu_endpoint: bool = False) -> bytes: tmd = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content cetk = requests.get(url=cetk_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content # Assemble the certificate. - with io.BytesIO() as cert_data: - # Certificate Authority data. - cert_data.write(cetk[0x2A4 + 768:]) - # Certificate Policy data. - cert_data.write(tmd[0x328:0x328 + 768]) - # XS data. - cert_data.write(cetk[0x2A4:0x2A4 + 768]) - cert_data.seek(0x0) - cert = cert_data.read() + cert = b'' + # Certificate Authority data. + cert += cetk[0x2A4 + 768:] + # Certificate Policy data. + cert += tmd[0x328:0x328 + 768] + # XS data. + cert += cetk[0x2A4:0x2A4 + 768] # Since the cert is always the same, check the hash to make sure nothing went wildly wrong. if hashlib.sha1(cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0": raise Exception("An unknown error has occurred downloading and creating the certificate.") diff --git a/src/libWiiPy/shared.py b/src/libWiiPy/shared.py index d54af86..7157dc6 100644 --- a/src/libWiiPy/shared.py +++ b/src/libWiiPy/shared.py @@ -6,6 +6,7 @@ import binascii + def align_value(value, alignment=64) -> int: """ Aligns the provided value to the set alignment (defaults to 64). @@ -28,24 +29,24 @@ def align_value(value, alignment=64) -> int: return value -def pad_bytes_stream(data, alignment=64) -> bytes: +def pad_bytes(data, alignment=64) -> bytes: """ - Pads the provided bytes stream to the provided alignment (defaults to 64). + Pads the provided bytes object to the provided alignment (defaults to 64). Parameters ---------- - data : BytesIO + data : bytes The data to align. alignment : int The number to align to. Defaults to 64. Returns ------- - BytesIO + bytes The aligned data. """ - while (data.getbuffer().nbytes % alignment) != 0: - data.write(b'\x00') + while (len(data) % alignment) != 0: + data += b'\x00' return data diff --git a/src/libWiiPy/ticket.py b/src/libWiiPy/ticket.py index 45c8136..8f6a0aa 100644 --- a/src/libWiiPy/ticket.py +++ b/src/libWiiPy/ticket.py @@ -146,68 +146,62 @@ class Ticket: bytes The full Ticket file as bytes. """ - # Open the stream and begin writing to it. - with io.BytesIO() as ticket_data: - # Signature type. - ticket_data.write(self.signature_type) - # Signature data. - ticket_data.write(self.signature) - # Padding to 64 bytes. - ticket_data.write(b'\x00' * 60) - # Signature issuer. - ticket_data.write(str.encode(self.signature_issuer)) - # ECDH data. - ticket_data.write(self.ecdh_data) - # Ticket version. - ticket_data.write(int.to_bytes(self.ticket_version, 1)) - # Reserved (all \0x00). - ticket_data.write(b'\x00\x00') - # Title Key. - ticket_data.write(self.title_key_enc) - # Unknown (write \0x00). - ticket_data.write(b'\x00') - # Ticket ID. - ticket_data.write(self.ticket_id) - # Console ID. - ticket_data.write(int.to_bytes(self.console_id, 4)) - # Title ID. - ticket_data.write(binascii.unhexlify(self.title_id)) - # Unknown data 1. - ticket_data.write(self.unknown1) - # Title version. - title_version_high = round(self.title_version / 256) - ticket_data.write(int.to_bytes(title_version_high, 1)) - title_version_low = self.title_version % 256 - ticket_data.write(int.to_bytes(title_version_low, 1)) - # Permitted titles mask. - ticket_data.write(self.permitted_titles) - # Permit mask. - ticket_data.write(self.permit_mask) - # Title Export allowed. - ticket_data.write(int.to_bytes(self.title_export_allowed, 1)) - # Common Key index. - ticket_data.write(int.to_bytes(self.common_key_index, 1)) - # Unknown data 2. - ticket_data.write(self.unknown2) - # Content access permissions. - ticket_data.write(self.content_access_permissions) - # Padding (always \x00). - ticket_data.write(b'\x00\x00') - # Iterate over Title Limit objects, write them back into raw data, then add them to the Ticket. - for title_limit in range(len(self.title_limits_list)): - title_limit_data = io.BytesIO() - # Write all fields from the title limit entry. - title_limit_data.write(int.to_bytes(self.title_limits_list[title_limit].limit_type, 4)) - title_limit_data.write(int.to_bytes(self.title_limits_list[title_limit].maximum_usage, 4)) - # Seek to the start and write the entry to the Ticket. - title_limit_data.seek(0x0) - ticket_data.write(title_limit_data.read()) - title_limit_data.close() - # Set the Ticket attribute of the object to the new raw Ticket. - ticket_data.seek(0x0) - ticket_data_raw = ticket_data.read() + ticket_data = b'' + # Signature type. + ticket_data += self.signature_type + # Signature data. + ticket_data += self.signature + # Padding to 64 bytes. + ticket_data += b'\x00' * 60 + # Signature issuer. + ticket_data += str.encode(self.signature_issuer) + # ECDH data. + ticket_data += self.ecdh_data + # Ticket version. + ticket_data += int.to_bytes(self.ticket_version, 1) + # Reserved (all \0x00). + ticket_data += b'\x00\x00' + # Title Key. + ticket_data += self.title_key_enc + # Unknown (write \0x00). + ticket_data += b'\x00' + # Ticket ID. + ticket_data += self.ticket_id + # Console ID. + ticket_data += int.to_bytes(self.console_id, 4) + # Title ID. + ticket_data += binascii.unhexlify(self.title_id) + # Unknown data 1. + ticket_data += self.unknown1 + # Title version. + title_version_high = round(self.title_version / 256) + ticket_data += int.to_bytes(title_version_high, 1) + title_version_low = self.title_version % 256 + ticket_data += int.to_bytes(title_version_low, 1) + # Permitted titles mask. + ticket_data += self.permitted_titles + # Permit mask. + ticket_data += self.permit_mask + # Title Export allowed. + ticket_data += int.to_bytes(self.title_export_allowed, 1) + # Common Key index. + ticket_data += int.to_bytes(self.common_key_index, 1) + # Unknown data 2. + ticket_data += self.unknown2 + # Content access permissions. + ticket_data += self.content_access_permissions + # Padding (always \x00). + ticket_data += b'\x00\x00' + # Iterate over Title Limit objects, write them back into raw data, then add them to the Ticket. + for title_limit in range(len(self.title_limits_list)): + title_limit_data = b'' + # Write all fields from the title limit entry. + title_limit_data += int.to_bytes(self.title_limits_list[title_limit].limit_type, 4) + title_limit_data += int.to_bytes(self.title_limits_list[title_limit].maximum_usage, 4) + # Write the entry to the ticket. + ticket_data += title_limit_data # Return the raw TMD for the data contained in the object. - return ticket_data_raw + return ticket_data def get_title_id(self) -> str: """ diff --git a/src/libWiiPy/tmd.py b/src/libWiiPy/tmd.py index 4f78e46..0f4ee45 100644 --- a/src/libWiiPy/tmd.py +++ b/src/libWiiPy/tmd.py @@ -148,78 +148,72 @@ class TMD: bytes The full TMD file as bytes. """ - # Open the stream and begin writing to it. - with io.BytesIO() as tmd_data: - # Signed blob header. - tmd_data.write(self.blob_header) - # Signing certificate issuer. - tmd_data.write(self.issuer) - # TMD version. - tmd_data.write(int.to_bytes(self.tmd_version, 1)) - # Root certificate crl version. - tmd_data.write(int.to_bytes(self.ca_crl_version, 1)) - # Signer crl version. - tmd_data.write(int.to_bytes(self.signer_crl_version, 1)) - # If this is a vWii title or not. - tmd_data.write(int.to_bytes(self.vwii, 1)) - # IOS Title ID. - tmd_data.write(binascii.unhexlify(self.ios_tid)) - # Title's Title ID. - tmd_data.write(binascii.unhexlify(self.title_id)) - # Content type. - tmd_data.write(binascii.unhexlify(self.content_type)) - # Group ID. - tmd_data.write(int.to_bytes(self.group_id, 2)) - # 2 bytes of zero for reasons. - tmd_data.write(b'\x00\x00') - # Region. - tmd_data.write(int.to_bytes(self.region, 2)) - # Ratings. - tmd_data.write(self.ratings) - # Reserved (all \x00). - tmd_data.write(b'\x00' * 12) - # IPC mask. - tmd_data.write(self.ipc_mask) - # Reserved (all \x00). - tmd_data.write(b'\x00' * 18) - # Access rights. - tmd_data.write(self.access_rights) - # Title version. - title_version_high = round(self.title_version / 256) - tmd_data.write(int.to_bytes(title_version_high, 1)) - title_version_low = self.title_version % 256 - tmd_data.write(int.to_bytes(title_version_low, 1)) - # Number of contents. - tmd_data.write(int.to_bytes(self.num_contents, 2)) - # Boot index. - tmd_data.write(int.to_bytes(self.boot_index, 2)) - # Minor version. Unused so write \x00. - tmd_data.write(b'\x00\x00') - # Iterate over content records, write them back into raw data, then add them to the TMD. - for content_record in range(self.num_contents): - content_data = io.BytesIO() - # Write all fields from the content record. - content_data.write(int.to_bytes(self.content_records[content_record].content_id, 4)) - content_data.write(int.to_bytes(self.content_records[content_record].index, 2)) - content_data.write(int.to_bytes(self.content_records[content_record].content_type, 2)) - content_data.write(int.to_bytes(self.content_records[content_record].content_size, 8)) - content_data.write(binascii.unhexlify(self.content_records[content_record].content_hash)) - # Seek to the start and write the record to the TMD. - content_data.seek(0x0) - tmd_data.write(content_data.read()) - content_data.close() - # Set the TMD attribute of the object to the new raw TMD. - tmd_data.seek(0x0) - tmd_data_raw = tmd_data.read() + tmd_data = b'' + # Signed blob header. + tmd_data += self.blob_header + # Signing certificate issuer. + tmd_data += self.issuer + # TMD version. + tmd_data += int.to_bytes(self.tmd_version, 1) + # Root certificate crl version. + tmd_data += int.to_bytes(self.ca_crl_version, 1) + # Signer crl version. + tmd_data += int.to_bytes(self.signer_crl_version, 1) + # If this is a vWii title or not. + tmd_data += int.to_bytes(self.vwii, 1) + # IOS Title ID. + tmd_data += binascii.unhexlify(self.ios_tid) + # Title's Title ID. + tmd_data += binascii.unhexlify(self.title_id) + # Content type. + tmd_data += binascii.unhexlify(self.content_type) + # Group ID. + tmd_data += int.to_bytes(self.group_id, 2) + # 2 bytes of zero for reasons. + tmd_data += b'\x00\x00' + # Region. + tmd_data += int.to_bytes(self.region, 2) + # Ratings. + tmd_data += self.ratings + # Reserved (all \x00). + tmd_data += b'\x00' * 12 + # IPC mask. + tmd_data += self.ipc_mask + # Reserved (all \x00). + tmd_data += b'\x00' * 18 + # Access rights. + tmd_data += self.access_rights + # Title version. + title_version_high = round(self.title_version / 256) + tmd_data += int.to_bytes(title_version_high, 1) + title_version_low = self.title_version % 256 + tmd_data += int.to_bytes(title_version_low, 1) + # Number of contents. + tmd_data += int.to_bytes(self.num_contents, 2) + # Boot index. + tmd_data += int.to_bytes(self.boot_index, 2) + # Minor version. Unused so write \x00. + tmd_data += b'\x00\x00' + # Iterate over content records, write them back into raw data, then add them to the TMD. + for content_record in range(self.num_contents): + content_data = b'' + # Write all fields from the content record. + content_data += int.to_bytes(self.content_records[content_record].content_id, 4) + content_data += int.to_bytes(self.content_records[content_record].index, 2) + content_data += int.to_bytes(self.content_records[content_record].content_type, 2) + content_data += int.to_bytes(self.content_records[content_record].content_size, 8) + content_data += binascii.unhexlify(self.content_records[content_record].content_hash) + # Write the record to the TMD. + tmd_data += content_data # Return the raw TMD for the data contained in the object. - return tmd_data_raw + return tmd_data def get_title_region(self) -> str: """ Gets the region of the TMD's associated title. Can be one of several possible values: - 'JAP', 'USA', 'EUR', 'NONE', or 'KOR'. + 'JAP', 'USA', 'EUR', 'WORLD', or 'KOR'. Returns ------- @@ -234,7 +228,7 @@ class TMD: case 2: return "EUR" case 3: - return "NONE" + return "WORLD" case 4: return "KOR" diff --git a/src/libWiiPy/wad.py b/src/libWiiPy/wad.py index a0b8d65..5421784 100644 --- a/src/libWiiPy/wad.py +++ b/src/libWiiPy/wad.py @@ -5,7 +5,7 @@ import io import binascii -from .shared import align_value, pad_bytes_stream +from .shared import align_value, pad_bytes class WAD: @@ -15,7 +15,7 @@ class WAD: Attributes ---------- wad_type : str - The type of WAD, either ib for boot2 or Is for normal installable WADs. libWiiPy only supports Is currently. + The type of WAD, either ib for boot2 or Is for normal installable WADs. wad_cert_size : int The size of the WAD's certificate. wad_crl_size : int @@ -60,8 +60,8 @@ class WAD: The data for the WAD you wish to load. """ 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. + # Read the first 8 bytes of the file to ensure that it's a WAD. Has two possible valid values for the two + # different types of WADs that might be encountered. wad_data.seek(0x0) wad_magic_bin = wad_data.read(8) wad_magic_hex = binascii.hexlify(wad_magic_bin) @@ -140,50 +140,46 @@ class WAD: bytes The full WAD file as bytes. """ - # Open the stream and begin writing data to it. - with io.BytesIO() as wad_data: - # Lead-in data. - wad_data.write(b'\x00\x00\x00\x20') - # WAD type. - wad_data.write(str.encode(self.wad_type)) - # WAD version. - wad_data.write(self.wad_version) - # WAD cert size. - wad_data.write(int.to_bytes(self.wad_cert_size, 4)) - # WAD crl size. - wad_data.write(int.to_bytes(self.wad_crl_size, 4)) - # WAD ticket size. - wad_data.write(int.to_bytes(self.wad_tik_size, 4)) - # WAD TMD size. - wad_data.write(int.to_bytes(self.wad_tmd_size, 4)) - # WAD content size. - wad_data.write(int.to_bytes(self.wad_content_size, 4)) - # WAD meta size. - wad_data.write(int.to_bytes(self.wad_meta_size, 4)) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the cert data and write it out. - wad_data.write(self.get_cert_data()) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the crl data and write it out. - wad_data.write(self.get_crl_data()) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the ticket data and write it out. - wad_data.write(self.get_ticket_data()) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the TMD data and write it out. - wad_data.write(self.get_tmd_data()) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the meta/footer data and write it out. - wad_data.write(self.get_meta_data()) - wad_data = pad_bytes_stream(wad_data) - # Retrieve the content data and write it out. - wad_data.write(self.get_content_data()) - 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) - wad_data_raw = wad_data.read() + wad_data = b'' + # Lead-in data. + wad_data += b'\x00\x00\x00\x20' + # WAD type. + wad_data += str.encode(self.wad_type) + # WAD version. + wad_data += self.wad_version + # WAD cert size. + wad_data += int.to_bytes(self.wad_cert_size, 4) + # WAD crl size. + wad_data += int.to_bytes(self.wad_crl_size, 4) + # WAD ticket size. + wad_data += int.to_bytes(self.wad_tik_size, 4) + # WAD TMD size. + wad_data += int.to_bytes(self.wad_tmd_size, 4) + # WAD content size. + wad_data += int.to_bytes(self.wad_content_size, 4) + # WAD meta size. + wad_data += int.to_bytes(self.wad_meta_size, 4) + wad_data = pad_bytes(wad_data) + # Retrieve the cert data and write it out. + wad_data += self.get_cert_data() + wad_data = pad_bytes(wad_data) + # Retrieve the crl data and write it out. + wad_data += self.get_crl_data() + wad_data = pad_bytes(wad_data) + # Retrieve the ticket data and write it out. + wad_data += self.get_ticket_data() + wad_data = pad_bytes(wad_data) + # Retrieve the TMD data and write it out. + wad_data += self.get_tmd_data() + wad_data = pad_bytes(wad_data) + # Retrieve the meta/footer data and write it out. + wad_data += self.get_meta_data() + wad_data = pad_bytes(wad_data) + # Retrieve the content data and write it out. + wad_data += self.get_content_data() + wad_data = pad_bytes(wad_data) # Return the raw WAD file for the data contained in the object. - return wad_data_raw + return wad_data def get_wad_type(self) -> str: """