Include NVIDIA's pinned DLSS library for Linux in the AppImage, and place it beside the built game.

On Linux the game looks for libnvidia-ngx-dlss.so.<version> beside the executable, and the driver brings
only NGX's core, so the DLSS modes did nothing in every Linux build. tools/dlss_pin.py pins the Linux
library of the DLSS version it pins for Windows, with its own size and SHA-256; tools/make_appimage.py
puts it in the kit's bin/ with its notice (downloaded from NVIDIA's DLSS SDK repository and checked, or
--dlss; --no-dlss leaves it out), and tools/make_game.py copies it beside the game on Linux as it copies
the DLL on Windows, keeping a library the player put there.

Pull request #297 by mooki000, taken as it is; the builder's description is merged with the button art
option that came with the release workflow. On Windows place_dlss does what it did. No NVIDIA file is in
the repository.

tests/launcher_test.py passes with its two new tests (112 tests, Python 3.12 on Windows); the AppImage
was not built here.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-10 06:38:02 -05:00
parent ff4b6772f9
commit 32ee021905
6 changed files with 219 additions and 43 deletions

View File

@@ -123,6 +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 DLSS 310.9.1 for Linux (`libnvidia-ngx-dlss.so.310.9.1`, 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/Linux_x86_64/rel/libnvidia-ngx-dlss.so.310.9.1`), Copyright NVIDIA Corporation | NVIDIA RTX SDKs License (https://github.com/NVIDIA/DLSS/blob/v310.9.1/LICENSE.txt), the one licence of the repository's Windows and Linux libraries; licensed for systems with NVIDIA GPUs only | the Linux build kit's `bin/` (the AppImage of `tools/make_appimage.py`, unless made with `--no-dlss`) with `libnvidia-ngx-dlss-NOTICE.txt`; `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`, 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 |

View File

@@ -114,6 +114,15 @@ AMD FSR 3.1 loads AMD's `amd_fidelityfx_vk.dll`, which only the Windows build ca
FSR 3.1 upscaling nor FSR 3.1 frame generation, and `taa = fsr3` or `framegen = fsr3` in `nx2navi.ini` leave the
game's own scaling and swapchain as they are (the log says so).
NVIDIA DLSS (RTX cards) needs the driver's own `libnvidia-ngx.so.1` on the library path (the proprietary driver's
utilities package, `nvidia-utils` on Arch) and NVIDIA's DLSS library `libnvidia-ngx-dlss.so.<version>` beside
`ocarina`. The AppImage's kit has the library in `bin/` with `libnvidia-ngx-dlss-NOTICE.txt`, which names its licence,
and the build copies both beside `ocarina` (`tools/make_appimage.py --no-dlss` leaves it out). From a source checkout,
or to use another DLSS version, put the library there yourself: NVIDIA publishes it as
`lib/Linux_x86_64/rel/libnvidia-ngx-dlss.so.<version>` in [its DLSS repository](https://github.com/NVIDIA/DLSS), tagged
`v<version>`, and `tools/dlss_pin.py` names the version the kit carries. Later builds keep a library they did not
place. Without it DLSS does nothing on Linux.
`tools/make_appimage.py` packs this kit into one `NX2navi-Linux-<version>-x86_64.AppImage` with its own Python, for
players without a source checkout: it checks for the packages above, installs zbic with pip on its first run, picks
`--jobs` from the memory, builds into `~/Games/Ocarina` (or the folder given after the dump) and writes `play.sh`
@@ -138,7 +147,8 @@ beside the game.
`py -I tools\make_game.py --rebuild --out "C:\Games\Ocarina"` to draw them again (it skips everything else).
- 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.
your own `nvngx_dlss.dll` there: later builds keep it. On Linux the library is `libnvidia-ngx-dlss.so.<version>`
(see "Linux").
- 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.

View File

@@ -1712,6 +1712,119 @@ class MakeGameTest(Tmp):
self.assertEqual(self.mg.notice_sha256(self.tmp / 'n.txt'), dlss_pin.SHA256)
self.assertEqual((dlss_pin.NAME, dlss_pin.NOTICE), (self.mg.DLSS_DLL, self.mg.DLSS_NOTICE))
def test_dlss_library_beside_the_game_on_linux(self):
"""The kit's libnvidia-ngx-dlss.so.<version> and its notice go beside the game. The name carries the DLSS
version, so a kit with another version replaces the file an earlier build placed under its old name, and a
library the player put there, of any version, is kept. Each platform places only its own library."""
from unittest import mock
kit, out = self.tmp / 'kit', self.tmp / 'game'
(kit / 'bin').mkdir(parents=True)
out.mkdir()
note = out / 'libnvidia-ngx-dlss-NOTICE.txt'
def kit_lib(version, data, when):
for old in (kit / 'bin').glob('libnvidia-ngx-dlss.so.*'):
old.unlink()
lib = kit / 'bin' / f'libnvidia-ngx-dlss.so.{version}'
lib.write_bytes(data)
os.utime(lib, (when, when))
(kit / 'bin' / 'libnvidia-ngx-dlss-NOTICE.txt').write_text(
f'NVIDIA DLSS {version}\n\nsha256 {hashlib.sha256(data).hexdigest()}\n')
def beside(folder=out):
return sorted(p.name for p in folder.iterdir())
with mock.patch.object(self.mg, 'KIT', kit):
self.assertIsNone(self.mg.place_dlss(out, linux=True)) # a kit without DLSS: nothing
self.assertEqual(beside(), [])
(kit / 'bin' / 'nvngx_dlss.dll').write_bytes(b'the Windows DLL')
self.assertIsNone(self.mg.place_dlss(out, linux=True)) # the Windows DLL is not a Linux build's
self.assertEqual(beside(), [])
kit_lib('310.9.1', b'dlss one', 1_700_000_000)
self.assertIsNone(self.mg.place_dlss(out, linux=True))
self.assertEqual(beside(), ['libnvidia-ngx-dlss-NOTICE.txt', 'libnvidia-ngx-dlss.so.310.9.1'])
self.assertEqual((out / 'libnvidia-ngx-dlss.so.310.9.1').read_bytes(), b'dlss one')
self.assertIn(hashlib.sha256(b'dlss one').hexdigest(), note.read_text())
self.assertIsNone(self.mg.place_dlss(out, linux=True)) # unchanged
self.assertEqual(len(beside()), 2)
kit_lib('310.10.0', b'dlss two', 1_700_100_000) # another version: the earlier build's file goes
self.assertIsNone(self.mg.place_dlss(out, linux=True))
self.assertEqual(beside(), ['libnvidia-ngx-dlss-NOTICE.txt', 'libnvidia-ngx-dlss.so.310.10.0'])
self.assertEqual((out / 'libnvidia-ngx-dlss.so.310.10.0').read_bytes(), b'dlss two')
self.assertIn(hashlib.sha256(b'dlss two').hexdigest(), note.read_text())
# a library the player put there first, of another version: kept, and the kit's is not placed beside it
mine = self.tmp / 'mine'
mine.mkdir()
(mine / 'libnvidia-ngx-dlss.so.310.4.0').write_bytes(b"the player's own DLSS")
self.assertIn('libnvidia-ngx-dlss.so.310.4.0 kept', self.mg.place_dlss(mine, linux=True))
self.assertEqual(beside(mine), ['libnvidia-ngx-dlss.so.310.4.0'])
# the same version with other bytes (the player's own build) is kept too
(mine / 'libnvidia-ngx-dlss.so.310.4.0').unlink()
(mine / 'libnvidia-ngx-dlss.so.310.10.0').write_bytes(b"the player's own build")
self.assertIn('kept', self.mg.place_dlss(mine, linux=True))
self.assertEqual((mine / 'libnvidia-ngx-dlss.so.310.10.0').read_bytes(), b"the player's own build")
# a player's library put beside a library an earlier build placed: both stay
(out / 'libnvidia-ngx-dlss.so.310.4.0').write_bytes(b"the player's own DLSS")
self.assertIn('kept', self.mg.place_dlss(out, linux=True))
self.assertEqual(len(beside()), 3)
# and a Windows build takes the DLL, not the library
win = self.tmp / 'win'
win.mkdir()
self.assertIsNone(self.mg.place_dlss(win))
self.assertEqual(beside(win), ['nvngx_dlss.dll'])
def test_dlss_pin_for_linux(self):
"""The Linux library is pinned beside the Windows DLL, at the same DLSS version with its own size and sha256.
A file on disk must be the pinned one, a missing one is downloaded into the cache under its own name, and the
notice names the library and carries the sha256 line make_game.py reads."""
import dlss_pin
from unittest import mock
p = dlss_pin.pin(linux=True)
self.assertEqual(p.name, f'libnvidia-ngx-dlss.so.{dlss_pin.VERSION}')
self.assertTrue(p.name.startswith(self.mg.DLSS_SO_PREFIX))
self.assertFalse(p.notice.startswith(self.mg.DLSS_SO_PREFIX)) # the runtime takes that prefix for the library
self.assertEqual(p.notice, self.mg.DLSS_SO_NOTICE)
self.assertEqual(dlss_pin.pin().name, dlss_pin.NAME) # the default is still the Windows DLL
self.assertNotEqual((p.size, p.sha256), (dlss_pin.SIZE, dlss_pin.SHA256))
(self.tmp / 'n.txt').write_text(dlss_pin.notice_text(linux=True), encoding='utf-8')
self.assertEqual(self.mg.notice_sha256(self.tmp / 'n.txt'), p.sha256)
self.assertIn(p.name, dlss_pin.notice_text(linux=True))
self.assertNotIn('nvngx_dlss.dll', dlss_pin.notice_text(linux=True))
data = b'a DLSS library'
lib, cache = self.tmp / 'file.so', self.tmp / 'dl'
lib.write_bytes(data)
fetched = []
def download(url, dest, sha256=None, size=None):
fetched.append((url, Path(dest).name, sha256, size))
Path(dest).parent.mkdir(parents=True, exist_ok=True)
Path(dest).write_bytes(data)
return dest
pinned = dict(LINUX_SIZE=len(data), LINUX_SHA256=hashlib.sha256(data).hexdigest(),
SIZE=len(data) + 1, SHA256=hashlib.sha256(data + b'!').hexdigest())
with mock.patch.multiple(dlss_pin, **pinned), mock.patch('launcher.net.download', download), \
contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(dlss_pin.resolve(lib, cache, linux=True), lib)
with self.assertRaises(SystemExit): # the Windows DLL's pin does not accept the Linux file
dlss_pin.resolve(lib, cache)
lib.write_bytes(b'another DLSS!')
with self.assertRaises(SystemExit):
dlss_pin.resolve(lib, cache, linux=True)
with self.assertRaises(SystemExit):
dlss_pin.resolve(self.tmp / 'missing.so', cache, linux=True)
self.assertEqual(fetched, [])
self.assertEqual(dlss_pin.resolve(None, cache, linux=True), cache / p.name)
self.assertEqual(fetched, [(p.url, p.name, hashlib.sha256(data).hexdigest(), len(data))])
self.assertEqual(dlss_pin.resolve(None, cache), cache / f'nvngx_dlss-{dlss_pin.VERSION}.dll')
self.assertEqual(fetched[1][:2], (dlss_pin.URL, f'nvngx_dlss-{dlss_pin.VERSION}.dll'))
bin_dir = self.tmp / 'bin'
bin_dir.mkdir()
dlss_pin.place(cache / p.name, bin_dir, linux=True)
self.assertEqual((bin_dir / p.notice).read_text(encoding='utf-8'), dlss_pin.notice_text(linux=True))
self.assertEqual(sorted(x.name for x in bin_dir.iterdir()), sorted([p.notice, p.name]))
self.assertEqual((bin_dir / p.name).read_bytes(), data)
def test_compile_key_ignores_file_time_and_sysroot(self):
"""The compiler's id leaves its file time out (a tool update that puts the same compiler back keeps every
object), and the key leaves --sysroot out (the fast compiler's objects are the UCRT64 compiler's)."""

View File

@@ -1,12 +1,15 @@
"""NVIDIA's DLSS library (nvngx_dlss.dll) for the releases: tools/make_launcher.py and tools/make_kit.py put it in the
release's bin/ beside AMD's FSR 3.1 library, and tools/make_game.py copies it beside the game, where NGX loads it
(runtime/vk_dlaa.c). Without it DLSS runs only where the NVIDIA driver supplies the model itself.
"""NVIDIA's DLSS library for the releases: nvngx_dlss.dll for Windows, libnvidia-ngx-dlss.so.<version> for Linux.
tools/make_launcher.py and tools/make_kit.py put the DLL in the release's bin/ beside AMD's FSR 3.1 library,
tools/make_appimage.py puts the Linux library in the AppImage's kit bin/, and tools/make_game.py copies either beside
the game, where NGX loads it (runtime/vk_dlaa.c). Without it DLSS runs only where the NVIDIA driver supplies the model
itself (Windows), and on Linux not at all (runtime/resolution.c dlss_dll).
The file is NVIDIA's own build from the DLSS SDK repository at a pinned tag, unmodified and checked against its size
and SHA-256. NVIDIA's RTX SDKs License lets an application ship it in object form; the notice written beside it names
the version and the licence that covers it.
the version and the licence that covers it. Both platforms' files are published under the one licence file of that tag.
"""
from pathlib import Path
from typing import NamedTuple
NAME = 'nvngx_dlss.dll'
NOTICE = 'nvngx_dlss-NOTICE.txt' # beside the DLL; make_game.py reads its sha256 line
@@ -14,18 +17,41 @@ VERSION = '310.9.1'
URL = f'https://raw.githubusercontent.com/NVIDIA/DLSS/v{VERSION}/lib/Windows_x86_64/rel/{NAME}'
SIZE = 58956912
SHA256 = '3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983'
# The Linux library of the same release. Its name ends in the DLSS version; NGX finds it by that name beside the
# executable (runtime/resolution.c dlss_dll looks for the prefix).
LINUX_NAME = f'libnvidia-ngx-dlss.so.{VERSION}'
LINUX_NOTICE = 'libnvidia-ngx-dlss-NOTICE.txt' # beside the library; make_game.py reads its sha256 line
LINUX_URL = f'https://raw.githubusercontent.com/NVIDIA/DLSS/v{VERSION}/lib/Linux_x86_64/rel/{LINUX_NAME}'
LINUX_SIZE = 59481944
LINUX_SHA256 = '7561f16cab74e2b7ccf791bf44bb9cc43abb44bcf78f33c5663520ad4e857e02'
LICENSE_URL = f'https://github.com/NVIDIA/DLSS/blob/v{VERSION}/LICENSE.txt'
def notice_text():
return (f'{NAME} is NVIDIA DLSS {VERSION}, unmodified, as published in NVIDIA\'s DLSS SDK\n'
class Pin(NamedTuple):
name: str
notice: str
url: str
size: int
sha256: str
def pin(linux=False):
"""The pinned file of a platform."""
if linux:
return Pin(LINUX_NAME, LINUX_NOTICE, LINUX_URL, LINUX_SIZE, LINUX_SHA256)
return Pin(NAME, NOTICE, URL, SIZE, SHA256)
def notice_text(linux=False):
p = pin(linux)
return (f'{p.name} is NVIDIA DLSS {VERSION}, unmodified, as published in NVIDIA\'s DLSS SDK\n'
f'(https://github.com/NVIDIA/DLSS, tag v{VERSION}). Copyright NVIDIA Corporation.\n'
'\n'
'It is not part of NX2navi and NX2navi\'s licence does not cover it. It is distributed under the\n'
f'NVIDIA RTX SDKs License, and its use is subject to that licence: {LICENSE_URL}\n'
'NVIDIA licenses DLSS only for use on systems with NVIDIA GPUs.\n'
'\n'
f'sha256 {SHA256}\n')
f'sha256 {p.sha256}\n')
def sha256_file(path):
@@ -37,30 +63,31 @@ def sha256_file(path):
return h.hexdigest()
def resolve(local, cache_dir):
"""The pinned DLL: local when given (it must be the pinned file), else the copy in cache_dir, downloaded when
missing (launcher/net.py: resumed, checked against the size and SHA-256)."""
def resolve(local, cache_dir, linux=False):
"""The pinned library: local when given (it must be the pinned file), else the copy in cache_dir, downloaded when
missing (launcher/net.py: resumed, checked against the size and SHA-256). linux: the Linux library, else the DLL."""
p = pin(linux)
if local:
local = Path(local)
if not local.is_file():
raise SystemExit(f'{local} does not exist')
if local.stat().st_size != SIZE or sha256_file(local) != SHA256:
if local.stat().st_size != p.size or sha256_file(local) != p.sha256:
raise SystemExit(f'{local} is not NVIDIA DLSS {VERSION} (size or SHA-256 differ); a newer DLSS needs '
'VERSION, SIZE and SHA256 in tools/dlss_pin.py changed first')
return local
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from launcher import net
dest = Path(cache_dir) / f'nvngx_dlss-{VERSION}.dll'
if not (dest.is_file() and sha256_file(dest) == SHA256):
print(f'download {URL}', flush=True)
net.download(URL, dest, sha256=SHA256, size=SIZE)
dest = Path(cache_dir) / (p.name if linux else f'nvngx_dlss-{VERSION}.dll')
if not (dest.is_file() and sha256_file(dest) == p.sha256):
print(f'download {p.url}', flush=True)
net.download(p.url, dest, sha256=p.sha256, size=p.size)
return dest
def place(src, bin_dir):
"""The DLL and its notice into a release's bin/."""
def place(src, bin_dir, linux=False):
"""The library and its notice into a release's or the kit's bin/."""
import shutil
bin_dir = Path(bin_dir)
shutil.copy2(src, bin_dir / NAME)
(bin_dir / NOTICE).write_text(notice_text(), encoding='utf-8')
p, bin_dir = pin(linux), Path(bin_dir)
shutil.copy2(src, bin_dir / p.name)
(bin_dir / p.notice).write_text(notice_text(linux), encoding='utf-8')

View File

@@ -2,15 +2,17 @@
player's PC from their own dump with tools/make_game.py, as NX2navi-Setup.exe does on Windows.
python3 tools/make_appimage.py --python <standalone CPython 3.14> --appimagetool <appimagetool> [--out dist]
[--dlss libnvidia-ngx-dlss.so.<version> | --no-dlss]
The AppImage holds no game files: the kit's tools/, recomp/, runtime/ and docs, the shader translator built by
tools/build_xlate.py (bin/nx2navi_xlate), the pinned Vulkan Memory Allocator header, and a relocatable CPython 3.14
(python-build-standalone, as `uv python install 3.14` puts in ~/.local/share/uv/python) with numpy, lz4, zstandard,
fonttools and pillow, and with --prompt-art the button art make_game.py's prompts step draws from (as make_kit.py
--prompt-art: the pictures tools/prompt_art.py art_files names, into the kit's prompt-art folder; without it that step
is skipped and the game keeps the Switch prompts). zbic, the GPL package that unpacks the game's program files, is not
bundled: the AppImage installs it with pip into ~/.local/share/nx2navi/pydeps the first time it runs, as the Windows
launcher downloads it.
tools/build_xlate.py (bin/nx2navi_xlate), NVIDIA's DLSS library with its notice (bin/, tools/dlss_pin.py: downloaded
from NVIDIA's DLSS SDK repository and checked, or --dlss; not with --no-dlss), the pinned Vulkan Memory Allocator
header, and a relocatable CPython 3.14 (python-build-standalone, as `uv python install 3.14` puts in
~/.local/share/uv/python) with numpy, lz4, zstandard, fonttools and pillow, and with --prompt-art the button art
make_game.py's prompts step draws from (as make_kit.py --prompt-art: the pictures tools/prompt_art.py art_files names,
into the kit's prompt-art folder; without it that step is skipped and the game keeps the Switch prompts). zbic, the GPL
package that unpacks the game's program files, is not bundled: the AppImage installs it with pip into
~/.local/share/nx2navi/pydeps the first time it runs, as the Windows launcher downloads it.
The compiler and the libraries come from the system (README-BuildKit.md, "Linux"): the AppImage checks for clang,
clang++, lld and the headers before it starts, and names the packages for Arch, Fedora and Debian/Ubuntu.
@@ -134,6 +136,9 @@ def main():
ap.add_argument('--python', type=Path, required=True,
help='a relocatable CPython 3.14 folder (python-build-standalone, ~/.local/share/uv/python/cpython-3.14*)')
ap.add_argument('--appimagetool', type=Path, required=True, help='appimagetool-x86_64.AppImage')
ap.add_argument('--dlss', type=Path, help="NVIDIA's libnvidia-ngx-dlss.so.<version> 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 AppImage')
ap.add_argument('--out', type=Path, default=ROOT / 'dist', help='where the AppImage goes (default dist/)')
ap.add_argument('--prompt-art', type=Path, nargs='+', metavar='FOLDER',
help='folders holding XGamepad, P5Gamepad and Keyboard_Mouse (or those folders): the button art, as '
@@ -146,12 +151,14 @@ def main():
a.python / 'bin' / 'python3'):
if not need.exists():
sys.exit(f'{need} is missing (tools/build_xlate.py builds the translator; tools/build_game.py fetches VMA)')
sys.path.insert(0, str(ROOT / 'tools'))
import dlss_pin
dlss = None if a.no_dlss else dlss_pin.resolve(a.dlss, ROOT / 'build' / 'dl', linux=True)
app = ROOT / 'build' / 'appimage' / 'NX2navi.AppDir'
shutil.rmtree(app, ignore_errors=True)
kit = app / 'usr' / 'kit'
kit.mkdir(parents=True)
sys.path.insert(0, str(ROOT / 'tools'))
import build_shaders
build_shaders.build(ROOT / 'runtime') # the image carries the runtime's shaders compiled (runtime/*_spv.h)
for d in KIT_DIRS:
@@ -164,6 +171,8 @@ def main():
(kit / 'bin').mkdir()
for f in ('nx2navi_xlate', 'nx2navi_xlate.id'):
shutil.copy2(xlate / f, kit / 'bin' / f)
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', linux=True)
(kit / 'toolchain' / 'vma').mkdir(parents=True)
shutil.copy2(ROOT / 'toolchain' / 'vma' / 'vk_mem_alloc.h', kit / 'toolchain' / 'vma' / 'vk_mem_alloc.h')
if a.prompt_art:

View File

@@ -554,6 +554,9 @@ def copy_if_changed(src, dst):
DLSS_DLL, DLSS_NOTICE = 'nvngx_dlss.dll', 'nvngx_dlss-NOTICE.txt' # tools/dlss_pin.py, in the kit's bin/
# The Linux library's name ends in the DLSS version (tools/dlss_pin.py): it is found by its prefix, as the runtime
# finds it beside the game (runtime/resolution.c dlss_dll).
DLSS_SO_PREFIX, DLSS_SO_NOTICE = 'libnvidia-ngx-dlss.so.', 'libnvidia-ngx-dlss-NOTICE.txt'
# DLSS Frame Generation (runtime/vk_dlssg.c) loads these from beside the game: nx2navi_dlssg.dll (runtime/dlssg/,
# tools/build_dlssg.py), NVIDIA Streamline's interposer and the plugins it uses, and NGX's frame generation library.
# A release carries them with their licence texts in bin/dlssg/ (tools/make_launcher.py --dlssg); without the whole set
@@ -564,7 +567,7 @@ DLSSG_FILES = ('nx2navi_dlssg.dll', 'sl.interposer.dll', 'sl.common.dll', 'sl.dl
def notice_sha256(path):
"""The sha256 line of a DLSS notice: the DLL a kit placed beside the game."""
"""The sha256 line of a DLSS notice: the library a kit placed beside the game."""
try:
for line in path.read_text(encoding='utf-8').splitlines():
if line.startswith('sha256 '):
@@ -574,22 +577,35 @@ def notice_sha256(path):
return None
def place_dlss(out):
def dlss_files(folder, linux):
"""The DLSS libraries in folder, by name: nvngx_dlss.dll, or on Linux every libnvidia-ngx-dlss.so.<version>."""
return sorted(p for p in folder.glob(DLSS_SO_PREFIX + '*' if linux else DLSS_DLL) if p.is_file())
def place_dlss(out, linux=False):
"""NVIDIA's DLSS library from the kit's bin/ beside the game, where NGX loads it (runtime/vk_dlaa.c), with its
notice. A different nvngx_dlss.dll the player put there (another DLSS version) is kept; the one an earlier build
placed, named by the notice beside it, is replaced. Returns a line for the build's log when it kept the player's."""
src, dst, note = KIT / 'bin' / DLSS_DLL, out / DLSS_DLL, KIT / 'bin' / DLSS_NOTICE
if not src.is_file():
notice. A different library the player put there (another DLSS version) is kept; the one an earlier build placed,
named by the notice beside it, is replaced. linux: the Linux library, whose name carries the DLSS version, so a
kit with another version replaces the earlier build's file under its old name. Returns a line for the build's
log when it kept the player's."""
note_name = DLSS_SO_NOTICE if linux else DLSS_NOTICE
srcs, note = dlss_files(KIT / 'bin', linux), KIT / 'bin' / note_name
if not srcs:
return None
if dst.is_file():
src = srcs[-1]
beside = dlss_files(out, linux)
for dst in beside:
s, d = os.stat(src), os.stat(dst)
if d.st_size != s.st_size or int(d.st_mtime) != int(s.st_mtime):
if dst.name != src.name or d.st_size != s.st_size or int(d.st_mtime) != int(s.st_mtime):
have = sha256_file(dst)
if have != notice_sha256(out / DLSS_NOTICE) and have != sha256_file(src):
return f"{DLSS_DLL} kept: the one already beside the game is not the kit's (another DLSS version)"
copy_if_changed(src, dst)
if note.is_file() and (not (out / DLSS_NOTICE).is_file() or (out / DLSS_NOTICE).read_bytes() != note.read_bytes()):
shutil.copy2(note, out / DLSS_NOTICE) # by content: two versions' notices can share size and second
if have != notice_sha256(out / note_name) and have != sha256_file(src):
return f"{dst.name} kept: the one already beside the game is not the kit's (another DLSS version)"
copy_if_changed(src, out / src.name)
for dst in beside:
if not dst.samefile(out / src.name): # another version's library, placed by an earlier build: replaced
dst.unlink()
if note.is_file() and (not (out / note_name).is_file() or (out / note_name).read_bytes() != note.read_bytes()):
shutil.copy2(note, out / note_name) # by content: two versions' notices can share size and second
return None
@@ -1331,7 +1347,7 @@ def step_compile(out, cache_dir, msys, jobs, romfs_job=None, update=False):
copy_if_changed(xlate / name, out / name)
if (KIT / 'bin' / 'amd_fidelityfx_vk.dll').is_file(): # FSR 3.1 (runtime/vk_ffx.cpp)
copy_if_changed(KIT / 'bin' / 'amd_fidelityfx_vk.dll', out / 'amd_fidelityfx_vk.dll')
kept = place_dlss(out) if WIN else None # NVIDIA DLSS (runtime/vk_dlaa.c): its library is a Windows DLL
kept = place_dlss(out, linux=not WIN) # NVIDIA DLSS (runtime/vk_dlaa.c): this platform's library from the kit
if kept:
say(f' {kept}')
if WIN and place_dlssg(out): # DLSS Frame Generation (runtime/vk_dlssg.c): Windows DLLs too