mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2026-03-05 00:25:29 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a81722ec5 | |||
| ecc68d9e57 | |||
| c42dc66209 | |||
| 045613216a | |||
| 98666285db | |||
| ba320a29de | |||
| 9890a6dbac | |||
|
c92a8096ea
|
|||
|
99a55a3de5
|
|||
| 4a3e9f8e7f | |||
| 8eeebd1d75 | |||
|
3b7a2d09b0
|
|||
|
a85beac602
|
|||
| 338446efcb | |||
|
ccbc2e262b
|
|||
|
|
17a894dc0d | ||
| 60918f1a39 | |||
|
fa6c9eb740
|
11
README.md
11
README.md
@@ -10,6 +10,7 @@ libWiiPy is inspired by [libWiiSharp](https://github.com/TheShadowEevee/libWiiSh
|
||||
This list will expand as libWiiPy is developed, but these features are currently available:
|
||||
- TMD, ticket, and WAD parsing
|
||||
- WAD content extraction, decryption, re-encryption, and packing
|
||||
- Downloading titles from the NUS
|
||||
|
||||
# Usage
|
||||
A wiki, and in the future a potential documenation site, is being worked on, and can be accessed [here](https://github.com/NinjaCheetah/libWiiPy/wiki). It is currently fairly barebones, but it will be improved in the future.
|
||||
@@ -30,27 +31,27 @@ Please be aware that because libWiiPy is in a very early state right now, many f
|
||||
To build this package locally, the steps are quite simple, and should apply to all platforms. Make sure you've set up your `venv` first!
|
||||
|
||||
First, install the dependencies from `requirements.txt`:
|
||||
```py
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Then, build the package using the Python `build` module:
|
||||
```py
|
||||
```sh
|
||||
python -m build
|
||||
```
|
||||
|
||||
And that's all! You'll find your compiled pip package in `dist/`.
|
||||
|
||||
# Special Thanks
|
||||
This project wouldn't be possible without the amazing people behind its predecessors and all of the people who have contributed to the documentation of the Wii's inner workings over at [Wiibrew](https://wiibrew.org).
|
||||
This project wouldn't be possible without the amazing people behind its predecessors and all of the people who have contributed to the documentation of the Wii's inner workings over at [WiiBrew](https://wiibrew.org).
|
||||
|
||||
## Special Thanks for the Inspiration and Previous Projects
|
||||
- Xuzz, SquidMan, megazig, Matt_P, Omega and The Lemon Man for creating Wii.py
|
||||
- Leathl for creating libWiiSharp
|
||||
- TheShadowEevee for maintaining libWiiSharp
|
||||
|
||||
## Special Thanks to Wiibrew Contributors
|
||||
Thank you to all of the contributors to the documentation on the Wiibrew pages that make this all understandable! Some of the key articles referenced are as follows:
|
||||
## Special Thanks to WiiBrew Contributors
|
||||
Thank you to all of the contributors to the documentation on the WiiBrew pages that make this all understandable! Some of the key articles referenced are as follows:
|
||||
- [Title metadata](https://wiibrew.org/wiki/Title_metadata), for the documentation on how a TMD is structured
|
||||
- [WAD files](https://wiibrew.org/wiki/WAD_files), for the documentation on how a WAD is structured
|
||||
- [IOS history](https://wiibrew.org/wiki/IOS_history), for the documentation on IOS TIDs and how IOS is versioned
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "libWiiPy"
|
||||
version = "0.2.0"
|
||||
version = "0.2.3"
|
||||
authors = [
|
||||
{ name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" },
|
||||
{ name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" }
|
||||
@@ -15,6 +15,7 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"pycryptodome",
|
||||
"requests"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
build
|
||||
pycryptodome
|
||||
requests
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
from .commonkeys import *
|
||||
from .content import *
|
||||
from .ticket import *
|
||||
from .crypto import *
|
||||
from .title import *
|
||||
from .tmd import *
|
||||
from .wad import *
|
||||
from .nus import *
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
import struct
|
||||
from .commonkeys import get_common_key
|
||||
from .shared import convert_tid_to_iv
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
|
||||
def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: bytes | str) -> bytes:
|
||||
"""
|
||||
Gets the decrypted version of the encrypted Title Key provided.
|
||||
|
||||
@@ -17,9 +19,9 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
|
||||
title_key_enc : bytes
|
||||
The encrypted Title Key.
|
||||
common_key_index : int
|
||||
The index of the common key to be returned.
|
||||
title_id : bytes
|
||||
The title ID of the title that the key is for.
|
||||
The index of the common key used to encrypt the Title Key.
|
||||
title_id : bytes, str
|
||||
The Title ID of the title that the key is for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -28,8 +30,10 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
|
||||
"""
|
||||
# Load the correct common key for the title.
|
||||
common_key = get_common_key(common_key_index)
|
||||
# Calculate the IV by adding 8 bytes to the end of the Title ID.
|
||||
title_key_iv = title_id + (b'\x00' * 8)
|
||||
# Convert the IV into the correct format based on the type provided.
|
||||
title_key_iv = convert_tid_to_iv(title_id)
|
||||
# The IV will always be in the same format by this point, so add the last 8 bytes.
|
||||
title_key_iv = title_key_iv + (b'\x00' * 8)
|
||||
# Create a new AES object with the values provided.
|
||||
aes = AES.new(common_key, AES.MODE_CBC, title_key_iv)
|
||||
# Decrypt the Title Key using the AES object.
|
||||
@@ -37,6 +41,39 @@ def decrypt_title_key(title_key_enc, common_key_index, title_id) -> bytes:
|
||||
return title_key
|
||||
|
||||
|
||||
def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: bytes | str) -> bytes:
|
||||
"""
|
||||
Encrypts the provided Title Key with the selected common key.
|
||||
|
||||
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_key_dec : bytes
|
||||
The decrypted Title Key.
|
||||
common_key_index : int
|
||||
The index of the common key used to encrypt the Title Key.
|
||||
title_id : bytes, str
|
||||
The Title ID of the title that the key is for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
An encrypted Title Key.
|
||||
"""
|
||||
# Load the correct common key for the title.
|
||||
common_key = get_common_key(common_key_index)
|
||||
# Convert the IV into the correct format based on the type provided.
|
||||
title_key_iv = convert_tid_to_iv(title_id)
|
||||
# The IV will always be in the same format by this point, so add the last 8 bytes.
|
||||
title_key_iv = title_key_iv + (b'\x00' * 8)
|
||||
# Create a new AES object with the values provided.
|
||||
aes = AES.new(common_key, AES.MODE_CBC, title_key_iv)
|
||||
# Encrypt Title Key using the AES object.
|
||||
title_key = aes.encrypt(title_key_dec)
|
||||
return title_key
|
||||
|
||||
|
||||
def decrypt_content(content_enc, title_key, content_index, content_length) -> bytes:
|
||||
"""
|
||||
Gets the decrypted version of the encrypted content.
|
||||
@@ -72,8 +109,7 @@ def decrypt_content(content_enc, title_key, content_index, content_length) -> by
|
||||
# Decrypt the content using the AES object.
|
||||
content_dec = aes.decrypt(content_enc)
|
||||
# Trim additional bytes that may have been added so the content is the correct size.
|
||||
while len(content_dec) > content_length:
|
||||
content_dec = content_dec[:-1]
|
||||
content_dec = content_dec[:content_length]
|
||||
return content_dec
|
||||
|
||||
|
||||
|
||||
233
src/libWiiPy/nus.py
Normal file
233
src/libWiiPy/nus.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# "nus.py" from libWiiPy by NinjaCheetah & Contributors
|
||||
# https://github.com/NinjaCheetah/libWiiPy
|
||||
#
|
||||
# See https://wiibrew.org/wiki/NUS for details about the NUS
|
||||
|
||||
import requests
|
||||
import hashlib
|
||||
from typing import List
|
||||
from .title import Title
|
||||
from .tmd import TMD
|
||||
from .ticket import Ticket
|
||||
|
||||
nus_endpoint = ["http://nus.cdn.shop.wii.com/ccs/download/", "http://ccs.cdn.wup.shop.nintendo.net/ccs/download/"]
|
||||
|
||||
|
||||
def download_title(title_id: str, title_version: int = None, wiiu_endpoint: bool = False) -> Title:
|
||||
"""
|
||||
Download an entire title and all of its contents, then load the downloaded components into a Title object for
|
||||
further use. This method is NOT recommended for general use, as it has absolutely no verbosity. It is instead
|
||||
recommended to call the individual download methods instead to provide more flexibility and output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_id : str
|
||||
The Title ID of the title to download.
|
||||
title_version : int, option
|
||||
The version of the title to download. Defaults to latest if not set.
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Title
|
||||
A Title object containing all the data from the downloaded title.
|
||||
"""
|
||||
# First, create the new title.
|
||||
title = Title()
|
||||
# Download and load the TMD, Ticket, and certs.
|
||||
title.load_tmd(download_tmd(title_id, title_version, wiiu_endpoint))
|
||||
title.load_ticket(download_ticket(title_id, wiiu_endpoint))
|
||||
title.wad.set_cert_data(download_cert(wiiu_endpoint))
|
||||
# Download all contents
|
||||
title.load_content_records()
|
||||
title.content.content_list = download_contents(title_id, title.tmd, wiiu_endpoint)
|
||||
# Return the completed title.
|
||||
return title
|
||||
|
||||
|
||||
def download_tmd(title_id: str, title_version: int = None, wiiu_endpoint: bool = False) -> bytes:
|
||||
"""
|
||||
Downloads the TMD of the Title specified in the object. Will download the latest version by default, or another
|
||||
version if it was manually specified in the object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_id : str
|
||||
The Title ID of the title to download the TMD for.
|
||||
title_version : int, option
|
||||
The version of the TMD to download. Defaults to latest if not set.
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The TMD file from the NUS.
|
||||
"""
|
||||
# Build the download URL. The structure is download/<TID>/tmd for latest and download/<TID>/tmd.<version> for
|
||||
# when a specific version is requested.
|
||||
if wiiu_endpoint is False:
|
||||
tmd_url = nus_endpoint[0] + title_id + "/tmd"
|
||||
else:
|
||||
tmd_url = nus_endpoint[1] + title_id + "/tmd"
|
||||
# Add the version to the URL if one was specified.
|
||||
if title_version is not None:
|
||||
tmd_url += "." + str(title_version)
|
||||
# Make the request.
|
||||
tmd_request = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True)
|
||||
# Handle a 404 if the TID/version doesn't exist.
|
||||
if tmd_request.status_code != 200:
|
||||
raise ValueError("The requested Title ID or TMD version does not exist. Please check the Title ID and Title"
|
||||
" version and then try again.")
|
||||
# Save the raw TMD.
|
||||
raw_tmd = tmd_request.content
|
||||
# Use a TMD object to load the data and then return only the actual TMD.
|
||||
tmd_temp = TMD()
|
||||
tmd_temp.load(raw_tmd)
|
||||
tmd = tmd_temp.dump()
|
||||
return tmd
|
||||
|
||||
|
||||
def download_ticket(title_id: str, wiiu_endpoint: bool = False) -> bytes:
|
||||
"""
|
||||
Downloads the Ticket of the Title specified in the object. This will only work if the Title ID specified is for
|
||||
a free title.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_id : str
|
||||
The Title ID of the title to download the Ticket for.
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The Ticket file from the NUS.
|
||||
"""
|
||||
# Build the download URL. The structure is download/<TID>/cetk, and cetk will only exist if this is a free
|
||||
# title.
|
||||
if wiiu_endpoint is False:
|
||||
ticket_url = nus_endpoint[0] + title_id + "/cetk"
|
||||
else:
|
||||
ticket_url = nus_endpoint[1] + title_id + "/cetk"
|
||||
# Make the request.
|
||||
ticket_request = requests.get(url=ticket_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True)
|
||||
if ticket_request.status_code != 200:
|
||||
raise ValueError("The requested Title ID does not exist, or refers to a non-free title. Tickets can only"
|
||||
" be downloaded for titles that are free on the NUS.")
|
||||
# Save the raw cetk file.
|
||||
cetk = ticket_request.content
|
||||
# Use a Ticket object to load only the Ticket data from cetk and return it.
|
||||
ticket_temp = Ticket()
|
||||
ticket_temp.load(cetk)
|
||||
ticket = ticket_temp.dump()
|
||||
return ticket
|
||||
|
||||
|
||||
def download_cert(wiiu_endpoint: bool = False) -> bytes:
|
||||
"""
|
||||
Downloads the signing certificate used by all WADs. This uses System Menu 4.3U as the source.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The cert file.
|
||||
"""
|
||||
# Download the TMD and cetk for the System Menu 4.3U.
|
||||
if wiiu_endpoint is False:
|
||||
tmd_url = nus_endpoint[0] + "0000000100000002/tmd.513"
|
||||
cetk_url = nus_endpoint[0] + "0000000100000002/cetk"
|
||||
else:
|
||||
tmd_url = nus_endpoint[1] + "0000000100000002/tmd.513"
|
||||
cetk_url = nus_endpoint[1] + "0000000100000002/cetk"
|
||||
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.
|
||||
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.")
|
||||
return cert
|
||||
|
||||
|
||||
def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False) -> bytes:
|
||||
"""
|
||||
Downloads a specified content for the title specified in the object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_id : str
|
||||
The Title ID of the title to download content from.
|
||||
content_id : int
|
||||
The Content ID of the content you wish to download.
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
The downloaded content.
|
||||
"""
|
||||
# Build the download URL. The structure is download/<TID>/<Content ID>.
|
||||
content_id_hex = hex(content_id)[2:]
|
||||
if len(content_id_hex) < 2:
|
||||
content_id_hex = "0" + content_id_hex
|
||||
if wiiu_endpoint is False:
|
||||
content_url = nus_endpoint[0] + title_id + "/000000" + content_id_hex
|
||||
else:
|
||||
content_url = nus_endpoint[1] + title_id + "/000000" + content_id_hex
|
||||
# Make the request.
|
||||
content_request = requests.get(url=content_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True)
|
||||
if content_request.status_code != 200:
|
||||
raise ValueError("The requested Title ID does not exist, or an invalid Content ID is present in the"
|
||||
" content records provided.\n Failed while downloading Content ID: 000000" +
|
||||
content_id_hex)
|
||||
content_data = content_request.content
|
||||
return content_data
|
||||
|
||||
|
||||
def download_contents(title_id: str, tmd: TMD, wiiu_endpoint: bool = False) -> List[bytes]:
|
||||
"""
|
||||
Downloads all the contents for the title specified in the object. This requires a TMD to already be available
|
||||
so that the content records can be accessed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title_id : str
|
||||
The Title ID of the title to download content from.
|
||||
tmd : TMD
|
||||
The TMD that matches the title that the contents being downloaded are from.
|
||||
wiiu_endpoint : bool, option
|
||||
Whether the Wii U endpoint for the NUS should be used or not. This increases download speeds. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[bytes]
|
||||
A list of all the downloaded contents.
|
||||
"""
|
||||
# Retrieve the content records from the TMD.
|
||||
content_records = tmd.content_records
|
||||
# Create a list of Content IDs to download.
|
||||
content_ids = []
|
||||
for content_record in content_records:
|
||||
content_ids.append(content_record.content_id)
|
||||
# Iterate over that list and download each content in it, then add it to the array of contents.
|
||||
content_list = []
|
||||
for content_id in content_ids:
|
||||
# Call self.download_content() for each Content ID.
|
||||
content = download_content(title_id, content_id, wiiu_endpoint)
|
||||
content_list.append(content)
|
||||
return content_list
|
||||
@@ -4,6 +4,9 @@
|
||||
# This file defines general functions that may be useful in other modules of libWiiPy. Putting them here cuts down on
|
||||
# clutter in other files.
|
||||
|
||||
import binascii
|
||||
|
||||
|
||||
def align_value(value, alignment=64) -> int:
|
||||
"""
|
||||
Aligns the provided value to the set alignment (defaults to 64).
|
||||
@@ -26,22 +29,43 @@ 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
|
||||
|
||||
|
||||
def convert_tid_to_iv(title_id: str) -> bytes:
|
||||
title_key_iv = b''
|
||||
if type(title_id) is bytes:
|
||||
# This catches the format b'0000000100000002'
|
||||
if len(title_id) == 16:
|
||||
title_key_iv = binascii.unhexlify(title_id)
|
||||
# This catches the format b'\x00\x00\x00\x01\x00\x00\x00\x02'
|
||||
elif len(title_id) == 8:
|
||||
pass
|
||||
# If it isn't one of those lengths, it cannot possibly be valid, so reject it.
|
||||
else:
|
||||
raise ValueError("Title ID is not valid!")
|
||||
# Allow for a string like "0000000100000002"
|
||||
elif type(title_id) is str:
|
||||
title_key_iv = binascii.unhexlify(title_id)
|
||||
# If the Title ID isn't bytes or a string, it isn't valid and is rejected.
|
||||
else:
|
||||
raise TypeError("Title ID type is not valid! It must be either type str or bytes.")
|
||||
return title_key_iv
|
||||
|
||||
@@ -99,10 +99,9 @@ class Ticket:
|
||||
self.console_id = int.from_bytes(ticket_data.read(4))
|
||||
# Title ID.
|
||||
ticket_data.seek(0x1DC)
|
||||
self.title_id = ticket_data.read(8)
|
||||
self.title_id = binascii.hexlify(ticket_data.read(8))
|
||||
# Title ID (as a string).
|
||||
title_id_hex = binascii.hexlify(self.title_id)
|
||||
self.title_id_str = str(title_id_hex.decode())
|
||||
self.title_id_str = str(self.title_id.decode())
|
||||
# Unknown data 1.
|
||||
ticket_data.seek(0x1E4)
|
||||
self.unknown1 = ticket_data.read(2)
|
||||
@@ -147,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(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:
|
||||
"""
|
||||
|
||||
@@ -70,6 +70,9 @@ class Title:
|
||||
wad_data : bytes
|
||||
The raw data of the WAD.
|
||||
"""
|
||||
# Set WAD type to ib if the title being packed is boot2.
|
||||
if self.tmd.title_id == "0000000100000001":
|
||||
self.wad.wad_type = "ib"
|
||||
# Dump the TMD and set it in the WAD.
|
||||
self.wad.set_tmd_data(self.tmd.dump())
|
||||
# Dump the Ticket and set it in the WAD.
|
||||
|
||||
@@ -35,8 +35,8 @@ class TMD:
|
||||
self.sig: bytes = b''
|
||||
self.issuer: bytes = b'' # Follows the format "Root-CA%08x-CP%08x"
|
||||
self.tmd_version: int = 0 # This seems to always be 0 no matter what?
|
||||
self.ca_crl_version: int = 0
|
||||
self.signer_crl_version: int = 0
|
||||
self.ca_crl_version: int = 0 # Certificate Authority Certificate Revocation List version
|
||||
self.signer_crl_version: int = 0 # Certificate Policy Certificate Revocation List version
|
||||
self.vwii: int = 0 # Whether the title is for the vWii. 0 = No, 1 = Yes
|
||||
self.ios_tid: str = "" # The Title ID of the IOS version the associated title runs on.
|
||||
self.ios_version: int = 0 # The IOS version the associated title runs on.
|
||||
@@ -44,17 +44,19 @@ class TMD:
|
||||
self.content_type: str = "" # The type of content contained within the associated title.
|
||||
self.group_id: int = 0 # The ID of the publisher of the associated title.
|
||||
self.region: int = 0 # The ID of the region of the associated title.
|
||||
self.ratings: bytes = b''
|
||||
self.ratings: bytes = b'' # The parental controls rating of the associated title.
|
||||
self.reserved1: bytes = b'' # Unknown data labeled "Reserved" on WiiBrew.
|
||||
self.ipc_mask: bytes = b''
|
||||
self.reserved2: bytes = b'' # Other "Reserved" data from WiiBrew.
|
||||
self.access_rights: bytes = b''
|
||||
self.title_version: int = 0 # The version of the associated title.
|
||||
self.num_contents: int = 0 # The number of contents contained in the associated title.
|
||||
self.boot_index: int = 0
|
||||
self.boot_index: int = 0 # The content index that contains the bootable executable.
|
||||
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 TMD object. This allows for manipulating an already
|
||||
existing TMD.
|
||||
|
||||
Parameters
|
||||
@@ -74,10 +76,10 @@ class TMD:
|
||||
# TMD version, seems to usually be 0, but I've seen references to other numbers.
|
||||
tmd_data.seek(0x180)
|
||||
self.tmd_version = int.from_bytes(tmd_data.read(1))
|
||||
# Root certificate crl version.
|
||||
# Certificate Authority CRL version.
|
||||
tmd_data.seek(0x181)
|
||||
self.ca_crl_version = int.from_bytes(tmd_data.read(1))
|
||||
# Signer crl version.
|
||||
# Certificate Policy CRL version.
|
||||
tmd_data.seek(0x182)
|
||||
self.signer_crl_version = int.from_bytes(tmd_data.read(1))
|
||||
# If this is a vWii title or not.
|
||||
@@ -103,16 +105,22 @@ class TMD:
|
||||
# Publisher of the title.
|
||||
tmd_data.seek(0x198)
|
||||
self.group_id = int.from_bytes(tmd_data.read(2))
|
||||
# Region of the title, 0 = JAP, 1 = USA, 2 = EUR, 3 = NONE, 4 = KOR.
|
||||
# Region of the title, 0 = JAP, 1 = USA, 2 = EUR, 3 = WORLD, 4 = KOR.
|
||||
tmd_data.seek(0x19C)
|
||||
region_hex = tmd_data.read(2)
|
||||
self.region = int.from_bytes(region_hex)
|
||||
# Likely the localized content rating for the title. (ESRB, CERO, PEGI, etc.)
|
||||
# Content rating of the title for parental controls. Likely based on ESRB, CERO, PEGI, etc. rating.
|
||||
tmd_data.seek(0x19E)
|
||||
self.ratings = tmd_data.read(16)
|
||||
# "Reserved" data 1.
|
||||
tmd_data.seek(0x1AE)
|
||||
self.reserved1 = tmd_data.read(12)
|
||||
# IPC mask.
|
||||
tmd_data.seek(0x1BA)
|
||||
self.ipc_mask = tmd_data.read(12)
|
||||
# "Reserved" data 2.
|
||||
tmd_data.seek(0x1C6)
|
||||
self.reserved2 = tmd_data.read(18)
|
||||
# Access rights of the title; DVD-video access and AHBPROT.
|
||||
tmd_data.seek(0x1D8)
|
||||
self.access_rights = tmd_data.read(4)
|
||||
@@ -125,7 +133,7 @@ class TMD:
|
||||
# The number of contents listed in the TMD.
|
||||
tmd_data.seek(0x1DE)
|
||||
self.num_contents = int.from_bytes(tmd_data.read(2))
|
||||
# Content index in content list that contains the boot file.
|
||||
# The content index that contains the bootable executable.
|
||||
tmd_data.seek(0x1E0)
|
||||
self.boot_index = int.from_bytes(tmd_data.read(2))
|
||||
# Get content records for the number of contents in num_contents.
|
||||
@@ -148,78 +156,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)
|
||||
# Certificate Authority CRL version.
|
||||
tmd_data += int.to_bytes(self.ca_crl_version, 1)
|
||||
# Certificate Policy 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)
|
||||
# Parental Controls Ratings.
|
||||
tmd_data += self.ratings
|
||||
# "Reserved" 1.
|
||||
tmd_data += self.reserved1
|
||||
# IPC mask.
|
||||
tmd_data += self.ipc_mask
|
||||
# "Reserved" 2.
|
||||
tmd_data += self.reserved2
|
||||
# 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 +236,7 @@ class TMD:
|
||||
case 2:
|
||||
return "EUR"
|
||||
case 3:
|
||||
return "NONE"
|
||||
return "WORLD"
|
||||
case 4:
|
||||
return "KOR"
|
||||
|
||||
@@ -257,7 +259,7 @@ class TMD:
|
||||
Gets the type of the TMD's associated title.
|
||||
|
||||
Can be one of several possible values:
|
||||
'System', 'Game', 'Channel', 'SystemChannel', 'GameWithChannel', or 'HiddenChannel'
|
||||
'System', 'Game', 'Channel', 'SystemChannel', 'GameChannel', or 'HiddenChannel'
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -275,7 +277,7 @@ class TMD:
|
||||
case '00010002':
|
||||
return "SystemChannel"
|
||||
case '00010004':
|
||||
return "GameWithChannel"
|
||||
return "GameChannel"
|
||||
case '00010005':
|
||||
return "DLC"
|
||||
case '00010008':
|
||||
|
||||
@@ -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,15 +60,14 @@ 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)
|
||||
wad_magic = str(wad_magic_hex.decode())
|
||||
if wad_magic != "0000002049730000":
|
||||
raise TypeError("This does not appear to be a valid WAD file, or is a boot2 WAD, which is not currently"
|
||||
" supported by this library.")
|
||||
if wad_magic != "0000002049730000" and wad_magic != "0000002069620000":
|
||||
raise TypeError("This does not appear to be a valid WAD file.")
|
||||
# ====================================================================================
|
||||
# Get the sizes of each data region contained within the WAD.
|
||||
# ====================================================================================
|
||||
@@ -141,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:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user