Skip to content

Commit 1b6e0db

Browse files
committed
Revert changes related to processing content indices
Changes released in libWiiPy v0.5.0 and v0.5.1 to how indices were handled ended up way overcomplicating things, resulting in lots of issues now that I'm working with the content module again in WiiPy. These changes have mostly been reverted. The issues were related to handling WADs where the content indices don't align with the actual index of the content, like in cases where content has bene removed. This issue has been fixed again with a new and much simpler patch that should not introduce new bugs.
1 parent 9ae059b commit 1b6e0db

File tree

2 files changed

+59
-126
lines changed

2 files changed

+59
-126
lines changed

src/libWiiPy/title/content.py

Lines changed: 48 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ def get_enc_content_by_index(self, index: int) -> bytes:
117117
"""
118118
Gets an individual content from the content region based on the provided index, in encrypted form.
119119
120-
This uses the content index, which is the value tied to each content and used as the IV for encryption, rather
121-
than the literal index in the array of content, because sometimes the contents end up out of order in a WAD
122-
while still retaining the original indices.
123-
124120
Parameters
125121
----------
126122
index : int
@@ -131,17 +127,10 @@ def get_enc_content_by_index(self, index: int) -> bytes:
131127
bytes
132128
The encrypted content listed in the content record.
133129
"""
134-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
135-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
136-
current_indices = []
137-
for record in self.content_records:
138-
current_indices.append(record.index)
139-
if index not in current_indices:
140-
raise ValueError("You are trying to get the content at index " + str(index) + ", but no content with that "
141-
"index exists!")
142-
# This is the literal index in the list of content that we're going to get.
143-
target_index = current_indices.index(index)
144-
content_enc = self.content_list[target_index]
130+
if index >= self.num_contents:
131+
raise ValueError(f"You are trying to get the content at index {index}, but no content with that "
132+
f"index exists!")
133+
content_enc = self.content_list[index]
145134
return content_enc
146135

147136
def get_enc_content_by_cid(self, cid: int) -> bytes:
@@ -181,14 +170,10 @@ def get_content_by_index(self, index: int, title_key: bytes, skip_hash=False) ->
181170
"""
182171
Gets an individual content from the content region based on the provided index, in decrypted form.
183172
184-
This uses the content index, which is the value tied to each content and used as the IV for encryption, rather
185-
than the literal index in the array of content, because sometimes the contents end up out of order in a WAD
186-
while still retaining the original indices.
187-
188173
Parameters
189174
----------
190175
index : int
191-
The content index of the content you want to get.
176+
The index of the content you want to get.
192177
title_key : bytes
193178
The Title Key for the title the content is from.
194179
skip_hash : bool, optional
@@ -199,19 +184,14 @@ def get_content_by_index(self, index: int, title_key: bytes, skip_hash=False) ->
199184
bytes
200185
The decrypted content listed in the content record.
201186
"""
202-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
203-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
204-
current_indices = []
205-
for record in self.content_records:
206-
current_indices.append(record.index)
207-
# This is the literal index in the list of content that we're going to get.
208-
target_index = current_indices.index(index)
187+
# Get the content index in the Content Record to ensure decryption works properly.
188+
cnt_index = self.content_records[index].index
209189
content_enc = self.get_enc_content_by_index(index)
210-
content_dec = decrypt_content(content_enc, title_key, index, self.content_records[target_index].content_size)
190+
content_dec = decrypt_content(content_enc, title_key, cnt_index, self.content_records[index].content_size)
211191
# Hash the decrypted content and ensure that the hash matches the one in its Content Record.
212192
# If it does not, then something has gone wrong in the decryption, and an error will be thrown.
213193
content_dec_hash = hashlib.sha1(content_dec).hexdigest()
214-
content_record_hash = str(self.content_records[target_index].content_hash.decode())
194+
content_record_hash = str(self.content_records[index].content_hash.decode())
215195
# Compare the hash and throw a ValueError if the hash doesn't match.
216196
if content_dec_hash != content_record_hash:
217197
if skip_hash:
@@ -273,9 +253,7 @@ def get_contents(self, title_key: bytes, skip_hash=False) -> List[bytes]:
273253

