NUSGet-After-Dark/modules/download_dsi.py
NinjaCheetah 87da32d62e
Improved WAD name formatting for batch downloads
Also started working on a fix for the checkmark/X icons denoting a ticket not appearing anymore.
2024-12-18 09:08:21 -05:00

117 lines
6.4 KiB
Python

# "modules/download_dsi.py", licensed under the MIT license
# Copyright 2024 NinjaCheetah
import pathlib
from typing import List, Tuple
import libTWLPy
def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_tad_chkbox: bool, keep_enc_chkbox: bool,
decrypt_contents_chkbox: bool, use_local_chkbox: bool, tad_file_name: str,
progress_callback=None):
# Actual NUS download function that runs in a separate thread, but DSi flavored.
# Immediately knock out any invalidly formatted Title IDs.
if len(tid) != 16:
return -1
# An error here is acceptable, because it may just mean the box is empty. Or the version string is nonsense.
# Either way, just fall back on downloading the latest version of the title.
try:
title_version = int(version)
except ValueError:
title_version = None
# Set variables for these two options so that their state can be compared against the user's choices later.
pack_tad_enabled = pack_tad_chkbox
decrypt_contents_enabled = decrypt_contents_chkbox
# Create a new libTWLPy Title.
title = libTWLPy.Title()
# Make a directory for this title if it doesn't exist.
title_dir = out_folder.joinpath(tid)
title_dir.mkdir(exist_ok=True)
# Announce the title being downloaded, and the version if applicable.
if title_version is not None:
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...")
else:
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...")
progress_callback.emit(" - Downloading and parsing TMD...")
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
try:
if title_version is not None:
title.load_tmd(libTWLPy.download_tmd(tid, title_version))
else:
title.load_tmd(libTWLPy.download_tmd(tid))
title_version = title.tmd.title_version
# If libTWLPy returns an error, that means that either the TID or version doesn't exist, so return code -2.
except ValueError:
return -2
# Make a directory for this version if it doesn't exist.
version_dir = title_dir.joinpath(str(title_version))
version_dir.mkdir(exist_ok=True)
# Write out the TMD to a file.
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump())
# Use a local ticket, if one exists and "use local files" is enabled.
if use_local_chkbox and version_dir.joinpath("tik").exists():
progress_callback.emit(" - Parsing local copy of Ticket...")
title.load_ticket(version_dir.joinpath("tik").read_bytes())
else:
progress_callback.emit(" - Downloading and parsing Ticket...")
try:
title.load_ticket(libTWLPy.download_ticket(tid))
version_dir.joinpath("tik").write_bytes(title.ticket.dump())
except ValueError:
# If libTWLPy returns an error, then no ticket is available. Log this, and disable options requiring a
# ticket so that they aren't attempted later.
progress_callback.emit(" - No Ticket is available!")
pack_tad_enabled = False
decrypt_contents_enabled = False
# Load the content record from the TMD, and download the content it lists. DSi titles only have one content.
title.load_content_records()
content_file_name = f"{title.tmd.content_record.content_id:08X}"
# Check for a local copy of the current content if "use local files" is enabled, and use it.
if use_local_chkbox and version_dir.joinpath(content_file_name).exists():
progress_callback.emit(" - Using local copy of content")
content = version_dir.joinpath(content_file_name).read_bytes()
else:
progress_callback.emit(f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: "
f"{title.tmd.content_record.content_size} bytes)...")
content = libTWLPy.download_content(tid, title.tmd.content_record.content_id)
progress_callback.emit(" - Done!")
# If keep encrypted contents is on, write out the content after its downloaded.
if keep_enc_chkbox is True:
version_dir.joinpath(content_file_name).write_bytes(content)
title.content.content = content
# If decrypt local contents is still true, decrypt the content and write out the decrypted file.
if decrypt_contents_enabled is True:
try:
progress_callback.emit(f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...")
dec_content = title.get_content()
content_file_name = f"{title.tmd.content_record.content_id:08X}.app"
version_dir.joinpath(content_file_name).write_bytes(dec_content)
except ValueError:
# If libWiiPy throws an error during decryption, return code -3. This should only be possible if using
# local encrypted contents that have been altered at present.
return -3
# If pack TAD is still true, pack the TMD, ticket, and content into a TAD.
if pack_tad_enabled is True:
# Get the TAD certificate chain, courtesy of libTWLPy.
progress_callback.emit(" - Building certificate...")
title.tad.set_cert_data(libTWLPy.download_cert())
# Use a typed TAD name if there is one, and auto generate one based on the TID and version if there isn't.
progress_callback.emit("Packing TAD...")
if tad_file_name != "" and tad_file_name is not None:
# Batch downloads may insert -vLatest, so if it did we can fill in the real number now.
tad_file_name = tad_file_name.replace("-vLatest", f"-v{title_version}")
if tad_file_name[-4:].lower() != ".tad":
tad_file_name += ".tad"
else:
tad_file_name = f"{tid}-v{title_version}.tad"
# Have libTWLPy dump the TAD, and write that data out.
version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad())
progress_callback.emit("Download complete!")
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this
# point, it means that they enabled decryption or TAD packing for a title that doesn't have a ticket. Return
# code 1 so that a warning popup is shown informing them of this.
if (not pack_tad_enabled and pack_tad_chkbox) or (not decrypt_contents_enabled and decrypt_contents_chkbox):
return 1
return 0