mirror of
https://github.com/NinjaCheetah/NUSGet.git
synced 2025-04-25 15:11:02 -04:00
117 lines
6.4 KiB
Python
117 lines
6.4 KiB
Python
# "modules/download_dsi.py", licensed under the MIT license
|
|
# Copyright 2024-2025 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
|