274254
def get_index_from_cid(self, cid: int) -> int:
275255
"""
276-
Gets the content index of a content by its Content ID. The returned index is the value tied to each content and
277-
used as the IV for encryption, rather than the literal index in the array of content, because sometimes the
278-
contents end up out of order in a WAD while still retaining the original indices.
256+
Gets the index of a content by its Content ID.
279257
280258
Parameters
281259
----------
@@ -293,9 +271,8 @@ def get_index_from_cid(self, cid: int) -> int:
293271
content_ids.append(record.content_id)
294272
if cid not in content_ids:
295273
raise ValueError("The specified Content ID does not exist!")
296-
literal_index = content_ids.index(cid)
297-
target_index = self.content_records[literal_index].index
298-
return target_index
274+
index = content_ids.index(cid)
275+
return index
299276

300277
def add_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int,
301278
content_hash: bytes) -> None:
@@ -327,6 +304,7 @@ def add_enc_content(self, enc_content: bytes, cid: int, index: int, content_type
327304
# If we're good, then append all the data and create a new ContentRecord().
328305
self.content_list.append(enc_content)
329306
self.content_records.append(_ContentRecord(cid, index, content_type, content_size, content_hash))
307+
self.num_contents += 1
330308

331309
def add_content(self, dec_content: bytes, cid: int, content_type: int, title_key: bytes) -> None:
332310
"""
@@ -363,18 +341,14 @@ def set_enc_content(self, enc_content: bytes, index: int, content_size: int, con
363341
"""
364342
Sets the content at the provided content index to the provided new encrypted content. The provided hash and
365343
content size are set in the corresponding content record. A new Content ID or content type can also be
366-
specified, but if it isn't than the current values are preserved.
367-
368-
This uses the content index, which is the value tied to each content and used as the IV for encryption, rather
369-
than the literal index in the array of content, because sometimes the contents end up out of order in a WAD
370-
while still retaining the original indices.
344+
specified, but if it isn't then the current values are preserved.
371345
372346
Parameters
373347
----------
374348
enc_content : bytes
375349
The new encrypted content to set.
376350
index : int
377-
The target content index to set the new content at.
351+
The target index to set the new content at.
378352
content_size : int
379353
The size of the new encrypted content when decrypted.
380354
content_hash : bytes
@@ -384,34 +358,27 @@ def set_enc_content(self, enc_content: bytes, index: int, content_size: int, con
384358
content_type : int, optional
385359
The type of the new content. Current value will be preserved if not set.
386360
"""
387-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
388-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
389-
current_indices = []
390-
for record in self.content_records:
391-
current_indices.append(record.index)
392-
if index not in current_indices:
393-
raise ValueError("You are trying to set the content at index " + str(index) + ", but no content with that "
394-
"index currently exists!")
395-
# This is the literal index in the list of content/content records that we're going to change.
396-
target_index = current_indices.index(index)
361+
if index >= self.num_contents:
362+
raise ValueError(f"You are trying to set the content at index {index}, but no content with that "
363+
f"index currently exists!")
397364
# Reassign the values, but only set the optional ones if they were passed.
398-
self.content_records[target_index].content_size = content_size
399-
self.content_records[target_index].content_hash = content_hash
365+
self.content_records[index].content_size = content_size
366+
self.content_records[index].content_hash = content_hash
400367
if cid is not None:
401-
self.content_records[target_index].content_id = cid
368+
self.content_records[index].content_id = cid
402369
if content_type is not None:
403-
self.content_records[target_index].content_type = content_type
370+
self.content_records[index].content_type = content_type
404371
# Add blank entries to the list to ensure that its length matches the length of the content record list.
405372
while len(self.content_list) < len(self.content_records):
406373
self.content_list.append(b'')
407-
self.content_list[target_index] = enc_content
374+
self.content_list[index] = enc_content
408375

409376
def set_content(self, dec_content: bytes, index: int, title_key: bytes, cid: int = None,
410377
content_type: int = None) -> None:
411378
"""
412379
Sets the content at the provided content index to the provided new decrypted content. The hash and content size
413380
of this content will be generated and then set in the corresponding content record. A new Content ID or content
414-
type can also be specified, but if it isn't than the current values are preserved.
381+
type can also be specified, but if it isn't then the current values are preserved.
415382
416383
The provided Title Key is used to encrypt the content so that it can be set in the ContentRegion.
417384
@@ -432,8 +399,9 @@ def set_content(self, dec_content: bytes, index: int, title_key: bytes, cid: int
432399
content_size = len(dec_content)
433400
# Calculate the hash of the new content.
434401
content_hash = str.encode(hashlib.sha1(dec_content).hexdigest())
435-
# Encrypt the content using the provided Title Key and index.
436-
enc_content = encrypt_content(dec_content, title_key, index)
402+
# Encrypt the content using the provided Title Key and the index from the Content Record, to ensure that
403+
# encryption will succeed even if the provided index doesn't match the content's index.
404+
enc_content = encrypt_content(dec_content, title_key, self.content_records[index].index)
437405
# Pass values to set_enc_content()
438406
self.set_enc_content(enc_content, index, content_size, content_hash, cid, content_type)
439407

@@ -443,76 +411,53 @@ def load_enc_content(self, enc_content: bytes, index: int) -> None:
443411
it matches the record at that index. Not recommended for most use cases, use decrypted content and
444412
load_content() instead.
445413
446-
This uses the content index, which is the value tied to each content and used as the IV for encryption, rather
447-
than the literal index in the array of content, because sometimes the contents end up out of order in a WAD
448-
while still retaining the original indices.
449-
450414
Parameters
451415
----------
452416
enc_content : bytes
453417
The encrypted content to load.
454418
index : int
455419
The content index to load the content at.
456420
"""
457-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
458-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
459-
current_indices = []
460-
for record in self.content_records:
461-
current_indices.append(record.index)
462-
if index not in current_indices:
463-
raise ValueError("You are trying to load the content at index " + str(index) + ", but no content with that "
464-
"index currently exists! Make sure the correct content records have been loaded.")
421+
if index >= self.num_contents:
422+
raise ValueError(f"You are trying to load the content at index {index}, but no content with that "
423+
f"index currently exists! Make sure the correct content records have been loaded.")
465424
# Add blank entries to the list to ensure that its length matches the length of the content record list.
466425
while len(self.content_list) < len(self.content_records):
467426
self.content_list.append(b'')
468-
# This is the literal index in the list of content/content records that we're going to change.
469-
target_index = current_indices.index(index)
470-
self.content_list[target_index] = enc_content
427+
self.content_list[index] = enc_content
471428

472429
def load_content(self, dec_content: bytes, index: int, title_key: bytes) -> None:
473430
"""
474431
Loads the provided decrypted content into the ContentRegion at the specified index, but first checks to make
475432
sure that it matches the corresponding record. This content will then be encrypted using the provided Title Key
476433
before being loaded.
477434
478-
This uses the content index, which is the value tied to each content and used as the IV for encryption, rather
479-
than the literal index in the array of content, because sometimes the contents end up out of order in a WAD
480-
while still retaining the original indices.
481-
482435
Parameters
483436
----------
484437
dec_content : bytes
485438
The decrypted content to load.
486439
index : int
487-
The content index to load the content at.
440+
The index to load the content at.
488441
title_key: bytes
489442
The Title Key that matches the decrypted content.
490443
"""
491-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
492-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
493-
current_indices = []
494-
for record in self.content_records:
495-
current_indices.append(record.index)
496-
if index not in current_indices:
497-
raise ValueError("You are trying to load the content at index " + str(index) + ", but no content with that "
498-
"index currently exists! Make sure the correct content records have been loaded.")
499-
# This is the literal index in the list of content/content records that we're going to change.
500-
target_index = current_indices.index(index)
444+
if index >= self.num_contents:
445+
raise ValueError(f"You are trying to load the content at index {index}, but no content with that "
446+
f"index currently exists! Make sure the correct content records have been loaded.")
501447
# Check the hash of the content against the hash stored in the record to ensure it matches.
502448
content_hash = hashlib.sha1(dec_content).hexdigest()
503-
if content_hash != self.content_records[target_index].content_hash.decode():
449+
if content_hash != self.content_records[index].content_hash.decode():
504450
raise ValueError("The decrypted content provided does not match the record at the provided index. \n"
505451
"Expected hash is: {}\n".format(self.content_records[index].content_hash.decode()) +
506452
"Actual hash is: {}".format(content_hash))
507453
# Add blank entries to the list to ensure that its length matches the length of the content record list.
508454
while len(self.content_list) < len(self.content_records):
509455
self.content_list.append(b'')
510456
# If the hash matches, encrypt the content and set it where it belongs.
511-
# This uses the index from the content records instead of just the index given, because there are some strange
512-
# circumstances where the actual index in the array and the assigned content index don't match up, and this
513-
# needs to accommodate that. Seems to only apply to custom WADs ? (Like cIOS WADs?)
514-
enc_content = encrypt_content(dec_content, title_key, index)
515-
self.content_list[target_index] = enc_content
457+
# This uses the index from the content records instead of just the index given, because there are some poorly
458+
# made custom WADs out there that don't have the contents in order, for whatever reason.
459+
enc_content = encrypt_content(dec_content, title_key, self.content_records[index].index)
460+
self.content_list[index] = enc_content
516461

517462
def remove_content_by_index(self, index: int) -> None:
518463
"""
@@ -525,19 +470,13 @@ def remove_content_by_index(self, index: int) -> None:
525470
index : int
526471
The index of the content you want to remove.
527472
"""
528-
# Get a list of the current content indices, so we can make sure the target one exists. Doing it this way
529-
# ensures we can find the target, even if the highest content index is greater than the highest literal index.
530-
current_indices = []
531-
for record in self.content_records:
532-
current_indices.append(record.index)
533-
if index not in current_indices:
534-
raise ValueError("You are trying to remove the content at index " + str(index) + ", but no content with "
535-
"that index currently exists!")
536-
# This is the literal index in the list of content/content records that we're going to change.
537-
target_index = current_indices.index(index)
473+
if index >= self.num_contents:
474+
raise ValueError(f"You are trying to remove the content at index {index}, but no content with "
475+
f"that index currently exists!")
538476
# Delete the target index from both the content list and content records.
539-
self.content_list.pop(target_index)
540-
self.content_records.pop(target_index)
477+
self.content_list.pop(index)
478+
self.content_records.pop(index)
479+
self.num_contents -= 1
541480

542481
def remove_content_by_cid(self, cid: int) -> None:
543482
"""
@@ -551,11 +490,11 @@ def remove_content_by_cid(self, cid: int) -> None:
551490
The Content ID of the content you want to remove.
552491
"""
553492
try:
554-
content_index = self.get_index_from_cid(cid)
493+
index = self.get_index_from_cid(cid)
555494
except ValueError:
556495
raise ValueError(f"You are trying to remove content with Content ID {cid}, "
557496
f"but no content with that ID exists!")
558-
self.remove_content_by_index(content_index)
497+
self.remove_content_by_index(index)
559498

560499

561500
@_dataclass

0 commit comments

Comments
 (0)