@@ -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