ASI add-on for existing OptiScaler installs

Add an optional D4R_INSTALL_MODE=asi release (d4r-<version>-asi.zip)
holding only d4r.asi and a companion d4r/ folder, to drop into an
existing OptiScaler plugin directory.

- Shim: export InitializeASI. It identifies OptiScaler by its PE export
  name to find the active INI, then launches d4r-setup.exe out of
  process (no GPU or network work under the loader lock). The .asi
  build loads its runtime and config from the companion d4r/ folder.

- tools/d4r_asi_setup.cpp: native helper. It waits for the game to
  exit, installs the D3D12 wrapper and the private d4r12core.dll beside
  the EXE, and merges the NGX route into the loader's INI (including
  GE-Proton's prefix-local INI) with backups. Unchanged launches do
  nothing. It skips Proton helpers and background utilities and runs
  setup for one plugin install at a time.

- scripts/private_d3d12_core.py: points vkd3d's d3d12.dll forwarder at
  d4r12core.dll without moving code addresses.

- install.sh runs from inside the ASI's d4r/ as well as the proxy
  release root. d4r-check.sh checks ASI installs (loader, plugin path,
  LoadAsiPlugins, HookOriginalNvngxOnly, Wine drive paths).

- Packaging, build script, docs, ASI_README.txt and tests.
This commit is contained in:
ayo committed 2026-10-12 17:36:01 -04:00
1 parent 85175e0b27
commit d5c8be25c8
16 files changed
+1091 -39

No files matched your search

