mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-06-29 06:31:01 -04:00
Full content extraction is working!
This commit is contained in:
parent
b3923cfe40
commit
7ca46372b0
@ -34,8 +34,12 @@ class ContentRegion:
|
|||||||
self.content_region_size = sys.getsizeof(content_region_data)
|
self.content_region_size = sys.getsizeof(content_region_data)
|
||||||
self.num_contents = len(self.content_records)
|
self.num_contents = len(self.content_records)
|
||||||
# Calculate the offsets of each content in the content region.
|
# Calculate the offsets of each content in the content region.
|
||||||
|
# Content is aligned to 16 bytes, however a new content won't start until the next multiple of 64 bytes.
|
||||||
|
# Because of this, we need to add bytes to the next 64 byte offset if the previous content wasn't that long.
|
||||||
for content in self.content_records[:-1]:
|
for content in self.content_records[:-1]:
|
||||||
start_offset = int(64 * round(content.content_size / 64)) + self.content_start_offsets[-1]
|
start_offset = content.content_size + self.content_start_offsets[-1]
|
||||||
|
if (content.content_size % 64) != 0:
|
||||||
|
start_offset += 64 - (content.content_size % 64)
|
||||||
self.content_start_offsets.append(start_offset)
|
self.content_start_offsets.append(start_offset)
|
||||||
|
|
||||||
def get_enc_content(self, index: int) -> bytes:
|
def get_enc_content(self, index: int) -> bytes:
|
||||||
@ -54,8 +58,10 @@ class ContentRegion:
|
|||||||
with io.BytesIO(self.content_region) as content_region_data:
|
with io.BytesIO(self.content_region) as content_region_data:
|
||||||
# Seek to the start of the requested content based on the list of offsets.
|
# Seek to the start of the requested content based on the list of offsets.
|
||||||
content_region_data.seek(self.content_start_offsets[index])
|
content_region_data.seek(self.content_start_offsets[index])
|
||||||
# Calculate the number of bytes we need to read by rounding the size to the nearest 16 bytes.
|
# Calculate the number of bytes we need to read by adding bytes up the nearest multiple of 16 if needed.
|
||||||
bytes_to_read = int(64 * round(self.content_records[index].content_size / 64))
|
bytes_to_read = self.content_records[index].content_size
|
||||||
|
if (bytes_to_read % 16) != 0:
|
||||||
|
bytes_to_read += 16 - (bytes_to_read % 16)
|
||||||
# Read the file based on the size of the content in the associated record.
|
# Read the file based on the size of the content in the associated record.
|
||||||
content_enc = content_region_data.read(bytes_to_read)
|
content_enc = content_region_data.read(bytes_to_read)
|
||||||
return content_enc
|
return content_enc
|
||||||
@ -77,18 +83,16 @@ class ContentRegion:
|
|||||||
"""
|
"""
|
||||||
# Load the encrypted content at the specified index and then decrypt it with the Title Key.
|
# Load the encrypted content at the specified index and then decrypt it with the Title Key.
|
||||||
content_enc = self.get_enc_content(index)
|
content_enc = self.get_enc_content(index)
|
||||||
content_dec = decrypt_content(content_enc, title_key, self.content_records[index].index, self.content_records[index].content_size)
|
content_dec = decrypt_content(content_enc, title_key, self.content_records[index].index,
|
||||||
|
self.content_records[index].content_size)
|
||||||
# Hash the decrypted content and ensure that the hash matches the one in its Content Record.
|
# Hash the decrypted content and ensure that the hash matches the one in its Content Record.
|
||||||
# If it does not, then something has gone wrong in the decryption, and an error will be thrown.
|
# If it does not, then something has gone wrong in the decryption, and an error will be thrown.
|
||||||
content_dec_hash = hashlib.sha1(content_dec)
|
content_dec_hash = hashlib.sha1(content_dec)
|
||||||
content_record_hash = str(self.content_records[index].content_hash.decode())
|
content_record_hash = str(self.content_records[index].content_hash.decode())
|
||||||
|
# Compare the hash and throw a ValueError if the hash doesn't match.
|
||||||
if content_dec_hash.hexdigest() != content_record_hash:
|
if content_dec_hash.hexdigest() != content_record_hash:
|
||||||
#raise ValueError("Content hash did not match the expected hash in its record! This most likely means that "
|
raise ValueError("Content hash did not match the expected hash in its record! The incorrect Title Key may"
|
||||||
#"the incorrect Title Key was used for this content.\n"
|
"have been used!.\n"
|
||||||
#"Expected hash is: {}\n".format(content_record_hash) +
|
|
||||||
#"Actual hash is: {}".format(content_dec_hash.hexdigest()))
|
|
||||||
print("Content hash did not match the expected hash in its record! This most likely means that "
|
|
||||||
"the incorrect Title Key was used for this content.\n"
|
|
||||||
"Expected hash is: {}\n".format(content_record_hash) +
|
"Expected hash is: {}\n".format(content_record_hash) +
|
||||||
"Actual hash is: {}".format(content_dec_hash.hexdigest()))
|
"Actual hash is: {}".format(content_dec_hash.hexdigest()))
|
||||||
return content_dec
|
return content_dec
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# "crypto.py" from libWiiPy by NinjaCheetah & Contributors
|
# "crypto.py" from libWiiPy by NinjaCheetah & Contributors
|
||||||
# https://github.com/NinjaCheetah/libWiiPy
|
# https://github.com/NinjaCheetah/libWiiPy
|
||||||
#
|
|
||||||
# See https://wiibrew.org/wiki/Ticket for details about the TMD format
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from .commonkeys import get_common_key
|
from .commonkeys import get_common_key
|
||||||
@ -9,7 +7,7 @@ from Crypto.Cipher import AES
|
|||||||
from Crypto.Util.Padding import pad, unpad
|
from Crypto.Util.Padding import pad, unpad
|
||||||
|
|
||||||
|
|
||||||
def decrypt_title_key(title_key_enc, common_key_index, title_id):
|
def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
|
||||||
"""Gets the decrypted version of the encrypted Title Key provided.
|
"""Gets the decrypted version of the encrypted Title Key provided.
|
||||||
|
|
||||||
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
|
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
|
||||||
@ -21,7 +19,7 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id):
|
|||||||
common_key_index : int
|
common_key_index : int
|
||||||
The index of the common key to be returned.
|
The index of the common key to be returned.
|
||||||
title_id : bytes
|
title_id : bytes
|
||||||
The title ID of the tite that the key is for.
|
The title ID of the title that the key is for.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -39,7 +37,7 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id):
|
|||||||
return title_key
|
return title_key
|
||||||
|
|
||||||
|
|
||||||
def decrypt_content(content_enc, title_key, content_index, content_length):
|
def decrypt_content(content_enc, title_key, content_index, content_length) -> bytes:
|
||||||
"""Gets the decrypted version of the encrypted content.
|
"""Gets the decrypted version of the encrypted content.
|
||||||
|
|
||||||
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
|
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
|
||||||
@ -50,6 +48,10 @@ def decrypt_content(content_enc, title_key, content_index, content_length):
|
|||||||
The encrypted content.
|
The encrypted content.
|
||||||
title_key : bytes
|
title_key : bytes
|
||||||
The Title Key for the title the content is from.
|
The Title Key for the title the content is from.
|
||||||
|
content_index : int
|
||||||
|
The index in the TMD's content record of the content being decrypted.
|
||||||
|
content_length : int
|
||||||
|
The length in the TMD's content record of the content being decrypted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -60,18 +62,15 @@ def decrypt_content(content_enc, title_key, content_index, content_length):
|
|||||||
content_index_bin = struct.pack('>H', content_index)
|
content_index_bin = struct.pack('>H', content_index)
|
||||||
while len(content_index_bin) < 16:
|
while len(content_index_bin) < 16:
|
||||||
content_index_bin += b'\x00'
|
content_index_bin += b'\x00'
|
||||||
# In CBC mode, content must be padded out to a 16-byte boundary, so do that here, and then remove bytes added after.
|
# Align content to 64 bytes to ensure that all the data is being decrypted, and so it works with AES encryption.
|
||||||
padded = False
|
if (len(content_enc) % 64) != 0:
|
||||||
if (len(content_enc) % 128) != 0:
|
print("needs padding to 64 bytes")
|
||||||
print("needs padding to 16 bytes")
|
content_enc = content_enc + (b'\x00' * (64 - (len(content_enc) % 64)))
|
||||||
content_enc = pad(content_enc, 128, "pkcs7")
|
|
||||||
padded = True
|
|
||||||
# Create a new AES object with the values provided, with the content's unique ID as the IV.
|
# Create a new AES object with the values provided, with the content's unique ID as the IV.
|
||||||
aes = AES.new(title_key, AES.MODE_CBC, content_index_bin)
|
aes = AES.new(title_key, AES.MODE_CBC, content_index_bin)
|
||||||
# Decrypt the content using the AES object.
|
# Decrypt the content using the AES object.
|
||||||
content_dec = aes.decrypt(content_enc)
|
content_dec = aes.decrypt(content_enc)
|
||||||
# Remove padding bytes, if any were added.
|
# Trim additional bytes that may have been added so the content is the correct size.
|
||||||
if padded:
|
while len(content_dec) > content_length:
|
||||||
while len(content_dec) > content_length:
|
content_dec = content_dec[:-1]
|
||||||
content_dec = content_dec[:-1]
|
|
||||||
return content_dec
|
return content_dec
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# "ticket.py" from libWiiPy by NinjaCheetah & Contributors
|
# "ticket.py" from libWiiPy by NinjaCheetah & Contributors
|
||||||
# https://github.com/NinjaCheetah/libWiiPy
|
# https://github.com/NinjaCheetah/libWiiPy
|
||||||
#
|
#
|
||||||
# See https://wiibrew.org/wiki/Ticket for details about the TMD format
|
# See https://wiibrew.org/wiki/Ticket for details about the ticket format
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from .crypto import decrypt_title_key
|
from .crypto import decrypt_title_key
|
||||||
|
@ -22,8 +22,8 @@ class ContentRecord:
|
|||||||
content_hash
|
content_hash
|
||||||
The SHA-1 hash of the decrypted content.
|
The SHA-1 hash of the decrypted content.
|
||||||
"""
|
"""
|
||||||
content_id: int # Unique ID for the current content
|
content_id: int # The unique ID of the content.
|
||||||
index: int # Index in the list of contents
|
index: int # The index of this content in the content record.
|
||||||
content_type: int # Type of content, possible values of: 0x0001: Normal, 0x4001: DLC, 0x8001: Shared
|
content_type: int # Type of content, possible values of: 0x0001: Normal, 0x4001: DLC, 0x8001: Shared.
|
||||||
content_size: int # Size of the current content
|
content_size: int # Size of the content when decrypted.
|
||||||
content_hash: bytes # SHA1 hash of the current content
|
content_hash: bytes # SHA-1 hash of the content when decrypted.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user