mirror of
https://github.com/NinjaCheetah/WiiPy.git
synced 2025-04-26 13:21:01 -04:00
Began rewriting NUS subcommand to allow for more options
New command uses its own subcommands. Currently only offers "title", which supports downloading a WAD using --wad or downloading to a folder using --output. More subcommands will be added. Verbose output is also the default now and --verbose has been removed.
This commit is contained in:
parent
436189659d
commit
475f82aa18
147
modules/nus.py
147
modules/nus.py
@ -1,22 +1,21 @@
|
|||||||
# "nus.py" from WiiPy by NinjaCheetah
|
# "nus.py" from WiiPy by NinjaCheetah
|
||||||
# https://github.com/NinjaCheetah/WiiPy
|
# https://github.com/NinjaCheetah/WiiPy
|
||||||
|
|
||||||
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import libWiiPy
|
import libWiiPy
|
||||||
|
|
||||||
|
|
||||||
def handle_nus(args):
|
def handle_nus_title(args):
|
||||||
title_version = None
|
title_version = None
|
||||||
file_path = None
|
wad_file = None
|
||||||
|
output_dir = None
|
||||||
|
can_decrypt = False
|
||||||
tid = args.tid
|
tid = args.tid
|
||||||
if args.wii:
|
if args.wii:
|
||||||
use_wiiu_servers = False
|
wiiu_nus_enabled = False
|
||||||
else:
|
else:
|
||||||
use_wiiu_servers = True
|
wiiu_nus_enabled = True
|
||||||
if args.verbose:
|
|
||||||
verbose = True
|
|
||||||
else:
|
|
||||||
verbose = False
|
|
||||||
|
|
||||||
# Check if --version was passed, because it'll be None if it wasn't.
|
# Check if --version was passed, because it'll be None if it wasn't.
|
||||||
if args.version is not None:
|
if args.version is not None:
|
||||||
@ -26,70 +25,112 @@ def handle_nus(args):
|
|||||||
print("Enter a valid integer for the Title Version.")
|
print("Enter a valid integer for the Title Version.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# If --output was passed, then save the file to the specified path (as long as it's valid).
|
# If --wad was passed, check to make sure the path is okay.
|
||||||
|
if args.wad is not None:
|
||||||
|
wad_file = pathlib.Path(args.wad)
|
||||||
|
if wad_file.suffix != ".wad":
|
||||||
|
wad_file = wad_file.with_suffix(".wad")
|
||||||
|
|
||||||
|
# If --output was passed, make sure the directory either doesn't exist or is empty.
|
||||||
if args.output is not None:
|
if args.output is not None:
|
||||||
file_path = pathlib.Path(args.output)
|
output_dir = pathlib.Path(args.output)
|
||||||
if not file_path.parent.exists() or not file_path.parent.is_dir():
|
if output_dir.exists():
|
||||||
print("The specified output path does not exist!")
|
if output_dir.is_dir() and next(os.scandir(output_dir), None):
|
||||||
return
|
raise ValueError("Output folder is not empty!")
|
||||||
if file_path.suffix != ".wad":
|
elif output_dir.is_file():
|
||||||
file_path = file_path.with_suffix(".wad")
|
raise ValueError("A file already exists with the provided directory name!")
|
||||||
|
else:
|
||||||
|
os.mkdir(output_dir)
|
||||||
|
|
||||||
# Download the title from the NUS. This is done "manually" (as opposed to using download_title()) so that we can
|
# Download the title from the NUS. This is done "manually" (as opposed to using download_title()) so that we can
|
||||||
# provide verbose output if desired.
|
# provide verbose output.
|
||||||
title = libWiiPy.title.Title()
|
title = libWiiPy.title.Title()
|
||||||
|
|
||||||
# Announce the title being downloaded, and the version if applicable.
|
# Announce the title being downloaded, and the version if applicable.
|
||||||
if verbose:
|
|
||||||
if title_version is not None:
|
|
||||||
print("Downloading title " + tid + " v" + str(title_version) + ", please wait...")
|
|
||||||
else:
|
|
||||||
print("Downloading title " + tid + " vLatest, please wait...")
|
|
||||||
|
|
||||||
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
|
|
||||||
if verbose:
|
|
||||||
print(" - Downloading and parsing TMD...")
|
|
||||||
if title_version is not None:
|
if title_version is not None:
|
||||||
title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=use_wiiu_servers))
|
print("Downloading title " + tid + " v" + str(title_version) + ", please wait...")
|
||||||
else:
|
else:
|
||||||
title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=use_wiiu_servers))
|
print("Downloading title " + tid + " vLatest, please wait...")
|
||||||
|
print(" - Downloading and parsing TMD...")
|
||||||
|
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
|
||||||
|
if title_version is not None:
|
||||||
|
title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled))
|
||||||
|
else:
|
||||||
|
title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled))
|
||||||
|
title_version = title.tmd.title_version
|
||||||
|
# Write out the TMD to a file.
|
||||||
|
if output_dir is not None:
|
||||||
|
tmd_out = open(output_dir.joinpath("tmd." + str(title_version)), "wb")
|
||||||
|
tmd_out.write(title.tmd.dump())
|
||||||
|
tmd_out.close()
|
||||||
|
|
||||||
# Download and parse the Ticket.
|
# Download the ticket, if we can.
|
||||||
if verbose:
|
print(" - Downloading and parsing Ticket...")
|
||||||
print(" - Downloading and parsing Ticket...")
|
|
||||||
try:
|
try:
|
||||||
title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=use_wiiu_servers))
|
title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled))
|
||||||
|
can_decrypt = True
|
||||||
|
if output_dir is not None:
|
||||||
|
ticket_out = open(output_dir.joinpath("tik"), "wb")
|
||||||
|
ticket_out.write(title.ticket.dump())
|
||||||
|
ticket_out.close()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If libWiiPy returns an error, then no ticket is available, so we can't continue.
|
# If libWiiPy returns an error, then no ticket is available. Log this, and disable options requiring a
|
||||||
print("No Ticket is available for this title! Exiting...")
|
# ticket so that they aren't attempted later.
|
||||||
return
|
print(" - No Ticket is available!")
|
||||||
|
if wad_file is not None and output_dir is None:
|
||||||
|
print("--wad was passed, but this title cannot be packed into a WAD!")
|
||||||
|
return
|
||||||
|
|
||||||
# Load the content records from the TMD, and begin iterating over the records.
|
# Load the content records from the TMD, and begin iterating over the records.
|
||||||
title.load_content_records()
|
title.load_content_records()
|
||||||
content_list = []
|
content_list = []
|
||||||
for content in range(len(title.tmd.content_records)):
|
for content in range(len(title.tmd.content_records)):
|
||||||
if verbose:
|
# Generate the content file name by converting the Content ID to hex and then removing the 0x.
|
||||||
print(" - Downloading content " + str(content + 1) + " of " +
|
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
|
||||||
str(len(title.tmd.content_records)) + " (Content ID: " +
|
while len(content_file_name) < 8:
|
||||||
str(title.tmd.content_records[content].content_id) + ", Size: " +
|
content_file_name = "0" + content_file_name
|
||||||
str(title.tmd.content_records[content].content_size) + " bytes)...")
|
print(" - Downloading content " + str(content + 1) + " of " +
|
||||||
|
str(len(title.tmd.content_records)) + " (Content ID: " +
|
||||||
|
str(title.tmd.content_records[content].content_id) + ", Size: " +
|
||||||
|
str(title.tmd.content_records[content].content_size) + " bytes)...")
|
||||||
content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id,
|
content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id,
|
||||||
wiiu_endpoint=use_wiiu_servers))
|
wiiu_endpoint=wiiu_nus_enabled))
|
||||||
if verbose:
|
print(" - Done!")
|
||||||
print(" - Done!")
|
# If we're supposed to be outputting to a folder, then write these files out.
|
||||||
|
if output_dir is not None:
|
||||||
|
enc_content_out = open(output_dir.joinpath(content_file_name), "wb")
|
||||||
|
enc_content_out.write(content_list[content])
|
||||||
|
enc_content_out.close()
|
||||||
title.content.content_list = content_list
|
title.content.content_list = content_list
|
||||||
|
|
||||||
# Get the WAD certificate chain.
|
# Try to decrypt the contents for this title if a ticket was available.
|
||||||
if verbose:
|
if can_decrypt is True and output_dir is not None:
|
||||||
|
for content in range(len(title.tmd.content_records)):
|
||||||
|
print(" - Decrypting content " + str(content + 1) + " of " + str(len(title.tmd.content_records)) +
|
||||||
|
" (Content ID: " + str(title.tmd.content_records[content].content_id) + ")...")
|
||||||
|
dec_content = title.get_content_by_index(content)
|
||||||
|
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
|
||||||
|
while len(content_file_name) < 8:
|
||||||
|
content_file_name = "0" + content_file_name
|
||||||
|
content_file_name = content_file_name + ".app"
|
||||||
|
dec_content_out = open(output_dir.joinpath(content_file_name), "wb")
|
||||||
|
dec_content_out.write(dec_content)
|
||||||
|
dec_content_out.close()
|
||||||
|
else:
|
||||||
|
print("Title has no Ticket, so content will not be decrypted!")
|
||||||
|
|
||||||
|
# If --wad was passed, pack a WAD and output that.
|
||||||
|
if wad_file is not None:
|
||||||
|
# Get the WAD certificate chain.
|
||||||
print(" - Building certificate...")
|
print(" - Building certificate...")
|
||||||
title.wad.set_cert_data(libWiiPy.title.download_cert(wiiu_endpoint=use_wiiu_servers))
|
title.wad.set_cert_data(libWiiPy.title.download_cert(wiiu_endpoint=wiiu_nus_enabled))
|
||||||
|
# Ensure that the path ends in .wad, and add that if it doesn't.
|
||||||
# If we haven't gotten a name yet, make one from the TID and version.
|
print("Packing WAD...")
|
||||||
if file_path is None:
|
if wad_file.suffix != ".wad":
|
||||||
file_path = pathlib.Path(args.tid + "-v" + str(title.tmd.title_version) + ".wad")
|
wad_file = wad_file.with_suffix(".wad")
|
||||||
|
# Have libWiiPy dump the WAD, and write that data out.
|
||||||
wad_file = open(file_path, "wb")
|
file = open(wad_file, "wb")
|
||||||
wad_file.write(title.dump_wad())
|
file.write(title.dump_wad())
|
||||||
wad_file.close()
|
file.close()
|
||||||
|
|
||||||
print("Downloaded title with Title ID \"" + args.tid + "\"!")
|
print("Downloaded title with Title ID \"" + args.tid + "\"!")
|
||||||
|
@ -100,7 +100,7 @@ def handle_wad(args):
|
|||||||
if output_path.is_dir() and next(os.scandir(output_path), None):
|
if output_path.is_dir() and next(os.scandir(output_path), None):
|
||||||
raise ValueError("Output folder is not empty!")
|
raise ValueError("Output folder is not empty!")
|
||||||
elif output_path.is_file():
|
elif output_path.is_file():
|
||||||
raise ValueError("A file already exists with the provided name!")
|
raise ValueError("A file already exists with the provided directory name!")
|
||||||
else:
|
else:
|
||||||
os.mkdir(output_path)
|
os.mkdir(output_path)
|
||||||
|
|
||||||
|
31
wiipy.py
31
wiipy.py
@ -14,7 +14,7 @@ if __name__ == "__main__":
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="WiiPy is a simple command line tool to manage file formats used by the Wii.")
|
description="WiiPy is a simple command line tool to manage file formats used by the Wii.")
|
||||||
parser.add_argument("--version", action="version",
|
parser.add_argument("--version", action="version",
|
||||||
version=f"WiiPy v1.1.0, based on libWiiPy v{version('libWiiPy')} (from branch \'main\')")
|
version=f"WiiPy v1.2.0, based on libWiiPy v{version('libWiiPy')} (from branch \'main\')")
|
||||||
subparsers = parser.add_subparsers(dest="subcommand", required=True)
|
subparsers = parser.add_subparsers(dest="subcommand", required=True)
|
||||||
|
|
||||||
# Argument parser for the WAD subcommand.
|
# Argument parser for the WAD subcommand.
|
||||||
@ -30,17 +30,24 @@ if __name__ == "__main__":
|
|||||||
action="store_true")
|
action="store_true")
|
||||||
|
|
||||||
# Argument parser for the NUS subcommand.
|
# Argument parser for the NUS subcommand.
|
||||||
nus_parser = subparsers.add_parser("nus", help="download a title from the NUS",
|
nus_parser = subparsers.add_parser("nus", help="download data from the NUS",
|
||||||
description="download a title from the NUS")
|
description="download from the NUS")
|
||||||
nus_parser.set_defaults(func=handle_nus)
|
nus_subparsers = nus_parser.add_subparsers(dest="subcommand", required=True)
|
||||||
nus_parser.add_argument("tid", metavar="TID", type=str, help="Title ID to download")
|
# Title NUS subcommand.
|
||||||
nus_parser.add_argument("-v", "--version", metavar="VERSION", type=int,
|
nus_title_parser = nus_subparsers.add_parser("title", help="download a title from the NUS",
|
||||||
help="version to download (optional)")
|
description="download a title from the NUS")
|
||||||
nus_parser.add_argument("-o", "--output", metavar="OUT", type=str, help="output file (optional)")
|
nus_title_parser.set_defaults(func=handle_nus_title)
|
||||||
nus_parser.add_argument("--verbose", help="output more information about the current download",
|
nus_title_parser.add_argument("tid", metavar="TID", type=str, help="Title ID to download")
|
||||||
action="store_true")
|
nus_title_parser.add_argument("-v", "--version", metavar="VERSION", type=int,
|
||||||
nus_parser.add_argument("-w", "--wii", help="use original Wii NUS instead of the Wii U servers",
|
help="version to download (optional)")
|
||||||
action="store_true")
|
nus_title_out_group_label = nus_title_parser.add_argument_group(title="output types (required)")
|
||||||
|
nus_title_out_group = nus_title_out_group_label.add_mutually_exclusive_group(required=True)
|
||||||
|
nus_title_out_group.add_argument("-o", "--output", metavar="OUT", type=str,
|
||||||
|
help="download the title to a folder")
|
||||||
|
nus_title_out_group.add_argument("-w", "--wad", metavar="WAD", type=str,
|
||||||
|
help="pack a wad with the provided name")
|
||||||
|
nus_title_parser.add_argument("--wii", help="use original Wii NUS instead of the Wii U servers",
|
||||||
|
action="store_true")
|
||||||
|
|
||||||
# Argument parser for the U8 subcommand.
|
# Argument parser for the U8 subcommand.
|
||||||
u8_parser = subparsers.add_parser("u8", help="pack/unpack a U8 archive",
|
u8_parser = subparsers.add_parser("u8", help="pack/unpack a U8 archive",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user