+365
View File
@@ -0,0 +1,365 @@
"""Native ASI configuration: backups, existing plugins, loader contract and automatic dispatch."""
import configparser
import hashlib
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import time
import unittest
ROOT = Path(__file__).resolve().parents[1]
@unittest.skipUnless(shutil.which('wine') and shutil.which('x86_64-w64-mingw32-g++'), 'Wine/MinGW required')
class AsiSetupTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temporary = tempfile.TemporaryDirectory(prefix='d4r-asi-tests-')
cls.directory = Path(cls.temporary.name)
cls.helper = cls.directory / 'd4r-setup.exe'
cls.probe = cls.directory / 'probe.exe'
for source, output in (('d4r_asi_setup.cpp', cls.helper), ('d4r_asi_probe.cpp', cls.probe)):
subprocess.run(['x86_64-w64-mingw32-g++', '-std=c++20', '-O2', '-Wall', '-Wextra',
'-Werror', '-static', '-municode', str(ROOT / 'tools' / source), '-o', str(output)],
check=True, capture_output=True)
# Hold the second destination open without delete sharing. The helper
# must restore the first DLL when the second replacement fails.
lock_source = cls.directory / 'locked-dll.cpp'
lock_source.write_text(r'''
#include <windows.h>
#include <string>
int wmain(int argc, wchar_t** argv) {
if (argc != 5) return 2;
HANDLE locked = CreateFileW(argv[2], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (locked == INVALID_HANDLE_VALUE) return 3;
std::wstring command = L"\"" + std::wstring(argv[1]) + L"\" \"" + argv[3] + L"\" \"" + argv[4] + L"\"";
STARTUPINFOW startup = {}; startup.cb = sizeof(startup);
PROCESS_INFORMATION process = {};
if (!CreateProcessW(argv[1], command.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup, &process)) return 4;
WaitForSingleObject(process.hProcess, INFINITE);
DWORD code = 0; GetExitCodeProcess(process.hProcess, &code);
CloseHandle(process.hThread); CloseHandle(process.hProcess); CloseHandle(locked);
return code == 1 ? 0 : 5;
}
''')
cls.lock_probe = cls.directory / 'locked-dll.exe'
subprocess.run(['x86_64-w64-mingw32-g++', '-std=c++20', '-O2', '-Wall', '-Wextra',
'-Werror', '-static', '-municode', str(lock_source), '-o', str(cls.lock_probe)],
check=True, capture_output=True)
cls.env = {k: v for k, v in os.environ.items() if not k.startswith('D4R_')}
cls.env.update(WINEPREFIX=str(cls.directory / 'prefix'), WINEDEBUG='-all', WINEARCH='win64',
WINEDLLOVERRIDES='winemenubuilder.exe=d', D4R_ASI_SETUP_QUIET='1')
@classmethod
def tearDownClass(cls):
cls.temporary.cleanup()
@staticmethod
def winpath(path):
return 'Z:' + str(path).replace('/', '\\')
def fixture(self, game, source):
runtime = game / 'custom plugins/d4r'
runtime.mkdir(parents=True)
(game / 'Game.exe').touch()
(game / 'winmm.dll').write_bytes(b'existing OptiScaler loader')
(game / 'OptiScaler.ini').write_bytes(source)
plugin = game / 'custom plugins/d4r.asi'
plugin.write_bytes(b'ASI fixture')
(plugin.parent / 'Other.asi').write_bytes(b'other mod')
for name in ('nvcuda.dll', 'nvngx_dlss.dll', 'ngx/_nvngx.dll',
'zluda/libcuda.so', 'rocm/lib/libamdhip64.so.7'):
path = runtime / name
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
(runtime / 'd4r.ini').write_text('[DLSS]\nModel=K\n')
shutil.copy2(self.helper, runtime / 'd4r-setup.exe')
for name in ('d3d12.dll', 'd4r12core.dll'):
(runtime / name).write_bytes(('new ' + name).encode())
(game / name).write_bytes(('old ' + name).encode())
(game / 'D3D12Core.dll').write_bytes(b'original Microsoft SDK core')
return plugin
def setup(self, game, plugin):
return subprocess.run(['wine', str(self.helper), self.winpath(game), self.winpath(plugin)],
env=self.env, cwd=self.directory, text=True, capture_output=True, timeout=60)
def test_custom_plugin_folder_preservation_backup_and_retry(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp) / 'Game with spaces'
original = (b'; keep this comment\r\n[Plugins]\r\nPath=custom plugins\r\n'
b'LoadAsiPlugins=true\r\nLoadReShade=true\r\n'
b'[FrameGen]\r\nFGOutput=fsr31\r\n[Menu]\r\nShortcutKey=45\r\n')
plugin = self.fixture(game, original)
result = self.setup(game, plugin)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(next(game.glob('OptiScaler.ini.d4r-backup.*')).read_bytes(), original)
configured = (game / 'OptiScaler.ini').read_bytes()
self.assertEqual(configured.count(b'\n'), configured.count(b'\r\n'))
self.assertIn(b'LoadReShade=true\r\n', configured)
self.assertIn(b'[FrameGen]\r\nFGOutput=fsr31\r\n', configured)
self.assertIn(b'; keep this comment\r\n', configured)
ini = configparser.ConfigParser()
ini.read_string(configured.decode())
self.assertEqual(ini['Libraries']['NvngxPath'], self.winpath(plugin))
self.assertEqual(ini['Plugins']['Path'], self.winpath(plugin.parent))
self.assertEqual((game / 'winmm.dll').read_bytes(), b'existing OptiScaler loader')
self.assertEqual((plugin.parent / 'Other.asi').read_bytes(), b'other mod')
self.assertEqual((game / 'D3D12Core.dll').read_bytes(), b'original Microsoft SDK core')
for name in ('d3d12.dll', 'd4r12core.dll'):
self.assertEqual((game / name).read_bytes(), ('new ' + name).encode())
self.assertEqual(next(game.glob(name + '.d4r-backup.*')).read_bytes(), ('old ' + name).encode())
self.assertEqual(self.setup(game, plugin).returncode, 0)
self.assertEqual(len(list(game.glob('*.d4r-backup.*'))), 3)
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), configured)
check = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), str(game)],
text=True, capture_output=True)
self.assertEqual(check.returncode, 0, check.stdout + check.stderr)
self.assertIn('(d4r ASI)', check.stdout)
for old, new, message in ((b'LoadAsiPlugins=true', b'LoadAsiPlugins=false', 'LoadAsiPlugins=true'),
(b'HookOriginalNvngxOnly=true', b'HookOriginalNvngxOnly=false',
'HookOriginalNvngxOnly=true')):
(game / 'OptiScaler.ini').write_bytes(configured.replace(old, new))
check = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), str(game)],
text=True, capture_output=True)
self.assertNotEqual(check.returncode, 0)
self.assertIn(message, check.stdout)
def test_duplicate_settings_and_missing_nvidia_leave_config_untouched(self):
for source in (b'[Libraries]\nNvngxPath=auto\nnvngxpath=other\n',
b'[Plugins]\nPath=plugins\n[plugins]\nPath=elsewhere\n',
b'[Plugins]\nLoadAsiPlugins=true\n'):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
plugin = self.fixture(game, source)
if source == b'[Plugins]\nLoadAsiPlugins=true\n':
(plugin.parent / 'd4r/nvngx_dlss.dll').unlink()
result = self.setup(game, plugin)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), source)
self.assertFalse(list(game.glob('*.d4r-backup.*')))
self.assertFalse(list(game.glob('.d4r-setup.*')))
@unittest.skipUnless((ROOT / 'build/d4r.asi').exists(), 'Build the ASI first')
def test_actual_asi_load_and_automatic_configuration(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp) / 'Game with spaces'
original = b'[Plugins]\nLoadAsiPlugins=true\nPath=custom plugins\n'
plugin = self.fixture(game, original)
shutil.copy2(ROOT / 'build/d4r.asi', plugin)
shutil.copy2(self.probe, game / 'probe.exe')
result = subprocess.run(['wine', str(game / 'probe.exe'), self.winpath(plugin),
self.winpath(plugin.parent / 'd4r')], env=self.env, cwd=self.directory,
text=True, capture_output=True, timeout=60)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn('ASI loader contract passed', result.stdout)
deadline = time.monotonic() + 15
status = plugin.parent / 'd4r/installation-status.txt'
while (not status.exists() or 'setup complete' not in status.read_text()) and time.monotonic() < deadline:
time.sleep(0.05)
self.assertIn(self.winpath(plugin), (game / 'OptiScaler.ini').read_text())
log = (plugin.parent / 'd4r/d4r_nvngx.log').read_text()
self.assertEqual(log.count('InitializeASI:'), 1)
self.assertFalse((plugin.parent / 'd4r_nvngx.log').exists())
def test_invalid_second_dll_destination_preserves_first_dll_and_config(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
source = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, source)
(game / 'd4r12core.dll').unlink()
(game / 'd4r12core.dll').mkdir()
result = self.setup(game, plugin)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((game / 'd3d12.dll').read_bytes(), b'old d3d12.dll')
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), source)
self.assertFalse(list(game.glob('.d4r-setup.*')))
def test_nvidia_installer_accepts_both_runtime_layouts(self):
for nested in (False, True):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
base = Path(tmp)
runtime = base / 'd4r'
(runtime / 'ngx').mkdir(parents=True)
for name in ('nvngx.dll', 'd4r.ini'):
(runtime / name).touch()
dlss, ngx = b'verified DLSS fixture', b'verified NGX fixture'
(runtime / 'nvngx_dlss.dll').write_bytes(dlss)
(runtime / 'ngx/_nvngx.dll').write_bytes(ngx)
script = (ROOT / 'install.sh').read_text()
replacements = {'DLSS_SHA256': hashlib.sha256(dlss).hexdigest(),
'NGX_SHA256': hashlib.sha256(ngx).hexdigest()}
script = '\n'.join(key + '=' + replacements[key] if key in replacements else line
for line in script.splitlines() for key in [line.split('=', 1)[0]]) + '\n'
installer = (runtime if nested else base) / 'install.sh'
installer.write_text(script)
result = subprocess.run(['bash', str(installer)], cwd=self.directory,
text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn('already installed and verified', result.stdout)
self.assertEqual((runtime / 'nvngx_dlss.dll').read_bytes(), dlss)
def test_locked_second_dll_rolls_back_first_dll(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
result = subprocess.run(['wine', str(self.lock_probe), self.winpath(self.helper),
self.winpath(game / 'd4r12core.dll'), self.winpath(game), self.winpath(plugin)],
env=self.env, cwd=self.directory, text=True, capture_output=True, timeout=30)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
for name in ('d3d12.dll', 'd4r12core.dll'):
self.assertEqual((game / name).read_bytes(), ('old ' + name).encode())
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), original)
self.assertIn('earlier changes were restored', (plugin.parent / 'd4r/installation-status.txt').read_text())
def test_d3d12_optiscaler_loader_is_preserved(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
loader = b'MZ fixture OptiScaler.dll\0'
(game / 'd3d12.dll').write_bytes(loader)
result = self.setup(game, plugin)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((game / 'd3d12.dll').read_bytes(), loader)
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), original)
self.assertFalse(list(game.glob('*.d4r-backup.*')))
def test_prefix_config_detected_without_game_local_ini(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
base = Path(tmp)
game = base / 'game'
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
umu = base / 'prefix/drive_c/windows/system32/umu'
umu.mkdir(parents=True)
shutil.move(str(plugin.parent), umu / 'plugins')
plugin = umu / 'plugins/d4r.asi'
config = umu / 'OptiScaler.ini'
shutil.move(str(game / 'OptiScaler.ini'), config)
result = self.setup(game, plugin)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn(self.winpath(plugin), config.read_text())
self.assertFalse((game / 'OptiScaler.ini').exists())
self.assertEqual(next(umu.glob('OptiScaler.ini.d4r-backup.*')).read_bytes(), original)
# The real GE-Proton loader writes C: paths and lives in the prefix,
# while the game EXE lives outside that prefix on Z:.
dosdevices = base / 'prefix/dosdevices'
dosdevices.mkdir()
(dosdevices / 'c:').symlink_to('../drive_c')
(umu / 'OptiScaler.dll').touch()
win_plugins = r'C:\windows\system32\umu\plugins'
data = config.read_text().replace(self.winpath(plugin.parent), win_plugins)
config.write_text(data)
checker = plugin.parent / 'd4r/d4r-check.sh'
shutil.copy2(ROOT / 'packaging/d4r-check.sh', checker)
(game / 'winmm.dll').unlink()
result = subprocess.run(['sh', str(checker), str(game)], text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn('(d4r ASI)', result.stdout)
@unittest.skipUnless((ROOT / 'build/d4r.asi').exists(), 'Build the ASI first')
def test_active_proxy_ini_selected_over_unrelated_game_ini(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
base = Path(tmp)
game = base / 'game'
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
shutil.copy2(ROOT / 'build/d4r.asi', plugin)
umu = base / 'prefix/drive_c/windows/system32/umu'
umu.mkdir(parents=True)
config = umu / 'OptiScaler.ini'
config.write_bytes(b'; active proxy config\n[Plugins]\nLoadAsiPlugins=true\n')
proxy_source = base / 'proxy.cpp'
proxy_source.write_text('extern "C" __declspec(dllexport) void DummyOpti() {}\n')
definition = base / 'proxy.def'
definition.write_text('LIBRARY OptiScaler.dll\nEXPORTS\nDummyOpti\n')
proxy = umu / 'winmm.dll'
subprocess.run(['x86_64-w64-mingw32-g++', '-shared', '-static', str(proxy_source),
str(definition), '-o', str(proxy)], check=True, capture_output=True)
source = base / 'active-proxy.cpp'
source.write_text(r'''
#include <windows.h>
int wmain(int argc, wchar_t** argv) {
if (argc != 3 || !LoadLibraryW(argv[1])) return 2;
HMODULE asi = LoadLibraryW(argv[2]);
if (!asi) return 3;
auto init = reinterpret_cast<void (*)()>(GetProcAddress(asi, "InitializeASI"));
if (!init) return 4;
init();
return 0;
}
''')
probe = game / 'active-proxy.exe'
subprocess.run(['x86_64-w64-mingw32-g++', '-static', '-municode', str(source), '-o', str(probe)],
check=True, capture_output=True)
result = subprocess.run(['wine', str(probe), self.winpath(proxy), self.winpath(plugin)],
env=self.env, cwd=self.directory, text=True, capture_output=True, timeout=60)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
status = plugin.parent / 'd4r/installation-status.txt'
deadline = time.monotonic() + 15
while (not status.exists() or 'setup complete' not in status.read_text()) and time.monotonic() < deadline:
time.sleep(0.05)
self.assertTrue(status.exists())
self.assertIn('setup complete', status.read_text())
self.assertIn(self.winpath(plugin), config.read_text())
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), original)
@unittest.skipUnless((ROOT / 'build/d4r.asi').exists(), 'Build the ASI first')
def test_xalia_does_not_start_setup_or_modify_settings(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
shutil.copy2(ROOT / 'build/d4r.asi', plugin)
utility = game / 'xalia.exe'
shutil.copy2(self.probe, utility)
result = subprocess.run(['wine', str(utility), self.winpath(plugin),
self.winpath(plugin.parent / 'd4r')], env=self.env, cwd=self.directory,
text=True, capture_output=True, timeout=30)
# The probe's environment assertions intentionally fail because
# InitializeASI must leave this auxiliary process untouched.
self.assertEqual(result.returncode, 5, result.stdout + result.stderr)
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), original)
self.assertFalse((plugin.parent / 'd4r/installation-status.txt').exists())
self.assertFalse((plugin.parent / 'd4r/d4r_nvngx.log').exists())
@unittest.skipUnless((ROOT / 'build/d4r.asi').exists(), 'Build the ASI first')
def test_installation_waits_for_game_exit(self):
with tempfile.TemporaryDirectory(dir=self.directory) as tmp:
game = Path(tmp)
original = b'[Plugins]\nLoadAsiPlugins=true\n'
plugin = self.fixture(game, original)
shutil.copy2(ROOT / 'build/d4r.asi', plugin)
shutil.copy2(self.probe, game / 'probe.exe')
process = subprocess.Popen(['wine', str(game / 'probe.exe'), self.winpath(plugin),
self.winpath(plugin.parent / 'd4r')],
env=dict(self.env, D4R_ASI_PROBE_PAUSE_MS='5000'), cwd=self.directory,
text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
status = plugin.parent / 'd4r/installation-status.txt'
deadline = time.monotonic() + 15
while not status.exists() and process.poll() is None and time.monotonic() < deadline:
time.sleep(0.05)
self.assertTrue(status.exists())
self.assertIn('Close the game', status.read_text())
self.assertEqual((game / 'd3d12.dll').read_bytes(), b'old d3d12.dll')
self.assertEqual((game / 'OptiScaler.ini').read_bytes(), original)
output, errors = process.communicate(timeout=20)
self.assertEqual(process.returncode, 0, output + errors)
deadline = time.monotonic() + 10
while 'setup complete' not in status.read_text() and time.monotonic() < deadline:
time.sleep(0.05)
self.assertIn('setup complete', status.read_text())
self.assertEqual((game / 'd3d12.dll').read_bytes(), b'new d3d12.dll')
finally:
if process.poll() is None:
process.kill()
process.communicate()
if __name__ == '__main__':
unittest.main()
+34 -8
View File
@@ -4,6 +4,8 @@ Package tests use disposable runtime files and stand-ins for GPU kernel compilat
Actual OptiScaler/Proton loading additionally needs the D3D12 harness.
"""
import configparser
import hashlib
import itertools
import os
from pathlib import Path
import shutil
@@ -89,7 +91,7 @@ class OptiScalerConfigTests(unittest.TestCase):
inputs.mkdir()
(repo / 'scripts').mkdir(parents=True)
for name in ('package_release.sh', 'configure_optiscaler.py', 'check_glibc_compat.sh',
'check_redistributable.py'):
'check_redistributable.py', 'private_d3d12_core.py'):
shutil.copy2(ROOT / 'scripts' / name, repo / 'scripts' / name)
shutil.copytree(ROOT / 'packaging', repo / 'packaging')
for name in ('LICENSE', 'NOTICE'):
@@ -116,16 +118,22 @@ class OptiScalerConfigTests(unittest.TestCase):
subprocess.run(['cc', '-shared', '-x', 'c', '-o', str(elf), '-'],
input='int fixture(void) { return 0; }\n', text=True, check=True)
fixture(repo / 'build/d4r_nvngx.dll')
fixture(repo / 'build/d4r-setup.exe')
fixture(repo / 'build/wine-nvcuda/x86_64-unix/nvcuda.dll.so', elf.read_bytes())
fixture(inputs / 'zluda/libnvcuda.so', elf.read_bytes())
fixture(inputs / 'rocm/lib/libamdhip64.so.7', elf.read_bytes())
fixture(inputs / 'rocm/licenses/runtime.txt')
for name in ('d3d12.dll', 'd3d12core.dll'):
fixture(inputs / 'vkd3d' / name)
fixture(inputs / 'vkd3d' / name, b'MZ fixture d3d12core.dll\0 then d3d12core.dll\0' if name == 'd3d12.dll' else b'fixture')
fixture(inputs / 'opti/OptiScaler.dll')
fixture(inputs / 'opti/OptiScaler.ini', upstream_ini().replace('\n', '\r\n').encode())
dlss = fixture(inputs / 'nvngx_dlss.dll')
core = fixture(inputs / '_nvngx.dll')
installer = (ROOT / 'install.sh').read_text()
installer = '\n'.join('DLSS_SHA256=' + hashlib.sha256(dlss.read_bytes()).hexdigest()
if line.startswith('DLSS_SHA256=') else line
for line in installer.splitlines()) + '\n'
(repo / 'install.sh').write_text(installer)
license_path = fixture(inputs / 'license.txt')
for name in ('LICENSE-APACHE', 'LICENSE-MIT', 'ext/llvm-project/llvm/LICENSE.TXT', 'LICENSE', 'COPYING'):
fixture(inputs / 'source' / name)
@@ -153,19 +161,37 @@ class OptiScalerConfigTests(unittest.TestCase):
'direct_origins.bin', 'weights.bin', 'offsets.bin', 'lut.bin', 'hipnet.bin', 'model.json'):
fixture(inputs / 'engine' / name)
env['D4R_BUNDLE_ENGINE'] = str(inputs / 'engine')
for bundled in ('1', '0'):
with self.subTest(bundled=bundled):
for mode, bundled in itertools.product(('proxy', 'asi'), ('1', '0', 'installer')):
with self.subTest(mode=mode, bundled=bundled):
env['D4R_BUNDLE_NVIDIA'] = bundled
env['D4R_INSTALL_MODE'] = mode
if mode == 'asi':
env.pop('D4R_OPTISCALER', None)
result = subprocess.run(['bash', str(repo / 'scripts/package_release.sh'), str(repo / 'dist')],
env=env, text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
name = f'd4r-{version}' + ('-nonvidia' if bundled == '0' else '')
if mode == 'asi':
name += '-asi'
with zipfile.ZipFile(repo / 'dist' / (name + '.zip')) as archive:
self.assertIsNone(archive.testzip())
data = archive.read('OptiScaler.ini')
self.assert_routing(data)
configs.append(data)
self.assertEqual(data, (repo / 'dist' / name / 'OptiScaler.ini').read_bytes())
if mode == 'proxy':
data = archive.read('OptiScaler.ini')
self.assert_routing(data)
configs.append(data)
self.assertEqual(data, (repo / 'dist' / name / 'OptiScaler.ini').read_bytes())
else:
self.assertNotIn('OptiScaler.ini', archive.namelist())
self.assertNotIn('dxgi.dll', archive.namelist())
self.assertEqual(archive.read('d4r.asi'), archive.read('d4r/nvngx.dll'))
self.assertIn('d4r/d4r-setup.exe', archive.namelist())
self.assertEqual('d4r/install.sh' in archive.namelist(), bundled == 'installer')
self.assertFalse(any(n.endswith('.py') for n in archive.namelist()))
self.assertIn(b'ASI add-on', archive.read('d4r/README.txt'))
self.assertEqual({n.split('/')[0] for n in archive.namelist()}, {'d4r.asi', 'd4r'})
self.assertIn('d4r/d3d12.dll', archive.namelist())
self.assertIn('d4r/d4r12core.dll', archive.namelist())
self.assertNotIn('d4r/d3d12core.dll', archive.namelist())
self.assertIn('d4r/nvngx.dll', archive.namelist())
self.assertNotIn('nvngx.dll', archive.namelist())
self.assertNotIn('d4r/_nvngx.dll', archive.namelist())