Download the pinned Streamline runtime for DLSS Frame Generation, and let the build kit carry it too.

make_launcher.py --dlssg needed --streamline, a Streamline SDK folder unpacked by hand, and make_kit.py could not
carry DLSS Frame Generation at all, so a game built from the kit always left it off. tools/streamline_pin.py now pins
the Streamline 2.14.16 SDK release (its archive's size and SHA-256, and the SHA-256 of each file the game loads):
without --streamline the scripts download the archive once, check it, keep only sl.interposer.dll, sl.common.dll,
sl.dlss_g.dll, sl.pcl.dll, sl.reflex.dll, nvngx_dlssg.dll and the two licence texts, each checked, and delete the
archive. A folder given with --streamline must hold the pinned files.

The bin/dlssg/ assembly moves from make_launcher.py into streamline_pin.place, so the release and the kit write the
same set and the same dlssg-NOTICE.txt: a kit made with --dlssg and the 0.6.0 release's nx2navi_dlssg.dll has a
bin/dlssg/ identical to the 0.6.0 release's, byte for byte. make_kit.py takes --dlssg [--streamline] like the
launcher. nx2navi_dlssg.dll itself still comes from tools/build_dlssg.py.

tests/launcher_test.py test_streamline_pin covers the extraction, the cache, refused archives and folders, and the
notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
itaskaev2
2026-11-09 16:32:32 +01:00
committed by chrg6
parent 417d1a3740
commit 7341eec727
8 changed files with 215 additions and 61 deletions

View File

@@ -123,7 +123,7 @@ folder's `licenses/` and in each component's own folder.
| zlib 1.4 (MSYS2 `mingw-w64-ucrt-x86_64-zlib`), static library | https://zlib.net, Copyright (C) Jean-loup Gailly and Mark Adler | zlib | linked into `NX2navi-Setup.exe` (its unzip) |
| mingw-w64 CRT startup code and winpthreads 14.0.0, GCC 16.2.0's `libgcc` (MSYS2 packages), static | https://www.mingw-w64.org, https://gcc.gnu.org | mingw-w64: ZPL-2.1 / MIT / public domain; libgcc: GPL-3.0 with the GCC Runtime Library Exception | linked into `NX2navi.exe` and `NX2navi-Setup.exe` |
| NVIDIA DLSS 310.9.1 (`nvngx_dlss.dll`, NVIDIA's own build, unmodified; pinned with its size and sha256 in `tools/dlss_pin.py`) | https://github.com/NVIDIA/DLSS @ `v310.9.1` (`lib/Windows_x86_64/rel/nvngx_dlss.dll`), Copyright NVIDIA Corporation | NVIDIA RTX SDKs License (https://github.com/NVIDIA/DLSS/blob/v310.9.1/LICENSE.txt); licensed for systems with NVIDIA GPUs only | `app/<version>/bin/` with `nvngx_dlss-NOTICE.txt`, also in the build kit's `bin/`; `tools/make_game.py` copies both beside the built game, where NGX loads the library (`runtime/vk_dlaa.c`) |
| NVIDIA Streamline 2.14.16 runtime (`sl.interposer.dll`, `sl.common.dll`, `sl.dlss_g.dll`, `sl.pcl.dll`, `sl.reflex.dll`, NVIDIA's own builds, unmodified) and NVIDIA DLSS Frame Generation 310.9.16 (`nvngx_dlssg.dll`, as the Streamline SDK ships it) | https://github.com/NVIDIA-RTX/Streamline, SDK release 2.14.16 (`bin/x64/`), Copyright NVIDIA Corporation | Streamline: MIT; `nvngx_dlssg.dll`: NVIDIA RTX SDKs License, licensed for systems with NVIDIA GPUs only | `app/<version>/bin/dlssg/` with both licence texts and `dlssg-NOTICE.txt` (each file's sha256), when the release is made with `tools/make_launcher.py --dlssg --streamline`; `tools/make_game.py` copies them beside the built game, where `nx2navi_dlssg.dll` (this repository's `runtime/dlssg/`, built with the Streamline SDK's MIT headers) loads them (`runtime/vk_dlssg.c`) |
| NVIDIA Streamline 2.14.16 runtime (`sl.interposer.dll`, `sl.common.dll`, `sl.dlss_g.dll`, `sl.pcl.dll`, `sl.reflex.dll`, NVIDIA's own builds, unmodified) and NVIDIA DLSS Frame Generation 310.9.16 (`nvngx_dlssg.dll`, as the Streamline SDK ships it) | https://github.com/NVIDIA-RTX/Streamline, SDK release 2.14.16 (`bin/x64/`), Copyright NVIDIA Corporation | Streamline: MIT; `nvngx_dlssg.dll`: NVIDIA RTX SDKs License, licensed for systems with NVIDIA GPUs only | `app/<version>/bin/dlssg/` with both licence texts and `dlssg-NOTICE.txt` (each file's sha256), when the release is made with `tools/make_launcher.py --dlssg`, and the build kit's `bin/dlssg/` with `tools/make_kit.py --dlssg` (the SDK release pinned with its size and sha256 and each file's sha256 in `tools/streamline_pin.py`); `tools/make_game.py` copies them beside the built game, where `nx2navi_dlssg.dll` (this repository's `runtime/dlssg/`, built with the Streamline SDK's MIT headers) loads them (`runtime/vk_dlssg.c`) |
| Button prompt art: the 128x128 PNGs of the Input Prompts Pack V7 (`XGamepad`, `P5Gamepad` and `Keyboard_Mouse` styles), unmodified | the pack as licensed to the repository owner; never in this repository | the pack's licence, which allows redistributing these pictures in NX2navi's releases (confirmed by the repository owner, 2026-11-09) | `app/<version>/prompt-art/` and the build kit's `prompt-art/` when made with `--prompt-art` (`tools/prompt_art.py`); `tools/pad_prompts.py` draws the game's Xbox, PlayStation and keyboard and mouse prompts from them on the player's PC |
Downloaded on the player's PC by the launcher, not distributed: the MSYS2 UCRT64 packages that

View File

@@ -119,6 +119,9 @@ beside the game.
- The build copies NVIDIA's DLSS library `nvngx_dlss.dll` (with `nvngx_dlss-NOTICE.txt`, which names its licence)
and FSR 3.1's `amd_fidelityfx_vk.dll` beside `ocarina.exe` from the kit's `bin\`. To use another DLSS version, put
your own `nvngx_dlss.dll` there: later builds keep it.
- A kit with `bin\dlssg\` (made with `tools/make_kit.py --dlssg`) also brings DLSS Frame Generation on GeForce RTX 40
and 50 cards: the build copies `nx2navi_dlssg.dll`, NVIDIA Streamline's runtime, `nvngx_dlssg.dll` and their licence
texts beside `ocarina.exe`, and Frame generation in the Screen menu then offers DLSS too.
- Logs are in `logs\`. When reporting a problem, say where you were in the game and which graphics card you have, and
attach `logs\nx2navi.log`. Never send game files.

View File

@@ -268,6 +268,14 @@ game folder is the default `<launcher>\game`. The dump is never touched.
ship the library; it also asks that a commercial release be announced to NVIDIA first
(https://developer.nvidia.com/sw-notification) and that the use of DLSS be credited.
`--dlssg nx2navi_dlssg.dll` (built by `tools/build_dlssg.py`, which needs Visual Studio's C++ compiler) adds DLSS
Frame Generation in `bin/dlssg/`: beside it NVIDIA Streamline's runtime (`sl.interposer.dll`, `sl.common.dll`,
`sl.dlss_g.dll`, `sl.pcl.dll`, `sl.reflex.dll`) and `nvngx_dlssg.dll`, taken from the Streamline SDK release that
`tools/streamline_pin.py` pins. The script downloads that release once (cached in `toolchain/launcher-dl/`), checks
the archive's size and sha256 and each file's sha256, and writes `dlssg-NOTICE.txt` with the two licence texts;
`make_game.py` copies the whole set beside the game. `--streamline SDK` takes an SDK folder on disk instead (its
files must be the pinned ones).
`--prompt-art FOLDER ...` puts the button art in the release (`app/<version>/prompt-art`): folders that hold
`XGamepad`, `P5Gamepad` and `Keyboard_Mouse` (the Source folders of button prompt art packs), or those three
folders themselves; a set named itself wins over one found inside a folder named, and the same set twice is

View File

@@ -1657,13 +1657,13 @@ class MakeGameTest(Tmp):
def test_dlssg_beside_the_game(self):
"""DLSS Frame Generation's files go beside the game only as a whole set, with the licence texts; the release
script (tools/make_launcher.py) and the build agree on the set."""
import make_launcher
import streamline_pin
from unittest import mock
kit, out = self.tmp / 'kit', self.tmp / 'game'
src = kit / 'bin' / 'dlssg'
src.mkdir(parents=True)
out.mkdir()
self.assertEqual(make_launcher.DLSSG_FILES, self.mg.DLSSG_FILES)
self.assertEqual(streamline_pin.FILES, self.mg.DLSSG_FILES)
with mock.patch.object(self.mg, 'KIT', kit):
for n in self.mg.DLSSG_FILES[:-1]:
(src / n).write_bytes(n.encode())
@@ -1676,6 +1676,58 @@ class MakeGameTest(Tmp):
self.assertEqual((out / n).read_bytes(), (src / n).read_bytes())
self.assertEqual((out / 'streamline-LICENSE.txt').read_text(), 'MIT')
def test_streamline_pin(self):
"""The Streamline files come out of the SDK archive checked one by one; the archive goes once they are out and
a filled cache needs no download; an SDK folder on disk must hold the pinned files; bin/dlssg/ gets the set
make_game.py places, the two licence texts and a notice with every file's sha256."""
import streamline_pin
from unittest import mock
files = {n: f'{n} of the SDK'.encode() for n in streamline_pin.SDK_FILES}
pins = {n: hashlib.sha256(d).hexdigest() for n, d in files.items()}
cache = self.tmp / 'dl'
cache.mkdir()
archive = cache / f'streamline-sdk-v{streamline_pin.VERSION}.zip'
with zipfile.ZipFile(archive, 'w') as z:
for n, d in files.items():
z.writestr(n, d)
z.writestr('bin/x64/sl.nis.dll', b'a plugin the game does not load')
fetched = []
def download(url, dest, sha256=None, size=None): # the archive is already where net.download writes it
fetched.append(url)
self.assertEqual(Path(dest), archive)
return dest
with mock.patch.dict(streamline_pin.SDK_FILES, pins), mock.patch('launcher.net.download', download):
sdk = streamline_pin.resolve(None, cache)
self.assertEqual(fetched, [streamline_pin.URL])
self.assertFalse(archive.exists())
self.assertEqual(streamline_pin.wrong_files(sdk), [])
self.assertFalse((sdk / 'bin' / 'x64' / 'sl.nis.dll').exists())
self.assertEqual(streamline_pin.resolve(None, cache), sdk) # filled: no second download
self.assertEqual(len(fetched), 1)
self.assertEqual(streamline_pin.resolve(sdk, cache), sdk) # a folder on disk with the pinned files
(sdk / 'bin' / 'x64' / 'sl.common.dll').write_bytes(b'another Streamline')
with self.assertRaises(SystemExit):
streamline_pin.resolve(sdk, cache)
(sdk / 'bin' / 'x64' / 'sl.common.dll').write_bytes(files['bin/x64/sl.common.dll'])
with zipfile.ZipFile(archive, 'w') as z: # an archive whose file differs is refused
for n, d in files.items():
z.writestr(n, b'changed' if n == 'license.txt' else d)
with self.assertRaises(SystemExit):
streamline_pin.extract(archive, self.tmp / 'x')
dll = self.tmp / 'nx2navi_dlssg.dll'
dll.write_bytes(b'our frame generation shim')
dest = self.tmp / 'bin' / 'dlssg'
streamline_pin.place(dll, sdk, dest)
self.assertEqual(sorted(p.name for p in dest.iterdir()),
sorted((*streamline_pin.FILES, *streamline_pin.TEXTS, streamline_pin.NOTICE)))
self.assertEqual((dest / 'streamline-LICENSE.txt').read_bytes(), files['license.txt'])
note = (dest / streamline_pin.NOTICE).read_text(encoding='utf-8')
self.assertIn(f'Streamline {streamline_pin.VERSION} SDK', note)
self.assertIn(f'nx2navi_dlssg.dll sha256 {hashlib.sha256(dll.read_bytes()).hexdigest()}', note)
self.assertIn(f'nvngx_dlssg.dll sha256 {pins["bin/x64/nvngx_dlssg.dll"]}', note)
@unittest.skipIf(os.name == 'nt', 'a script stands in for windres.exe')
def test_compile_resources(self):
# The toolchain, the kit and the game under folders with spaces, as under C:\Users\Ali Sayed. windres must

View File

@@ -4,8 +4,9 @@
Streamline's C++ types have MSVC's ABI, so this one DLL is built with Visual Studio's compiler (the Build Tools are
enough; vswhere finds them), against the SDK's headers, which stay where they are. The Vulkan headers are MSYS2's.
A launcher release ships the DLL ready-made beside Streamline's own runtime DLLs (tools/make_launcher.py --dlssg
and --streamline), and tools/make_game.py place_dlssg puts them beside the game.
A launcher release and the build kit ship the DLL ready-made beside Streamline's own runtime DLLs
(tools/make_launcher.py and tools/make_kit.py --dlssg, with the Streamline files tools/streamline_pin.py pins), and
tools/make_game.py place_dlssg puts them beside the game.
"""
import argparse
import os

View File

@@ -1,12 +1,14 @@
"""Assemble the build kit: what a player needs to build Ocarina of Time on their own PC with tools/make_game.py.
python tools/make_kit.py --xlate <dir with nx2navi_xlate.exe and .id> [--ffx amd_fidelityfx_vk.dll]
[--dlss nvngx_dlss.dll | --no-dlss] [--prompt-art FOLDER [FOLDER ...]]
[--out build/kit] [--zip]
[--dlss nvngx_dlss.dll | --no-dlss] [--dlssg nx2navi_dlssg.dll [--streamline SDK]]
[--prompt-art FOLDER [FOLDER ...]] [--out build/kit] [--zip]
The kit holds this repository's runtime/, recomp/, the build tools, the shader translator built from tools/xlate,
NVIDIA's DLSS library with its notice (tools/dlss_pin.py: downloaded from NVIDIA's DLSS SDK repository and checked,
or --dlss; not with --no-dlss), AMD's FSR 3.1 library with --ffx, the button art with --prompt-art, and
or --dlss; not with --no-dlss), DLSS Frame Generation in bin/dlssg/ with --dlssg (nx2navi_dlssg.dll from
tools/build_dlssg.py beside NVIDIA Streamline's runtime, tools/streamline_pin.py: downloaded from NVIDIA's Streamline
SDK release and checked, or --streamline), AMD's FSR 3.1 library with --ffx, the button art with --prompt-art, and
README-BuildKit.md; no game code, data or anything generated from the game. check_content runs over the folder before
it is zipped.
@@ -48,6 +50,10 @@ def main():
ap.add_argument('--dlss', type=Path, help="NVIDIA's nvngx_dlss.dll from disk instead of downloading it from "
"NVIDIA's DLSS SDK repository")
ap.add_argument('--no-dlss', action='store_true', help='leave NVIDIA DLSS out of the kit')
ap.add_argument('--dlssg', type=Path, metavar='DLL',
help='nx2navi_dlssg.dll (tools/build_dlssg.py) for DLSS Frame Generation')
ap.add_argument('--streamline', type=Path, metavar='SDK',
help='the Streamline SDK folder instead of downloading the SDK release with --dlssg')
ap.add_argument('--prompt-art', type=Path, nargs='+', metavar='FOLDER',
help='the button art: folders holding XGamepad, P5Gamepad and Keyboard_Mouse, or those folders')
ap.add_argument('--out', type=Path, default=ROOT / 'build' / 'kit')
@@ -56,9 +62,13 @@ def main():
sys.path.insert(0, str(ROOT / 'tools'))
import dlss_pin
import prompt_art
import streamline_pin
if a.streamline and not a.dlssg:
sys.exit('--streamline goes with --dlssg')
if a.prompt_art:
prompt_art.art_files(a.prompt_art) # the art is all there before anything is downloaded
dlss = None if a.no_dlss else dlss_pin.resolve(a.dlss, ROOT / 'build' / 'dl')
sdk = streamline_pin.resolve(a.streamline, ROOT / 'build' / 'dl') if a.dlssg else None
kit = a.out / NAME
if kit.exists():
shutil.rmtree(kit)
@@ -79,6 +89,8 @@ def main():
shutil.copy2(a.ffx, kit / 'bin' / 'amd_fidelityfx_vk.dll')
if dlss: # DLSS: NGX loads it from beside the game (runtime/vk_dlaa.c); make_game.py copies it there
dlss_pin.place(dlss, kit / 'bin')
if sdk: # DLSS Frame Generation (runtime/vk_dlssg.c): make_game.py place_dlssg copies it beside the game
streamline_pin.place(a.dlssg, sdk, kit / 'bin' / 'dlssg')
if a.prompt_art: # make_game.py draws the button prompts from it (its prompts step)
n, size = prompt_art.copy_art(a.prompt_art, kit / prompt_art.FOLDER)
print(f'button art: {n} pictures, {size / 1e6:.1f} MB')

View File

@@ -21,8 +21,10 @@ The release folder (--out) holds:
bin/nvngx_dlss.dll and its notice (NVIDIA DLSS at the version tools/dlss_pin.py
pins, downloaded from NVIDIA's DLSS SDK repository and checked, or --dlss; not with
--no-dlss),
bin/dlssg/ with --dlssg and --streamline (DLSS Frame Generation: nx2navi_dlssg.dll,
Streamline's runtime DLLs and nvngx_dlssg.dll, their licences and a notice),
bin/dlssg/ with --dlssg (DLSS Frame Generation: nx2navi_dlssg.dll, Streamline's
runtime DLLs and nvngx_dlssg.dll at the version tools/streamline_pin.py pins,
downloaded from NVIDIA's Streamline SDK release and checked, or --streamline; their
licences and a notice),
lib/libnx2navirt.a (the runtime, prebuilt with the release's compiler),
prompt-art/ with --prompt-art (the button art make_game.py draws the Xbox,
PlayStation and keyboard and mouse prompts from, tools/prompt_art.py art_files),
@@ -58,6 +60,7 @@ sys.path.insert(0, str(ROOT / 'tools'))
import check_content # noqa: E402
import dlss_pin # noqa: E402
import prompt_art # noqa: E402
import streamline_pin # noqa: E402
from launcher import toolchain # noqa: E402
from launcher.paths import Layout # noqa: E402
@@ -172,50 +175,6 @@ def build_python(py):
# ---------------------------------------------------------------- app/<version>/
# DLSS Frame Generation: the files tools/make_game.py DLSSG_FILES names, put beside the game from bin/dlssg/.
DLSSG_FILES = ('nx2navi_dlssg.dll', 'sl.interposer.dll', 'sl.common.dll', 'sl.dlss_g.dll', 'sl.pcl.dll', 'sl.reflex.dll',
'nvngx_dlssg.dll')
DLSSG_NOTICE = 'dlssg-NOTICE.txt'
def place_dlssg(dll, sdk, dest):
"""DLSS Frame Generation into dest (the release's bin/dlssg/): nx2navi_dlssg.dll (tools/build_dlssg.py), NVIDIA
Streamline's interposer and the plugins it loads, and NGX's nvngx_dlssg.dll, unmodified from the Streamline SDK's
bin/x64, with Streamline's licence (MIT), the NVIDIA RTX SDKs License that covers nvngx_dlssg.dll, and a notice
naming each file's sha256."""
sdk_bin = sdk / 'bin' / 'x64'
srcs = {n: (dll if n == 'nx2navi_dlssg.dll' else sdk_bin / n) for n in DLSSG_FILES}
texts = {'streamline-LICENSE.txt': sdk / 'license.txt',
'nvidia-rtx-sdks-LICENSE.txt': sdk_bin / 'nvngx_dlss.license.txt'}
missing = [str(p) for p in (*srcs.values(), *texts.values()) if not p.is_file()]
if missing:
sys.exit(f'DLSS Frame Generation: {", ".join(missing)} missing')
dest.mkdir(parents=True, exist_ok=True)
lines = ['DLSS Frame Generation for NX2navi. nx2navi_dlssg.dll is NX2navi\'s own code (runtime/dlssg/), built',
'with the Streamline SDK\'s headers. The other files are NVIDIA\'s own builds, unmodified, from the',
f'{streamline_version(sdk)} SDK (https://github.com/NVIDIA-RTX/Streamline): the sl.*.dll files under its',
'MIT licence (streamline-LICENSE.txt), nvngx_dlssg.dll under the NVIDIA RTX SDKs License',
'(nvidia-rtx-sdks-LICENSE.txt), which licenses it only for use on systems with NVIDIA GPUs. They are not',
'part of NX2navi and NX2navi\'s licence does not cover them.', '']
for n, p in srcs.items():
shutil.copy2(p, dest / n)
lines.append(f'{n} sha256 {sha256_file(p)}')
for n, p in texts.items():
shutil.copy2(p, dest / n)
(dest / DLSSG_NOTICE).write_text('\n'.join(lines) + '\n', encoding='utf-8')
def streamline_version(sdk):
"""'Streamline 2.14.16' from the SDK's README title line, or 'Streamline' when it names none."""
try:
for line in (sdk / 'README.md').read_text(encoding='utf-8', errors='replace').splitlines()[:5]:
if 'Version' in line:
return 'Streamline ' + line.rsplit('Version', 1)[1].strip()
except OSError:
pass
return 'Streamline'
def build_app(app, ver, xlate, feed_repo, lock, ffx=None, dlss=None, art=None, dlssg=None):
copy_tree(ROOT / 'launcher', app / 'launcher')
for d in ('runtime', 'recomp'):
@@ -237,7 +196,7 @@ def build_app(app, ver, xlate, feed_repo, lock, ffx=None, dlss=None, art=None, d
if dlss: # DLSS: NGX loads it from beside the game (runtime/vk_dlaa.c); make_game.py copies it there
dlss_pin.place(dlss, app / 'bin')
if dlssg: # DLSS Frame Generation (runtime/vk_dlssg.c): make_game.py place_dlssg copies it beside the game
place_dlssg(dlssg[0], dlssg[1], app / 'bin' / 'dlssg')
streamline_pin.place(dlssg[0], dlssg[1], app / 'bin' / 'dlssg')
if art: # make_game.py draws the button prompts from it (its prompts step)
n, size = prompt_art.copy_art(art, app / prompt_art.FOLDER)
say(f'button art: {n} pictures, {size / 1e6:.1f} MB')
@@ -354,9 +313,10 @@ def main():
"downloading it from NVIDIA's DLSS SDK repository")
ap.add_argument('--no-dlss', action='store_true', help='leave NVIDIA DLSS out of the release')
ap.add_argument('--dlssg', type=Path, metavar='DLL',
help='nx2navi_dlssg.dll (tools/build_dlssg.py) for DLSS Frame Generation, with --streamline')
help='nx2navi_dlssg.dll (tools/build_dlssg.py) for DLSS Frame Generation')
ap.add_argument('--streamline', type=Path, metavar='SDK',
help='the Streamline SDK folder whose bin/x64 gives the Streamline and nvngx_dlssg.dll files')
help=f'the Streamline {streamline_pin.VERSION} SDK folder whose bin/x64 gives the Streamline and '
'nvngx_dlssg.dll files, instead of downloading the SDK release with --dlssg')
ap.add_argument('--prompt-art', type=Path, nargs='+', metavar='FOLDER',
help='the button art: folders holding XGamepad, P5Gamepad and Keyboard_Mouse, or those folders')
ap.add_argument('--out', type=Path, default=MAIN_ROOT / 'build' / 'launcher-release')
@@ -386,10 +346,10 @@ def main():
+ (f' (CLANG64 clang left out: {lock["fast_skipped"]})' if lock.get('fast_skipped') else ''))
dlss = None if a.no_dlss else dlss_pin.resolve(a.dlss, DL)
say(f'NVIDIA DLSS {dlss_pin.VERSION}: {dlss}' if dlss else 'NVIDIA DLSS left out (--no-dlss)')
if bool(a.dlssg) != bool(a.streamline):
sys.exit('--dlssg and --streamline go together')
dlssg = (a.dlssg, a.streamline) if a.dlssg else None
say(f'DLSS Frame Generation: {a.dlssg}, {streamline_version(a.streamline)}' if dlssg
if a.streamline and not a.dlssg:
sys.exit('--streamline goes with --dlssg')
dlssg = (a.dlssg, streamline_pin.resolve(a.streamline, DL)) if a.dlssg else None
say(f'DLSS Frame Generation: {a.dlssg}, Streamline {streamline_pin.VERSION}: {dlssg[1]}' if dlssg
else 'DLSS Frame Generation left out (no --dlssg)')
build_app(folder / 'app' / ver, ver, a.xlate, a.feed, lock, a.ffx, dlss, a.prompt_art, dlssg)
(folder / 'app' / 'current.txt').write_text(ver + '\n', encoding='utf-8')

118
tools/streamline_pin.py Normal file
View File

@@ -0,0 +1,118 @@
"""NVIDIA Streamline's runtime for DLSS Frame Generation (runtime/vk_dlssg.c): tools/make_launcher.py and
tools/make_kit.py put it in the release's or the kit's bin/dlssg/ beside nx2navi_dlssg.dll (tools/build_dlssg.py), and
tools/make_game.py place_dlssg copies the whole set beside the game.
The files are NVIDIA's own builds from the Streamline SDK release at a pinned version, unmodified: the SDK archive is
downloaded once, checked against its size and SHA-256, and only the files the game loads are kept, each checked
against its own SHA-256. The sl.*.dll files are under Streamline's MIT licence; nvngx_dlssg.dll is under the NVIDIA RTX
SDKs License, which licenses it only for use on systems with NVIDIA GPUs. The notice written beside them names the
version and each file's sha256.
"""
import shutil
import sys
import zipfile
from pathlib import Path
VERSION = '2.14.16'
URL = f'https://github.com/NVIDIA-RTX/Streamline/releases/download/v{VERSION}/streamline-sdk-v{VERSION}.zip'
SIZE = 276016170
SHA256 = '32041e51db0cab709a271b72a352281b690c2abbd669a827809401a5dd23956b'
DLL = 'nx2navi_dlssg.dll' # this repository's runtime/dlssg/, built by tools/build_dlssg.py
# The set tools/make_game.py DLSSG_FILES names: without all of it the game leaves DLSS Frame Generation off.
FILES = (DLL, 'sl.interposer.dll', 'sl.common.dll', 'sl.dlss_g.dll', 'sl.pcl.dll', 'sl.reflex.dll', 'nvngx_dlssg.dll')
NOTICE = 'dlssg-NOTICE.txt'
# The SDK's files the set takes, by their path in the SDK, with their sha256.
SDK_FILES = {
'bin/x64/sl.interposer.dll': 'b1d7395aebd7b9722841d64dcf7433680020b6a8e421f851c16d5ba0c2182edf',
'bin/x64/sl.common.dll': '38eea3b4209afc455636ea372b5933d65fcc13becec6c84d101dcfc4d8648f75',
'bin/x64/sl.dlss_g.dll': '3ed9538df724993bea8a050195a6a78b9c8c8a566d5b05ade477aab3c390e90f',
'bin/x64/sl.pcl.dll': '5698c38e722318681f215c9fe68e45b9bedd51378acc699d8eee26e961e57770',
'bin/x64/sl.reflex.dll': '443577e869352436ff7ec8f4d9c90418d3e95368436ce219261e0f4f07a38122',
'bin/x64/nvngx_dlssg.dll': '8d59d49b36410dd6b62fef2503a5871d347415bd85d943fa0848563a95dd50c7',
'license.txt': '7b6f23e7d6f3ad6292f9308d2b42cdc3d82ae4e9b2abb55f230279d83bedd43d',
'bin/x64/nvngx_dlss.license.txt': '3027f23ca5a46dd9cb8183fbd522983a86f64d7daac5982912bf9f214671f294',
}
# The licence texts beside the DLLs, by the SDK file each is copied from.
TEXTS = {'streamline-LICENSE.txt': 'license.txt', 'nvidia-rtx-sdks-LICENSE.txt': 'bin/x64/nvngx_dlss.license.txt'}
def sha256_file(path):
import hashlib
h = hashlib.sha256()
with open(path, 'rb') as f:
while b := f.read(1 << 20):
h.update(b)
return h.hexdigest()
def wrong_files(sdk):
"""The pinned SDK files that are missing from sdk or differ from the pin."""
sdk = Path(sdk)
return [n for n, h in SDK_FILES.items() if not (sdk / n).is_file() or sha256_file(sdk / n) != h]
def resolve(sdk, cache_dir):
"""A folder laid out as the SDK is that holds the pinned files: sdk when given (its files must be the pinned
ones), else cache_dir/streamline-<version>, filled from the SDK archive when missing (downloaded with
launcher/net.py: resumed, checked against the size and SHA-256, and deleted once its files are out)."""
if sdk:
sdk = Path(sdk)
if not sdk.is_dir():
raise SystemExit(f'{sdk} does not exist')
bad = wrong_files(sdk)
if bad:
raise SystemExit(f'{sdk} is not the Streamline {VERSION} SDK ({", ".join(bad)} missing or different); '
'another Streamline needs VERSION, SIZE, SHA256 and SDK_FILES in tools/streamline_pin.py '
'changed first')
return sdk
dest = Path(cache_dir) / f'streamline-{VERSION}'
if not wrong_files(dest):
return dest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from launcher import net
archive = Path(cache_dir) / f'streamline-sdk-v{VERSION}.zip'
if not (archive.is_file() and archive.stat().st_size == SIZE):
print(f'download {URL} ({SIZE / 1e6:.0f} MB)', flush=True)
net.download(URL, archive, sha256=SHA256, size=SIZE)
extract(archive, dest)
archive.unlink()
return dest
def extract(archive, dest):
"""The pinned files out of the SDK archive into dest, each checked against its sha256."""
import hashlib
with zipfile.ZipFile(archive) as z:
names = {n.replace('\\', '/'): n for n in z.namelist()}
for n, h in SDK_FILES.items():
if n not in names:
raise SystemExit(f'{archive.name} has no {n}')
data = z.read(names[n])
if hashlib.sha256(data).hexdigest() != h:
raise SystemExit(f'{n} in {archive.name} differs from the pinned file')
(dest / n).parent.mkdir(parents=True, exist_ok=True)
(dest / n).write_bytes(data)
def place(dll, sdk, dest):
"""DLSS Frame Generation into dest (a release's or the kit's bin/dlssg/): nx2navi_dlssg.dll, Streamline's
interposer and the plugins it loads, and NGX's nvngx_dlssg.dll from the SDK folder resolve returned, the two
licence texts, and a notice naming each file's sha256."""
dll, sdk, dest = Path(dll), Path(sdk), Path(dest)
if not dll.is_file():
raise SystemExit(f'{dll} does not exist')
srcs = {n: (dll if n == DLL else sdk / 'bin' / 'x64' / n) for n in FILES}
dest.mkdir(parents=True, exist_ok=True)
lines = ['DLSS Frame Generation for NX2navi. nx2navi_dlssg.dll is NX2navi\'s own code (runtime/dlssg/), built',
'with the Streamline SDK\'s headers. The other files are NVIDIA\'s own builds, unmodified, from the',
f'Streamline {VERSION} SDK (https://github.com/NVIDIA-RTX/Streamline): the sl.*.dll files under its',
'MIT licence (streamline-LICENSE.txt), nvngx_dlssg.dll under the NVIDIA RTX SDKs License',
'(nvidia-rtx-sdks-LICENSE.txt), which licenses it only for use on systems with NVIDIA GPUs. They are not',
'part of NX2navi and NX2navi\'s licence does not cover them.', '']
for n, p in srcs.items():
shutil.copy2(p, dest / n)
lines.append(f'{n} sha256 {sha256_file(p)}')
for n, src in TEXTS.items():
shutil.copy2(sdk / src, dest / n)
(dest / NOTICE).write_text('\n'.join(lines) + '\n', encoding='utf-8')