Rewrote nus.py to be functions instead of an NUSDownloader class because the class was unnecessary and made things more complex

This commit is contained in:
Campbell 2024-04-05 00:09:19 -04:00
parent 60918f1a39
commit ccbc2e262b
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344

View File

@ -12,32 +12,18 @@ from .tmd import TMD
from .ticket import Ticket
class NUSDownloader:
def download_title(title_id: str, title_version: int = None) -> Title:
"""
An NUS Downloader class that allows for easy downloading of a title from the NUS.
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 you wish to download data for.
title_version : int, default: None
The version of the title you wish to download data for. Will assume latest if this is "None".
"""
def __init__(self, title_id: str, title_version: int = None):
self.title_id = title_id
self.title_version = title_version
# Data from the NUS
self.raw_tmd: bytes = b''
self.tmd: bytes = b''
self.cetk: bytes = b''
self.cert: bytes = b''
self.ticket: bytes = b''
self.content_list: List[bytes] = []
def download_title(self) -> Title:
"""
Download an entire title and all of its contents, then load the downloaded components into a Title object for
further use.
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.
Returns
-------
@ -47,20 +33,28 @@ class NUSDownloader:
# First, create the new title.
title = Title()
# Download and load the TMD, Ticket, and certs.
title.load_tmd(self.download_tmd())
title.load_ticket(self.download_ticket())
title.wad.set_cert_data(self.download_cert())
title.load_tmd(download_tmd(title_id, title_version))
title.load_ticket(download_ticket(title_id))
title.wad.set_cert_data(download_cert())
# Download all contents
title.load_content_records()
title.content.content_list = self.download_contents(title.tmd)
title.content.content_list = download_contents(title_id, title.tmd)
# Return the completed title.
return title
def download_tmd(self) -> bytes:
def download_tmd(title_id: str, title_version: int = None) -> 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.
Returns
-------
bytes
@ -68,10 +62,10 @@ class NUSDownloader:
"""
# Build the download URL. The structure is download/<TID>/tmd for latest and download/<TID>/tmd.<version> for
# when a specific version is requested.
tmd_url = "http://ccs.shop.wii.com/ccs/download/" + self.title_id + "/tmd"
tmd_url = "http://ccs.shop.wii.com/ccs/download/" + title_id + "/tmd"
# Add the version to the URL if one was specified.
if self.title_version is not None:
tmd_url += "." + str(self.title_version)
if title_version is not None:
tmd_url += "." + str(title_version)
# Make the request.
tmd_response = urllib3.request(method='GET', url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'})
# Handle a 404 if the TID/version doesn't exist.
@ -79,18 +73,24 @@ class NUSDownloader:
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.
self.raw_tmd = tmd_response.data
raw_tmd = tmd_response.data
# Use a TMD object to load the data and then return only the actual TMD.
tmd_temp = TMD()
tmd_temp.load(self.raw_tmd)
self.tmd = tmd_temp.dump()
return self.tmd
tmd_temp.load(raw_tmd)
tmd = tmd_temp.dump()
return tmd
def download_ticket(self) -> bytes:
def download_ticket(title_id: str) -> 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.
Returns
-------
bytes
@ -98,21 +98,22 @@ class NUSDownloader:
"""
# Build the download URL. The structure is download/<TID>/cetk, and cetk will only exist if this is a free
# title.
ticket_url = "http://ccs.shop.wii.com/ccs/download/" + self.title_id + "/cetk"
ticket_url = "http://ccs.shop.wii.com/ccs/download/" + title_id + "/cetk"
# Make the request.
ticket_response = urllib3.request(method='GET', url=ticket_url, headers={'User-Agent': 'wii libnup/1.0'})
if ticket_response.status != 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.
self.cetk = ticket_response.data
cetk = ticket_response.data
# Use a Ticket object to load only the Ticket data from cetk and return it.
ticket_temp = Ticket()
ticket_temp.load(self.cetk)
self.ticket = ticket_temp.dump()
return self.ticket
ticket_temp.load(cetk)
ticket = ticket_temp.dump()
return ticket
def download_cert(self) -> bytes:
def download_cert() -> bytes:
"""
Downloads the signing certificate used by all WADs. This uses System Menu 4.3U as the source.
@ -135,18 +136,21 @@ class NUSDownloader:
# XS data.
cert_data.write(cetk[0x2A4:0x2A4 + 768])
cert_data.seek(0x0)
self.cert = cert_data.read()
cert = cert_data.read()
# Since the cert is always the same, check the hash to make sure nothing went wildly wrong.
if hashlib.sha1(self.cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
if hashlib.sha1(cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
raise Exception("An unknown error has occurred downloading and creating the certificate.")
return self.cert
return cert
def download_content(self, content_id) -> bytes:
def download_content(title_id: str, content_id: int) -> 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.
@ -159,7 +163,7 @@ class NUSDownloader:
content_id_hex = hex(content_id)[2:]
if len(content_id_hex) < 2:
content_id_hex = "0" + content_id_hex
content_url = "http://ccs.shop.wii.com/ccs/download/" + self.title_id + "/000000" + content_id_hex
content_url = "http://ccs.shop.wii.com/ccs/download/" + title_id + "/000000" + content_id_hex
# Make the request.
content_response = urllib3.request(method='GET', url=content_url, headers={'User-Agent': 'wii libnup/1.0'})
if content_response.status != 200:
@ -168,13 +172,16 @@ class NUSDownloader:
content_id_hex)
return content_response.data
def download_contents(self, tmd: TMD) -> List[bytes]:
def download_contents(title_id: str, tmd: TMD) -> 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.
@ -190,8 +197,9 @@ class NUSDownloader:
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 = self.download_content(content_id)
self.content_list.append(content)
return self.content_list
content = download_content(title_id, content_id)
content_list.append(content)
return content_list