Files
itaskaev2andClaude Opus 5.5 444e6f5b51 Download NVIDIA DLSS in the setup for RTX GPUs
dlaa and the DLSS modes need nvngx_dlss.dll next to totk.exe, which the release does not ship, so most players
never had DLSS. A new setup step after prepare downloads NVIDIA's own copy from the DLSS SDK repository
(github.com/NVIDIA/DLSS, tag v310.9.1, NVIDIA RTX SDK license) and checks its size and SHA-256.

The window has a tick box for it, on by default and named with its source and licence; the CLI takes --dlss. The
step runs on Windows with an NVIDIA RTX GPU (the host check's Vulkan devices), keeps a DLSS library the player
already put there, and on a failed download warns and lets the game run without DLSS; the next setup run tries
again. Texts in English, Russian, Brazilian Portuguese and Simplified Chinese, docs/installer.md step 7, tests in
tests/installer_test.py (DlssTest, with a file: URL).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:36:07 +03:00

95 lines
4.6 KiB
Python

"""Command line: python -m installer --base FILE --update FILE --keys FILE --lang CODE --dest DIR [--firmware DIR]
[--root DIR] [--temp DIR] [--verify] [--dlss]
A first install needs every option. --firmware names the Switch firmware folder (an unpacked firmware or an emulator's
Contents/registered); the setup extracts the system shared fonts from it when the language is Chinese, Korean or
Traditional Chinese. On a folder with totk-install.json (resume after a cancel or failure, or update with a newer
release) only --dest is needed: --lang defaults to the recorded language, and the game files are needed only when the
extraction has to run again. Prints JSON events on stdout (events.py). Exit code: 0 done, 1 failed, 2 bad command
line, 3 cancelled (Ctrl+C or Ctrl+Break).
--dlss downloads NVIDIA's DLSS library (nvngx_dlss.dll) from NVIDIA's DLSS SDK repository into the install folder on
Windows with an NVIDIA RTX GPU (steps.py, Dlss).
"""
import argparse
import hashlib
import signal
import sys
import time
from pathlib import Path
from . import layout as layouts
from . import state as statefile
from .events import Reporter
from .runner import Context, run_steps
from .steps import all_steps, migrate_state
EXIT = {'ok': 0, 'failed': 1, 'cancelled': 3}
class Parser(argparse.ArgumentParser):
def error(self, message):
Reporter().error('cli.usage', {'detail': message})
sys.exit(2)
# The temp folder inside the install folder when the layout names none (the Linux release); the check step does not
# count it when it requires an empty install folder
TEMP_IN_DEST = '.setup-temp'
def temp_folder(base, dest):
"""One temp folder per install folder, so two installs never share generated files."""
tag = hashlib.sha256(str(dest).lower().encode()).hexdigest()[:8]
return base / f'{dest.name or "root"}-{tag}'
def cancel(signum, frame):
raise KeyboardInterrupt
def main(argv=None):
ap = Parser(prog='python -m installer', description=__doc__.splitlines()[0])
ap.add_argument('--base', type=Path, help='base game, XCI or NSP (or NSZ, XCZ: unpacked first)')
ap.add_argument('--update', type=Path, help='the update NSP or NSZ: 1.1.0, 1.2.1 or 1.4.3')
ap.add_argument('--keys', type=Path, help='prod.keys')
ap.add_argument('--firmware', type=Path, help='Switch firmware folder (Contents/registered) for the shared fonts '
'of Chinese, Korean and Traditional Chinese text')
ap.add_argument('--lang', help='game language, a Switch language code (en-US, en-GB, ja, de, ru, ...)')
ap.add_argument('--dest', type=Path, required=True, help='install folder')
ap.add_argument('--root', type=Path, help='release folder or repository (default: the folder above installer/)')
ap.add_argument('--temp', type=Path, help='temp folder (default: temp/ of the release on Windows, .setup-temp in '
'the install folder on Linux, build/install-temp in a repository)')
ap.add_argument('--verify', action='store_true', help='hash every kept file, also those this release made')
ap.add_argument('--dlss', action='store_true', help="download NVIDIA's nvngx_dlss.dll (NVIDIA RTX SDK license) next "
'to the game, for dlaa and the DLSS modes')
args = ap.parse_args(argv)
# The window cancels with Ctrl+Break to the installer's process group (CREATE_NEW_PROCESS_GROUP), which also reaches
# the tools it runs; Python's default for it ends the process without recording anything.
for name in ('SIGBREAK', 'SIGTERM'):
if hasattr(signal, name):
signal.signal(getattr(signal, name), cancel)
layout = layouts.find(args.root)
dest = args.dest.resolve()
base = args.temp or layout.temp
temp = temp_folder(base.resolve(), dest) if base else dest / TEMP_IN_DEST
log = temp / 'logs' / time.strftime('install-%Y%m%d-%H%M%S.log')
rep = Reporter(log_path=log)
t0 = time.monotonic()
try:
state = statefile.load(dest)
except statefile.StateError as e:
rep.error(e.id, {'path': str(e.path)})
rep.emit('end', result='failed', seconds=0)
return 1
ctx = Context(layout, dest, temp, rep, args, state=state, verify=args.verify)
migrate_state(ctx)
steps = all_steps()
rep.emit('start', release=layout.release, mode=ctx.mode, dev=layout.dev, dest=str(dest), log=str(log),
steps=[s.id for s in steps])
result = run_steps(steps, ctx)
rep.emit('end', result=result, seconds=round(time.monotonic() - t0, 1))
rep.close()
return EXIT[result]