Improved docstrings and made them all much more consistent across the project

This commit is contained in:
Campbell 2024-04-03 18:05:12 -04:00
parent 7c631454a1
commit e85eae567a
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
10 changed files with 179 additions and 82 deletions

View File

@ -1,5 +1,7 @@
# "__init__.py" from libWiiPy by NinjaCheetah & Contributors
# https://github.com/NinjaCheetah/libWiiPy
#
# These are the essential modules from libWiiPy that you'd probably want imported by default.
from .commonkeys import *
from .content import *

View File

@ -8,8 +8,9 @@ korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e'
vwii_key = '30bfc76e7c19afbb23163330ced7c28d'
def get_common_key(common_key_index):
"""Gets the specified Wii Common Key based on the index provided.
def get_common_key(common_key_index) -> bytes:
"""
Gets the specified Wii Common Key based on the index provided.
Possible values for common_key_index: 0: Common Key, 1: Korean Key, 2: vWii Key

View File

@ -12,7 +12,16 @@ from .crypto import decrypt_content, encrypt_content
class ContentRegion:
"""Creates a ContentRegion object to parse the continuous content region of a WAD.
"""
A ContentRegion object to parse the continuous content region of a WAD. Allows for retrieving content from the
region in both encrypted or decrypted form, and setting new content.
Attributes
----------
content_records : List[ContentRecord]
The content records for the content stored in the region.
num_contents : int
The total number of contents stored in the region.
"""
def __init__(self):
@ -23,7 +32,8 @@ class ContentRegion:
self.content_list: List[bytes] = []
def load(self, content_region: bytes, content_records: List[ContentRecord]) -> None:
"""Loads the raw content region and builds a list of all the contents.
"""
Loads the raw content region and builds a list of all the contents.
Parameters
----------
@ -59,7 +69,8 @@ class ContentRegion:
self.content_list.append(content_enc)
def dump(self) -> bytes:
"""Takes the list of contents and assembles them back into one content region. Returns this content region as a
"""
Takes the list of contents and assembles them back into one content region. Returns this content region as a
bytes object and sets the raw content region variable to this result, then calls load() again to make sure the
content list matches the raw data.
@ -85,7 +96,8 @@ class ContentRegion:
return content_region_raw
def get_enc_content_by_index(self, index: int) -> bytes:
"""Gets an individual content from the content region based on the provided index, in encrypted form.
"""
Gets an individual content from the content region based on the provided index, in encrypted form.
Parameters
----------
@ -101,7 +113,8 @@ class ContentRegion:
return content_enc
def get_enc_content_by_cid(self, cid: int) -> bytes:
"""Gets an individual content from the content region based on the provided Content ID, in encrypted form.
"""
Gets an individual content from the content region based on the provided Content ID, in encrypted form.
Parameters
----------
@ -127,7 +140,8 @@ class ContentRegion:
return content_enc
def get_enc_contents(self) -> List[bytes]:
"""Gets a list of all encrypted contents from the content region.
"""
Gets a list of all encrypted contents from the content region.
Returns
-------
@ -137,7 +151,8 @@ class ContentRegion:
return self.content_list
def get_content_by_index(self, index: int, title_key: bytes) -> bytes:
"""Gets an individual content from the content region based on the provided index, in decrypted form.
"""
Gets an individual content from the content region based on the provided index, in decrypted form.
Parameters
----------
@ -168,7 +183,8 @@ class ContentRegion:
return content_dec
def get_content_by_cid(self, cid: int, title_key: bytes) -> bytes:
"""Gets an individual content from the content region based on the provided Content ID, in decrypted form.
"""
Gets an individual content from the content region based on the provided Content ID, in decrypted form.
Parameters
----------
@ -196,7 +212,8 @@ class ContentRegion:
return content_dec
def get_contents(self, title_key: bytes) -> List[bytes]:
"""Gets a list of all contents from the content region, in decrypted form.
"""
Gets a list of all contents from the content region, in decrypted form.
Parameters
----------
@ -216,7 +233,8 @@ class ContentRegion:
def set_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int,
content_hash: bytes) -> None:
"""Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
"""
Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
set in the content record, with a new record being added if necessary.
Parameters
@ -257,7 +275,8 @@ class ContentRegion:
self.content_list[index] = enc_content
def set_content(self, dec_content: bytes, cid: int, index: int, content_type: int, title_key: bytes) -> None:
"""Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
"""
Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
set in the content record, with a new record being added if necessary.
Parameters
@ -283,7 +302,8 @@ class ContentRegion:
self.set_enc_content(enc_content, cid, index, content_type, dec_content_size, dec_content_hash)
def load_enc_content(self, enc_content: bytes, index: int) -> None:
"""Loads the provided encrypted content into the content region at the specified index, with the assumption that
"""
Loads the provided encrypted content into the content region at the specified index, with the assumption that
it matches the record at that index. Not recommended for most use cases, use decrypted content and
load_content() instead.
@ -303,7 +323,8 @@ class ContentRegion:
self.content_list[index] = enc_content
def load_content(self, dec_content: bytes, index: int, title_key: bytes) -> None:
"""Loads the provided decrypted content into the content region at the specified index, but first checks to make
"""
Loads the provided decrypted content into the content region at the specified index, but first checks to make
sure it matches the record at that index before loading. This content will be encrypted when loaded.
Parameters

View File

@ -7,7 +7,8 @@ from Crypto.Cipher import AES
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.
@ -37,7 +38,8 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
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.
This requires the index of the content to decrypt as it is used as the IV, as well as the content length to adjust
padding as necessary.
@ -76,7 +78,8 @@ def decrypt_content(content_enc, title_key, content_index, content_length) -> by
def encrypt_content(content_dec, title_key, content_index) -> bytes:
"""Gets the encrypted version of the decrypted content.
"""
Gets the encrypted version of the decrypted content.
This requires the index of the content to encrypt as it is used as the IV, as well as the content length to adjust
padding as necessary.

View File

@ -1,8 +1,12 @@
# "shared.py" from libWiiPy by NinjaCheetah & Contributors
# https://github.com/NinjaCheetah/libWiiPy
#
# This file defines general functions that may be useful in other modules of libWiiPy. Putting them here cuts down on
# clutter in other files.
def align_value(value, alignment=64):
"""Aligns the provided value to the set alignment (defaults to 64).
def align_value(value, alignment=64) -> int:
"""
Aligns the provided value to the set alignment (defaults to 64).
Parameters
----------
@ -22,8 +26,9 @@ def align_value(value, alignment=64):
return value
def pad_bytes_stream(data, alignment=64):
"""Pads the provided bytes stream to the provided alignment (defaults to 64).
def pad_bytes_stream(data, alignment=64) -> bytes:
"""
Pads the provided bytes stream to the provided alignment (defaults to 64).
Parameters
----------

View File

@ -12,8 +12,7 @@ from typing import List
class Ticket:
"""
Creates a Ticket object that allows for either loading and editing an existing Ticket or creating one manually if
desired.
A Ticket object that allows for either loading and editing an existing Ticket or creating one manually if desired.
Attributes
----------
@ -58,7 +57,8 @@ class Ticket:
# TODO: Write in v1 ticket attributes here. This code can currently only handle v0 tickets, and will reject v1.
def load(self, ticket: bytes) -> None:
"""Loads raw Ticket data and sets all attributes of the WAD object. This allows for manipulating an already
"""
Loads raw Ticket data and sets all attributes of the WAD object. This allows for manipulating an already
existing Ticket.
Parameters
@ -138,7 +138,8 @@ class Ticket:
self.title_limits_list.append(TitleLimit(limit_type, limit_value))
def dump(self) -> bytes:
"""Dumps the Ticket object back into bytes. This also sets the raw Ticket attribute of Ticket object to the
"""
Dumps the Ticket object back into bytes. This also sets the raw Ticket attribute of Ticket object to the
dumped data, and triggers load() again to ensure that the raw data and object match.
Returns
@ -209,8 +210,9 @@ class Ticket:
# Return the raw TMD for the data contained in the object.
return ticket_data_raw
def get_title_id(self):
"""Gets the Title ID of the ticket's associated title.
def get_title_id(self) -> str:
"""
Gets the Title ID of the ticket's associated title.
Returns
-------
@ -220,8 +222,9 @@ class Ticket:
title_id_str = str(self.title_id.decode())
return title_id_str
def get_common_key_type(self):
"""Gets the name of the common key used to encrypt the Title Key contained in the ticket.
def get_common_key_type(self) -> str:
"""
Gets the name of the common key used to encrypt the Title Key contained in the ticket.
Returns
-------
@ -240,8 +243,9 @@ class Ticket:
case 2:
return "vWii"
def get_title_key(self):
"""Gets the decrypted title key contained in the ticket.
def get_title_key(self) -> bytes:
"""
Gets the decrypted title key contained in the ticket.
Returns
-------
@ -251,8 +255,9 @@ class Ticket:
title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id)
return title_key
def set_title_id(self, title_id):
"""Sets the Title ID of the title in the Ticket.
def set_title_id(self, title_id) -> None:
"""
Sets the Title ID of the title in the Ticket.
Parameters
----------

