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 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 Parameters
---------- ----------
title_id : str title_id : str
The Title ID of the title you wish to download data for. The Title ID of the title to download.
title_version : int, default: None title_version : int, option
The version of the title you wish to download data for. Will assume latest if this is "None". The version of the title to download. Defaults to latest if not set.
"""
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.
Returns Returns
------- -------
@ -47,20 +33,28 @@ class NUSDownloader:
# First, create the new title. # First, create the new title.
title = Title() title = Title()
# Download and load the TMD, Ticket, and certs. # Download and load the TMD, Ticket, and certs.
title.load_tmd(self.download_tmd()) title.load_tmd(download_tmd(title_id, title_version))
title.load_ticket(self.download_ticket()) title.load_ticket(download_ticket(title_id))
title.wad.set_cert_data(self.download_cert()) title.wad.set_cert_data(download_cert())
# Download all contents # Download all contents
title.load_content_records() 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 the completed title.
return 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 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. 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 Returns
------- -------
bytes 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 # Build the download URL. The structure is download/<TID>/tmd for latest and download/<TID>/tmd.<version> for
# when a specific version is requested. # 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. # Add the version to the URL if one was specified.
if self.title_version is not None: if title_version is not None:
tmd_url += "." + str(self.title_version) tmd_url += "." + str(title_version)
# Make the request. # Make the request.
tmd_response = urllib3.request(method='GET', url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}) 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. # 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" raise ValueError("The requested Title ID or TMD version does not exist. Please check the Title ID and Title"
" version and then try again.") " version and then try again.")
# Save the raw TMD. # 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. # Use a TMD object to load the data and then return only the actual TMD.
tmd_temp = TMD() tmd_temp = TMD()
tmd_temp.load(self.raw_tmd) tmd_temp.load(raw_tmd)
self.tmd = tmd_temp.dump() tmd = tmd_temp.dump()
return self.tmd 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 Downloads the Ticket of the Title specified in the object. This will only work if the Title ID specified is for
a free title. a free title.
Parameters
----------
title_id : str
The Title ID of the title to download the Ticket for.
Returns Returns
------- -------
bytes 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 # Build the download URL. The structure is download/<TID>/cetk, and cetk will only exist if this is a free
# title. # 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. # Make the request.
ticket_response = urllib3.request(method='GET', url=ticket_url, headers={'User-Agent': 'wii libnup/1.0'}) ticket_response = urllib3.request(method='GET', url=ticket_url, headers={'User-Agent': 'wii libnup/1.0'})
if ticket_response.status != 200: if ticket_response.status != 200:
raise ValueError("The requested Title ID does not exist, or refers to a non-free title. Tickets can only" 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.") " be downloaded for titles that are free on the NUS.")
# Save the raw cetk file. # 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. # Use a Ticket object to load only the Ticket data from cetk and return it.
ticket_temp = Ticket() ticket_temp = Ticket()
ticket_temp.load(self.cetk) ticket_temp.load(cetk)
self.ticket = ticket_temp.dump() ticket = ticket_temp.dump()
return self.ticket 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. 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. # XS data.
cert_data.write(cetk[0x2A4:0x2A4 + 768]) cert_data.write(cetk[0x2A4:0x2A4 + 768])
cert_data.seek(0x0) 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. # 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.") 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. Downloads a specified content for the title specified in the object.
Parameters Parameters
---------- ----------
title_id : str
The Title ID of the title to download content from.
content_id : int content_id : int
The Content ID of the content you wish to download. The Content ID of the content you wish to download.
@ -159,7 +163,7 @@ class NUSDownloader:
content_id_hex = hex(content_id)[2:] content_id_hex = hex(content_id)[2:]
if len(content_id_hex) < 2: if len(content_id_hex) < 2:
content_id_hex = "0" + content_id_hex 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. # Make the request.
content_response = urllib3.request(method='GET', url=content_url, headers={'User-Agent': 'wii libnup/1.0'}) content_response = urllib3.request(method='GET', url=content_url, headers={'User-Agent': 'wii libnup/1.0'})
if content_response.status != 200: if content_response.status != 200:
@ -168,13 +172,16 @@ class NUSDownloader:
content_id_hex) content_id_hex)
return content_response.data 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 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. so that the content records can be accessed.
Parameters Parameters
---------- ----------
title_id : str
The Title ID of the title to download content from.
tmd : TMD tmd : TMD
The TMD that matches the title that the contents being downloaded are from. 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: for content_record in content_records:
content_ids.append(content_record.content_id) 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. # 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: for content_id in content_ids:
# Call self.download_content() for each Content ID. # Call self.download_content() for each Content ID.
content = self.download_content(content_id) content = download_content(title_id, content_id)
self.content_list.append(content) content_list.append(content)
return self.content_list return content_list