View File

@ -10,10 +10,15 @@ from .wad import WAD
class Title:
"""Creates a Title object that contains all components of a title, and allows altering them.
"""
A Title object that contains all components of a title, and allows altering them. Provides higher-level access
than manually creating WAD, TMD, Ticket, and ContentRegion objects and ensures that any data that needs to match
between files matches.
Attributes
----------
wad : WAD
A WAD object of a WAD containing the title's data.
tmd : TMD
A TMD object of the title's TMD.
ticket : Ticket
@ -28,7 +33,8 @@ class Title:
self.content: ContentRegion = ContentRegion()
def load_wad(self, wad: bytes) -> None:
"""Load existing WAD data into the title and create WAD, TMD, Ticket, and ContentRegion objects based off of it
"""
Load existing WAD data into the title and create WAD, TMD, Ticket, and ContentRegion objects based off of it
to allow you to modify that data. Note that this will overwrite any existing data for this title.
Parameters
@ -55,7 +61,8 @@ class Title:
"invalid.")
def dump_wad(self) -> bytes:
"""Dumps all title components (TMD, Ticket, and contents) back into the WAD object, and then dumps the WAD back
"""
Dumps all title components (TMD, Ticket, and contents) back into the WAD object, and then dumps the WAD back
into raw data and returns it.
Returns
@ -74,7 +81,8 @@ class Title:
return wad_data
def load_tmd(self, tmd: bytes) -> None:
"""Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title.
"""
Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title.
Parameters
----------
@ -85,7 +93,8 @@ class Title:
self.tmd.load(tmd)
def load_ticket(self, ticket: bytes) -> None:
"""Load existing Ticket data into the title. Note that this will overwrite any existing Ticket data for this
"""
Load existing Ticket data into the title. Note that this will overwrite any existing Ticket data for this
title.
Parameters
@ -97,7 +106,8 @@ class Title:
self.ticket.load(ticket)
def load_content_records(self) -> None:
"""Load content records from the TMD into the ContentRegion to allow loading content files based on the records.
"""
Load content records from the TMD into the ContentRegion to allow loading content files based on the records.
This requires that a TMD has already been loaded and will throw an exception if it isn't.
"""
if not self.tmd.content_records:
@ -106,7 +116,8 @@ class Title:
self.content.content_records = self.tmd.content_records
def set_title_id(self, title_id: str) -> None:
"""Sets the Title ID of the title in both the TMD and Ticket.
"""
Sets the Title ID of the title in both the TMD and Ticket.
Parameters
----------
@ -119,7 +130,8 @@ class Title:
self.ticket.set_title_id(title_id)
def get_content_by_index(self, index: id) -> bytes:
"""Gets an individual content from the content region based on the provided index, in decrypted form.
"""
Gets an individual content from the content region based on the provided index, in decrypted form.
Parameters
----------
@ -138,7 +150,8 @@ class Title:
return dec_content
def get_content_by_cid(self, cid: int) -> bytes:
"""Gets an individual content from the content region based on the provided Content ID, in decrypted form.
"""
Gets an individual content from the content region based on the provided Content ID, in decrypted form.
Parameters
----------
@ -158,7 +171,8 @@ class Title:
def set_enc_content(self, enc_content: bytes, cid: int, index: int, content_type: int, content_size: int,
content_hash: bytes) -> None:
"""Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
"""
Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
set in the content record, with a new record being added if necessary. The TMD is also updated to match the new
records.
@ -183,7 +197,8 @@ class Title:
self.tmd.content_records = self.content.content_records
def set_content(self, dec_content: bytes, cid: int, index: int, content_type: int) -> None:
"""Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
"""
Sets the provided index to a new content with the provided Content ID. Hashes and size of the content are
set in the content record, with a new record being added if necessary. The Title Key is sourced from this
title's loaded ticket. The TMD is also updated to match the new records.
@ -204,7 +219,8 @@ class Title:
self.tmd.content_records = self.content.content_records
def load_content(self, dec_content: bytes, index: int) -> None:
"""Loads the provided decrypted content into the content region at the specified index, but first checks to make
"""
Loads the provided decrypted content into the content region at the specified index, but first checks to make
sure it matches the record at that index before loading. This content will be encrypted when loaded.
Parameters

View File

@ -12,7 +12,7 @@ from .types import ContentRecord
class TMD:
"""
Creates a TMD object that allows for either loading and editing an existing TMD or creating one manually if desired.
A TMD object that allows for either loading and editing an existing TMD or creating one manually if desired.
Attributes
----------
@ -53,7 +53,8 @@ class TMD:
self.content_records: List[ContentRecord] = []
def load(self, tmd: bytes) -> None:
"""Loads raw TMD data and sets all attributes of the WAD object. This allows for manipulating an already
"""
Loads raw TMD data and sets all attributes of the WAD object. This allows for manipulating an already
existing TMD.
Parameters
@ -138,7 +139,8 @@ class TMD:
binascii.hexlify(content_record_hdr[4])))
def dump(self) -> bytes:
"""Dumps the TMD object back into bytes. This also sets the raw TMD attribute of TMD object to the dumped data,
"""
Dumps the TMD object back into bytes. This also sets the raw TMD attribute of TMD object to the dumped data,
and triggers load() again to ensure that the raw data and object match.
Returns
@ -212,8 +214,9 @@ class TMD:
# Return the raw TMD for the data contained in the object.
return tmd_data_raw
def get_title_region(self):
"""Gets the region of the TMD's associated title.
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'.
@ -235,8 +238,9 @@ class TMD:
case 4:
return "KOR"
def get_is_vwii_title(self):
"""Gets whether the TMD is designed for the vWii or not.
def get_is_vwii_title(self) -> bool:
"""
Gets whether the TMD is designed for the vWii or not.
Returns
-------
@ -248,8 +252,9 @@ class TMD:
else:
return False
def get_title_type(self):
"""Gets the type of the TMD's associated title.
def get_title_type(self) -> str:
"""
Gets the type of the TMD's associated title.
Can be one of several possible values:
'System', 'Game', 'Channel', 'SystemChannel', 'GameWithChannel', or 'HiddenChannel'
@ -279,7 +284,8 @@ class TMD:
return "Unknown"
def get_content_type(self):
"""Gets the type of content contained in the TMD's associated title.
"""
Gets the type of content contained in the TMD's associated title.
Can be one of several possible values:
'Normal', 'Development/Unknown', 'Hash Tree', 'DLC', or 'Shared'
@ -303,8 +309,9 @@ class TMD:
case _:
return "Unknown"
def get_content_record(self, record):
"""Gets the content record at the specified index.
def get_content_record(self, record) -> ContentRecord:
"""
Gets the content record at the specified index.
Parameters
----------
@ -322,8 +329,9 @@ class TMD:
raise IndexError("Invalid content record! TMD lists '" + str(self.num_contents - 1) +
"' contents but index was '" + str(record) + "'!")
def set_title_id(self, title_id):
"""Sets the Title ID of the title in the ticket.
def set_title_id(self, title_id) -> None:
"""
Sets the Title ID of the title in the ticket.
Parameters
----------

View File

@ -7,7 +7,9 @@ from dataclasses import dataclass
@dataclass
class ContentRecord:
"""
Creates a content record object that contains the details of a content contained in a title.
A content record object that contains the details of a content contained in a title. This information must match
the content stored at the index in the record, or else the content will not decrypt properly, as the hash of the
decrypted data will not match the hash in the content record.
Attributes
----------
@ -31,7 +33,10 @@ class ContentRecord:
@dataclass
class TitleLimit:
"""Creates a TitleLimit object that contains the type of restriction and the limit.
"""
A TitleLimit object that contains the type of restriction and the limit. The limit type can be one of the following:
0 = None, 1 = Time Limit, 3 = None, or 4 = Launch Count. The maximum usage is then either the time in minutes the
title can be played or the maximum number of launches allowed for that title, based on the type of limit applied.
Attributes
----------
@ -40,9 +45,8 @@ class TitleLimit:
maximum_usage : int
The maximum value for the type of play limit applied.
"""
# The type of play limit applied. The following types exist:
# The type of play limit applied.
# 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count
limit_type: int
# The maximum value of the limit applied.
# This is either the number of minutes for a time limit, or the number of launches for a launch limit.
maximum_usage: int

View File

@ -10,7 +10,24 @@ from .shared import align_value, pad_bytes_stream
class WAD:
"""
Creates a WAD object that allows for either loading and editing an existing WAD or creating a new WAD from raw data.
A WAD object that allows for either loading and editing an existing WAD or creating a new WAD from raw data.
Attributes
----------
wad_type : str
The type of WAD, either ib for boot2 or Is for normal installable WADs. libWiiPy only supports Is currently.
wad_cert_size : int
The size of the WAD's certificate.
wad_crl_size : int
The size of the WAD's crl.
wad_tik_size : int
The size of the WAD's Ticket.
wad_tmd_size : int
The size of the WAD's TMD.
wad_content_size : int
The size of WAD's total content region.
wad_meta_size : int
The size of the WAD's meta/footer.
"""
def __init__(self):
self.wad_hdr_size: int = 64
@ -33,7 +50,8 @@ class WAD:
self.wad_meta_data: bytes = b''
def load(self, wad_data) -> None:
"""Loads raw WAD data and sets all attributes of the WAD object. This allows for manipulating an already
"""
Loads raw WAD data and sets all attributes of the WAD object. This allows for manipulating an already
existing WAD file.
Parameters
@ -114,7 +132,8 @@ class WAD:
self.wad_meta_data = wad_data.read(self.wad_meta_size)
def dump(self) -> bytes:
"""Dumps the WAD object into the raw WAD file. This allows for creating a WAD file from the data contained in
"""
Dumps the WAD object into the raw WAD file. This allows for creating a WAD file from the data contained in
the WAD object.
Returns
@ -167,8 +186,9 @@ class WAD:
# Return the raw WAD file for the data contained in the object.
return wad_data_raw
def get_wad_type(self):
"""Gets the type of the WAD.
def get_wad_type(self) -> str:
"""
Gets the type of the WAD.
Returns
-------
@ -178,7 +198,8 @@ class WAD:
return self.wad_type
def get_cert_data(self) -> bytes:
"""Gets the certificate data from the WAD.
"""
Gets the certificate data from the WAD.
Returns
-------
@ -188,7 +209,8 @@ class WAD:
return self.wad_cert_data
def get_crl_data(self) -> bytes:
"""Gets the crl data from the WAD, if it exists.
"""
Gets the crl data from the WAD, if it exists.
Returns
-------
@ -198,7 +220,8 @@ class WAD:
return self.wad_crl_data
def get_ticket_data(self) -> bytes:
"""Gets the ticket data from the WAD.
"""
Gets the ticket data from the WAD.
Returns
-------
@ -208,7 +231,8 @@ class WAD:
return self.wad_tik_data
def get_tmd_data(self) -> bytes:
"""Returns the TMD data from the WAD.
"""
Returns the TMD data from the WAD.
Returns
-------
@ -218,7 +242,8 @@ class WAD:
return self.wad_tmd_data
def get_content_data(self) -> bytes:
"""Gets the content of the WAD.
"""
Gets the content of the WAD.
Returns
-------
@ -228,7 +253,8 @@ class WAD:
return self.wad_content_data
def get_meta_data(self) -> bytes:
"""Gets the meta region of the WAD, which is typically unused.
"""
Gets the meta region of the WAD, which is typically unused.
Returns
-------
@ -238,7 +264,8 @@ class WAD:
return self.wad_meta_data
def set_cert_data(self, cert_data) -> None:
"""Sets the certificate data of the WAD. Also calculates the new size.
"""
Sets the certificate data of the WAD. Also calculates the new size.
Parameters
----------
@ -250,7 +277,8 @@ class WAD:
self.wad_cert_size = len(cert_data)
def set_crl_data(self, crl_data) -> None:
"""Sets the crl data of the WAD. Also calculates the new size.
"""
Sets the crl data of the WAD. Also calculates the new size.
Parameters
----------
@ -262,7 +290,8 @@ class WAD:
self.wad_crl_size = len(crl_data)
def set_tmd_data(self, tmd_data) -> None:
"""Sets the TMD data of the WAD. Also calculates the new size.
"""
Sets the TMD data of the WAD. Also calculates the new size.
Parameters
----------
@ -274,7 +303,8 @@ class WAD:
self.wad_tmd_size = len(tmd_data)
def set_ticket_data(self, tik_data) -> None:
"""Sets the Ticket data of the WAD. Also calculates the new size.
"""
Sets the Ticket data of the WAD. Also calculates the new size.
Parameters
----------
@ -286,7 +316,8 @@ class WAD:
self.wad_tik_size = len(tik_data)
def set_content_data(self, content_data) -> None:
"""Sets the content data of the WAD. Also calculates the new size.
"""
Sets the content data of the WAD. Also calculates the new size.
Parameters
----------
@ -298,7 +329,8 @@ class WAD:
self.wad_content_size = len(content_data)
def set_meta_data(self, meta_data) -> None:
"""Sets the meta data of the WAD. Also calculates the new size.
"""
Sets the meta data of the WAD. Also calculates the new size.
Parameters
----------