Windows: drag-in install with a per-game d4r.ini
Players extract one ZIP into the game folder, copy their two NVIDIA DLLs, and start the game from Steam, Epic or a shortcut. No launcher script is needed; settings live in d4r\d4r.ini. - Shim player mode: when the launcher has not set D4R_HIP_ROOT and d4r.ini sits beside the shim, derive the backend environment from it. Codegen flags stay at the Windows-validated values. NVIDIA DLLs must match an exact SHA256 in dll-pins.txt, with "you have X, you need Y" errors. Rejects a vkd3d/Wine d3d12.dll and code objects built for another GPU. [DLSS] Model accepts K or M and is applied by rewriting the render preset hints. - d4r.ini exposes only options that exist on Windows: Model, AsyncInterop, ValidateOutput, Log, CacheDir. Invalid values fall back with a note in d4r_nvngx.log. - d4r_nvngx_loader: one _nvngx.dll for all GPUs. It matches the game's adapter by LUID and loads _nvngx_<arch>.dll from the same folder. - OptiScaler patch 0006: relative NvngxPath/NvngxDlssPath/ NvngxFeaturePath resolve against OptiScaler's folder. - package-windows-release.ps1 builds the release from the per-target game packages and refuses mixed targets or differing shared runtimes. - Tests: test-player-config.ps1 (ini and DLL-pin decisions, loader selection on the local GPU) and test-windows-release-package.ps1.
This commit is contained in:
1 parent
7ab56d42fc
commit
65ab1460a8
13 files changed
+1004
-1
No files matched your search
@@ -0,0 +1,176 @@
|
||||
// Drag-in releases ship one d4r folder for every supported GPU. This small
|
||||
// _nvngx.dll is what OptiScaler loads: on first use it reads the real HIP
|
||||
// architecture of the game's adapter (matched by LUID) and loads that
|
||||
// target's shim, d4r\_nvngx_<arch>.dll, from the same folder. The per-target
|
||||
// shim still enforces its own compiled target, so a mismatch cannot run.
|
||||
// Keeping every file in one folder preserves NGX's search for
|
||||
// nvngx_dlss.dll beside the calling module.
|
||||
#include "d3d12_external.h"
|
||||
#include "ngx_public_abi.h"
|
||||
#include <dxgi.h>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
constexpr unsigned failure = 0xBAD00002, unsupported = 4;
|
||||
std::mutex selectMutex;
|
||||
HMODULE shim = nullptr;
|
||||
bool selectionFailed = false;
|
||||
|
||||
std::filesystem::path module_directory() {
|
||||
HMODULE module = nullptr;
|
||||
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&module_directory), &module);
|
||||
wchar_t path[32768]{}; GetModuleFileNameW(module, path, 32768);
|
||||
return std::filesystem::path(path).parent_path();
|
||||
}
|
||||
// The shim never ran, so the loader writes the player's log itself.
|
||||
// Fresh at each launch, then appended: OptiScaler can query several adapters.
|
||||
void report(const std::string& message) {
|
||||
static bool fresh = true;
|
||||
std::ofstream(module_directory() / L"d4r_nvngx.log", fresh ? std::ios::trunc : std::ios::app)
|
||||
<< "d4r: DLSS disabled: " << message << "\n";
|
||||
fresh = false;
|
||||
std::fprintf(stderr, "D4R_WINDOWS_FAILURE %s\n", message.c_str());
|
||||
}
|
||||
std::string supported_list(const std::filesystem::path& dir) {
|
||||
std::string list;
|
||||
for (const auto& target : d4r::diag::gpu_targets)
|
||||
if (std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(target.architecture) + L".dll")))
|
||||
list += (list.empty() ? "" : ", ") + std::string(target.architecture);
|
||||
return list.empty() ? "none (the d4r folder is incomplete; extract the ZIP again)" : list;
|
||||
}
|
||||
|
||||
// Loads the matching shim once. A null LUID (calls made before NGX init)
|
||||
// accepts the only HIP device with a shim in this folder.
|
||||
HMODULE select(const LUID* luid) {
|
||||
std::lock_guard<std::mutex> lock(selectMutex);
|
||||
if (shim || selectionFailed) return shim;
|
||||
const auto dir = module_directory();
|
||||
try {
|
||||
// Spoofed architectures invalidate the target guarantee; see hip_api.h.
|
||||
for (const char* name : {"HSA_OVERRIDE_GFX_VERSION", "HSA_OVERRIDE_GFX_VERSION_0"})
|
||||
if (std::getenv(name)) throw std::runtime_error(std::string("unset ") + name + "; d4r does not support architecture overrides");
|
||||
d4r::diag::HipApi hip((dir / L"hip").string());
|
||||
if (hip.hipInit(0) != hipSuccess) throw std::runtime_error("the AMD HIP runtime found no usable GPU; update the AMD graphics driver");
|
||||
int count = 0; (void)hip.hipGetDeviceCount(&count);
|
||||
std::string found, detected;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
hipDeviceProp_t props{};
|
||||
if (hip.hipGetDeviceProperties(&props, i) != hipSuccess) continue;
|
||||
const auto arch = d4r::diag::gpu_architecture(props.gcnArchName);
|
||||
detected += (detected.empty() ? "" : ", ") + arch + " (" + props.name + ")";
|
||||
if (luid && std::memcmp(props.luid, luid, sizeof(*luid))) continue;
|
||||
if (!luid && !std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(arch) + L".dll"))) continue;
|
||||
if (!found.empty() && found != arch) throw std::runtime_error("several GPUs with different architectures; start the game once DLSS selects one");
|
||||
found = arch;
|
||||
}
|
||||
if (found.empty()) throw std::runtime_error("no supported AMD GPU for this game's adapter. Detected: " +
|
||||
(detected.empty() ? std::string("none") : detected) + ". This release supports: " + supported_list(dir) + ".");
|
||||
const auto path = dir / (L"_nvngx_" + d4r::diag::wide(found) + L".dll");
|
||||
if (!std::filesystem::exists(path))
|
||||
throw std::runtime_error("this release has no build for your GPU (" + found + "). It supports: " + supported_list(dir) + ".");
|
||||
shim = LoadLibraryExW(path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
if (!shim) {
|
||||
selectionFailed = true; // A broken install will not fix itself during this session.
|
||||
throw std::runtime_error("cannot load " + d4r::diag::utf8(path.c_str()) + " (Win32 error " + std::to_string(GetLastError()) + ")");
|
||||
}
|
||||
} catch (const std::exception& error) {
|
||||
// An adapter without a matching GPU (an iGPU queried first) must not
|
||||
// prevent selecting the discrete GPU on a later call.
|
||||
report(error.what());
|
||||
}
|
||||
return shim;
|
||||
}
|
||||
template <typename Fn> Fn resolve(const char* name, const LUID* luid = nullptr) {
|
||||
HMODULE module = select(luid);
|
||||
return module ? reinterpret_cast<Fn>(reinterpret_cast<void*>(GetProcAddress(module, name))) : nullptr;
|
||||
}
|
||||
LUID device_luid(ID3D12Device* device) { return d4r::win::adapter_luid(device); }
|
||||
}
|
||||
|
||||
#define API extern "C" __declspec(dllexport)
|
||||
// Discovery marker for OptiScaler: no GPU or runtime initialization.
|
||||
API unsigned d4r_WindowsBackendVersion() { return 1; }
|
||||
|
||||
API unsigned NVSDK_NGX_D3D12_Init_Ext(unsigned long long app, const wchar_t* data, ID3D12Device* device, unsigned sdk, const void* info) {
|
||||
if (!device) return 0xBAD00005;
|
||||
const LUID luid = device_luid(device);
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_Init_Ext)>("NVSDK_NGX_D3D12_Init_Ext", &luid);
|
||||
return fn ? fn(app, data, device, sdk, info) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_Init(unsigned long long app, const wchar_t* data, ID3D12Device* device, const void* info, unsigned sdk) {
|
||||
if (!device) return 0xBAD00005;
|
||||
const LUID luid = device_luid(device);
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_Init)>("NVSDK_NGX_D3D12_Init", &luid);
|
||||
return fn ? fn(app, data, device, info, sdk) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_Init_ProjectID(const char* project, int engine, const char* version, const wchar_t* data, ID3D12Device* device, unsigned sdk, const void* info) {
|
||||
if (!device) return 0xBAD00005;
|
||||
const LUID luid = device_luid(device);
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_Init_ProjectID)>("NVSDK_NGX_D3D12_Init_ProjectID", &luid);
|
||||
return fn ? fn(project, engine, version, data, device, sdk, info) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const d4r::ngx::FeatureDiscoveryInfo* info,
|
||||
d4r::ngx::FeatureRequirement* requirements) {
|
||||
if (!adapter || !info || !requirements) return 0xBAD00005;
|
||||
DXGI_ADAPTER_DESC desc{};
|
||||
if (FAILED(adapter->GetDesc(&desc))) return failure;
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_GetFeatureRequirements)>("NVSDK_NGX_D3D12_GetFeatureRequirements", &desc.AdapterLuid);
|
||||
if (fn) return fn(adapter, info, requirements);
|
||||
// Same answer as the shim for an adapter without a matching HIP device.
|
||||
*requirements = {}; requirements->FeatureSupported = unsupported;
|
||||
std::strcpy(requirements->MinOSVersion, "10.0.22000.0");
|
||||
return 1;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_AllocateParameters(void** p) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_AllocateParameters)>("NVSDK_NGX_D3D12_AllocateParameters");
|
||||
return fn ? fn(p) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_GetCapabilityParameters(void** p) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_GetCapabilityParameters)>("NVSDK_NGX_D3D12_GetCapabilityParameters");
|
||||
return fn ? fn(p) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_GetParameters(void** p) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_GetParameters)>("NVSDK_NGX_D3D12_GetParameters");
|
||||
return fn ? fn(p) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_DestroyParameters(void* p) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_DestroyParameters)>("NVSDK_NGX_D3D12_DestroyParameters");
|
||||
return fn ? fn(p) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_GetScratchBufferSize(unsigned feature, const void* p, size_t* size) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_GetScratchBufferSize)>("NVSDK_NGX_D3D12_GetScratchBufferSize");
|
||||
return fn ? fn(feature, p, size) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList* list, unsigned feature, void* p, void** out) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_CreateFeature)>("NVSDK_NGX_D3D12_CreateFeature");
|
||||
return fn ? fn(list, feature, p, out) : failure;
|
||||
}
|
||||
API unsigned d4r_D3D12_EvaluateAtBoundary(ID3D12CommandQueue* queue, void* handle, void* p) {
|
||||
auto fn = resolve<decltype(&d4r_D3D12_EvaluateAtBoundary)>("d4r_D3D12_EvaluateAtBoundary");
|
||||
return fn ? fn(queue, handle, p) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, const void* handle, void* p, void* callback) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_EvaluateFeature)>("NVSDK_NGX_D3D12_EvaluateFeature");
|
||||
return fn ? fn(list, handle, p, callback) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_EvaluateFeature_C(ID3D12GraphicsCommandList* list, const void* handle, void* p, void* callback) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_EvaluateFeature_C)>("NVSDK_NGX_D3D12_EvaluateFeature_C");
|
||||
return fn ? fn(list, handle, p, callback) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_ReleaseFeature(void* handle) {
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_ReleaseFeature)>("NVSDK_NGX_D3D12_ReleaseFeature");
|
||||
return fn ? fn(handle) : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_Shutdown() {
|
||||
// Nothing to shut down if no shim was ever selected.
|
||||
{ std::lock_guard<std::mutex> lock(selectMutex); if (!shim) return 1; }
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_Shutdown)>("NVSDK_NGX_D3D12_Shutdown");
|
||||
return fn ? fn() : failure;
|
||||
}
|
||||
API unsigned NVSDK_NGX_D3D12_Shutdown1(ID3D12Device* device) {
|
||||
{ std::lock_guard<std::mutex> lock(selectMutex); if (!shim) return 1; }
|
||||
auto fn = resolve<decltype(&NVSDK_NGX_D3D12_Shutdown1)>("NVSDK_NGX_D3D12_Shutdown1");
|
||||
return fn ? fn(device) : failure;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Native Windows backend. The Linux/Wine implementation remains separate.
|
||||
#include "ngx_windows_runtime.h"
|
||||
#include "player_config.h"
|
||||
#include <dxgi.h>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
@@ -59,6 +60,7 @@ API unsigned d4r_WindowsBackendVersion() { return 1; }
|
||||
API unsigned NVSDK_NGX_D3D12_Init_Ext(unsigned long long app, const wchar_t* data, ID3D12Device* device, unsigned sdk, const void* info) {
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
d4r::win::player::ensure_environment();
|
||||
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, app, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info));
|
||||
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1")
|
||||
d4r::win::commands::install(device);
|
||||
@@ -72,6 +74,7 @@ API unsigned NVSDK_NGX_D3D12_Init_ProjectID(const char* project, int engine, con
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
if (!project || !*project) return invalid;
|
||||
d4r::win::player::ensure_environment();
|
||||
d4r::ngx::ProjectIdentity identity{project, engine, version ? version : ""};
|
||||
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, 0, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info), &identity);
|
||||
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1") d4r::win::commands::install(device);
|
||||
@@ -84,6 +87,7 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const
|
||||
return call([&] {
|
||||
*requirements = {}; requirements->FeatureSupported = 16; // Unsupported feature.
|
||||
if (info->FeatureID != 1) return 1u;
|
||||
d4r::win::player::ensure_environment();
|
||||
DXGI_ADAPTER_DESC desc{}; d4r::win::dx(adapter->GetDesc(&desc), "NGX requirements adapter");
|
||||
// OptiScaler can override the DXGI vendor ID. The physical HIP
|
||||
// architecture and full adapter LUID are the authoritative identity.
|
||||
@@ -116,6 +120,7 @@ API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned
|
||||
std::lock_guard<std::mutex> lock(apiMutex);
|
||||
return call([&] {
|
||||
if (!runtime || feature != 1 || !p || !out) return invalid;
|
||||
d4r::win::player::apply_preset(p);
|
||||
auto value = std::make_shared<d4r::win::Feature>(runtime, p);
|
||||
auto handle = std::make_unique<Handle>(); handle->Id = nextId++;
|
||||
features.emplace(handle.get(), std::move(value)); *out = handle.release(); return 1u;
|
||||
@@ -141,6 +146,7 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, co
|
||||
};
|
||||
auto snapshot = std::make_shared<Snapshot>();
|
||||
if (!snapshot->parameters) return failure;
|
||||
d4r::win::player::apply_preset(p);
|
||||
d4r::ngx::copy_create(p, snapshot->parameters); d4r::ngx::copy_frame(p, snapshot->parameters);
|
||||
const char* names[] = {"Color", "Depth", "MotionVectors", "Output", "ExposureTexture"};
|
||||
const char* states[] = {"D4R.Color.State", "D4R.Depth.State", "D4R.Motion.State", "D4R.Output.State", "D4R.Exposure.State"};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# NVIDIA DLLs the Windows backend accepts, by exact SHA256. NVIDIA DLLs are
|
||||
# never shipped or committed; players copy their own.
|
||||
# dlss = nvngx_dlss.dll (d4r\nvngx_dlss.dll)
|
||||
# ngx = _nvngx.dll, NVIDIA's NGX core from a display driver (d4r\ngx\_nvngx.dll)
|
||||
# status: validated = tested on Windows hardware; anything else is accepted
|
||||
# but reported as unvalidated in d4r\d4r_nvngx.log.
|
||||
# kind sha256 version status
|
||||
dlss 3975567B8943C53ACCE397F2B72380092F84F162D00B0D2C7D08A1025C563983 310.9.1 validated
|
||||
ngx 66767018C36B3BAB46398DADE3ADF173DAA3730FDA75965689EA848C9BC4E79B 32.0.16.1714 validated
|
||||
@@ -0,0 +1,33 @@
|
||||
// Calls the drag-in loader the way OptiScaler does: the GPU-free discovery
|
||||
// marker, then NGX feature requirements for every DXGI adapter.
|
||||
#include <windows.h>
|
||||
#include <dxgi.h>
|
||||
#include <cstdio>
|
||||
|
||||
struct Info { unsigned SDKVersion, FeatureID; char reserved[48]; };
|
||||
struct Requirement { unsigned FeatureSupported, MinHWArchitecture; char MinOSVersion[255]; };
|
||||
using Requirements = unsigned (*)(IDXGIAdapter*, const Info*, Requirement*);
|
||||
using Version = unsigned (*)();
|
||||
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc != 2) { std::printf("usage: loader_selection_probe LOADER_DLL\n"); return 2; }
|
||||
HMODULE loader = LoadLibraryW(argv[1]);
|
||||
if (!loader) { std::printf("LOAD_FAILED error=%lu\n", GetLastError()); return 2; }
|
||||
auto version = reinterpret_cast<Version>(reinterpret_cast<void*>(GetProcAddress(loader, "d4r_WindowsBackendVersion")));
|
||||
auto requirements = reinterpret_cast<Requirements>(reinterpret_cast<void*>(GetProcAddress(loader, "NVSDK_NGX_D3D12_GetFeatureRequirements")));
|
||||
if (!version || !requirements) { std::printf("EXPORTS_MISSING\n"); return 2; }
|
||||
std::printf("MARKER version=%u hip_loaded=%d\n", version(), GetModuleHandleW(L"amdhip64_7.dll") != nullptr);
|
||||
IDXGIFactory1* factory = nullptr;
|
||||
if (FAILED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&factory)))) return 2;
|
||||
IDXGIAdapter1* adapter = nullptr;
|
||||
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) == S_OK; ++i) {
|
||||
DXGI_ADAPTER_DESC1 desc{}; adapter->GetDesc1(&desc);
|
||||
Info info{0x15, 1, {}}; Requirement result{};
|
||||
const unsigned status = requirements(adapter, &info, &result);
|
||||
std::printf("ADAPTER vendor=0x%04x status=0x%x supported=%u marker=0x%x name=%ls\n", desc.VendorId, status,
|
||||
result.FeatureSupported, result.MinHWArchitecture, desc.Description);
|
||||
adapter->Release();
|
||||
}
|
||||
factory->Release();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
#pragma once
|
||||
// Drag-in install, as on Linux: the game folder holds dxgi.dll (OptiScaler),
|
||||
// OptiScaler.ini and a d4r folder containing this shim and d4r.ini. When the
|
||||
// launcher has not set D4R_HIP_ROOT and d4r.ini exists beside this DLL, the
|
||||
// shim derives the backend's environment itself, so the game can be started
|
||||
// from Steam, Epic or a shortcut. Without d4r.ini nothing changes: diagnostic
|
||||
// runs keep using the launcher's environment.
|
||||
//
|
||||
// Codegen switches are fixed to the Windows-validated values; d4r.ini only
|
||||
// selects the model (K/M), diagnostics and the cache folder. NVIDIA DLLs must
|
||||
// match an identity in dll-pins.txt before anything is loaded.
|
||||
#include "hip_api.h"
|
||||
#include "ngx_parameters.h"
|
||||
#include <bcrypt.h>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace d4r::win::player {
|
||||
struct IniEntry { std::string section, key, value; };
|
||||
struct Pin { std::string kind, sha256, version, status; };
|
||||
struct Settings {
|
||||
unsigned preset = 11; // K
|
||||
bool asyncInterop = false, validateOutput = false, verbose = false;
|
||||
std::wstring cacheDir;
|
||||
};
|
||||
struct State {
|
||||
bool active = false;
|
||||
unsigned preset = 0;
|
||||
std::filesystem::path dir;
|
||||
std::vector<std::string> notes;
|
||||
};
|
||||
inline State& state() { static State value; return value; }
|
||||
|
||||
inline std::string trim(const std::string& value) {
|
||||
const auto first = value.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos) return {};
|
||||
return value.substr(first, value.find_last_not_of(" \t\r\n") - first + 1);
|
||||
}
|
||||
inline std::string lower(std::string value) {
|
||||
for (char& c : value) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
return value;
|
||||
}
|
||||
inline std::string upper(std::string value) {
|
||||
for (char& c : value) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
|
||||
return value;
|
||||
}
|
||||
inline std::filesystem::path module_directory() {
|
||||
HMODULE module = nullptr;
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&module_directory), &module)) throw std::runtime_error("Locate the d4r shim directory");
|
||||
wchar_t path[32768]{}; const DWORD length = GetModuleFileNameW(module, path, 32768);
|
||||
if (!length || length == 32768) throw std::runtime_error("d4r shim path is invalid");
|
||||
return std::filesystem::path(path).parent_path();
|
||||
}
|
||||
inline std::string read_text(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) throw std::runtime_error("Cannot read " + diag::utf8(path.c_str()));
|
||||
std::stringstream text; text << file.rdbuf(); return text.str();
|
||||
}
|
||||
|
||||
// The configparser dialect of packaging/d4r.ini and the Linux shim: [Section],
|
||||
// key = value, full-line ; or # comments, inline comments after whitespace.
|
||||
inline std::vector<IniEntry> parse_ini(const std::string& text) {
|
||||
std::vector<IniEntry> entries; std::string section;
|
||||
size_t start = text.compare(0, 3, "\xEF\xBB\xBF") == 0 ? 3 : 0;
|
||||
while (start < text.size()) {
|
||||
size_t end = text.find('\n', start);
|
||||
if (end == std::string::npos) end = text.size();
|
||||
const std::string line = trim(text.substr(start, end - start));
|
||||
start = end + 1;
|
||||
if (line.empty() || line[0] == ';' || line[0] == '#') continue;
|
||||
if (line[0] == '[') { section = trim(line.substr(1, line.find(']') - 1)); continue; }
|
||||
const size_t equals = line.find('=');
|
||||
if (equals == std::string::npos) continue;
|
||||
std::string value = line.substr(equals + 1);
|
||||
for (size_t i = 1; i < value.size(); ++i)
|
||||
if ((value[i] == ';' || value[i] == '#') && (value[i - 1] == ' ' || value[i - 1] == '\t')) { value.resize(i); break; }
|
||||
entries.push_back({section, trim(line.substr(0, equals)), trim(value)});
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Invalid values fall back to the safe default and are reported in the log
|
||||
// (and by d4r-check.ps1), rather than silently changing behaviour.
|
||||
inline Settings read_settings(const std::vector<IniEntry>& ini, std::vector<std::string>& notes) {
|
||||
Settings settings;
|
||||
const auto flag = [&](const IniEntry& entry, bool fallback) {
|
||||
const auto value = lower(entry.value);
|
||||
if (value.empty() || value == "auto") return fallback;
|
||||
if (value == "1" || value == "true" || value == "yes" || value == "on") return true;
|
||||
if (value == "0" || value == "false" || value == "no" || value == "off") return false;
|
||||
notes.push_back("d4r.ini: [" + entry.section + "] " + entry.key + " must be true or false, not '" + entry.value +
|
||||
"'; using " + (fallback ? "true" : "false"));
|
||||
return fallback;
|
||||
};
|
||||
for (const auto& entry : ini) {
|
||||
const auto name = lower(entry.section + "." + entry.key); // Matched case-insensitively, reported as written.
|
||||
if (name == "dlss.model") {
|
||||
const auto value = upper(entry.value);
|
||||
if (value.empty() || value == "AUTO" || value == "K" || value == "DLSS4" || value == "11") settings.preset = 11;
|
||||
else if (value == "M" || value == "DLSS4.5" || value == "13") settings.preset = 13;
|
||||
else notes.push_back("d4r.ini: [DLSS] Model '" + entry.value + "' is not available on Windows (use K or M); using K");
|
||||
} else if (name == "interop.asyncinterop") settings.asyncInterop = flag(entry, false);
|
||||
else if (name == "debug.validateoutput") settings.validateOutput = flag(entry, false);
|
||||
else if (name == "debug.log") {
|
||||
const auto value = lower(entry.value);
|
||||
if (value == "verbose") settings.verbose = true;
|
||||
else if (!value.empty() && value != "normal" && value != "auto")
|
||||
notes.push_back("d4r.ini: [Debug] Log must be normal or verbose, not '" + entry.value + "'; using normal");
|
||||
} else if (name == "paths.cachedir") {
|
||||
if (!entry.value.empty() && lower(entry.value) != "auto") settings.cacheDir = diag::wide(entry.value);
|
||||
} else notes.push_back("d4r.ini: [" + entry.section + "] " + entry.key + " is not a Windows setting; ignored");
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
// "kind sha256 version status" lines; kind is dlss or ngx.
|
||||
inline std::vector<Pin> parse_pins(const std::string& text) {
|
||||
std::vector<Pin> pins; std::istringstream lines(text); std::string line;
|
||||
while (std::getline(lines, line)) {
|
||||
line = trim(line);
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
std::istringstream fields(line); Pin pin;
|
||||
if (!(fields >> pin.kind >> pin.sha256 >> pin.version >> pin.status) || pin.sha256.size() != 64 ||
|
||||
(pin.kind != "dlss" && pin.kind != "ngx")) throw std::runtime_error("dll-pins.txt is damaged; extract the d4r ZIP again");
|
||||
pin.sha256 = upper(pin.sha256); pins.push_back(pin);
|
||||
}
|
||||
return pins;
|
||||
}
|
||||
|
||||
inline std::string sha256(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) return {};
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr; BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
struct Cleanup { BCRYPT_ALG_HANDLE& a; BCRYPT_HASH_HANDLE& h;
|
||||
~Cleanup() { if (h) BCryptDestroyHash(h); if (a) BCryptCloseAlgorithmProvider(a, 0); } } cleanup{algorithm, hash};
|
||||
if (BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_SHA256_ALGORITHM, nullptr, 0) < 0 ||
|
||||
BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0) < 0) throw std::runtime_error("SHA256 unavailable");
|
||||
std::vector<char> buffer(1 << 20);
|
||||
while (file) {
|
||||
file.read(buffer.data(), std::streamsize(buffer.size()));
|
||||
if (file.gcount() > 0 && BCryptHashData(hash, reinterpret_cast<PUCHAR>(buffer.data()), ULONG(file.gcount()), 0) < 0)
|
||||
throw std::runtime_error("SHA256 failed");
|
||||
}
|
||||
unsigned char digest[32]{};
|
||||
if (BCryptFinishHash(hash, digest, sizeof(digest), 0) < 0) throw std::runtime_error("SHA256 failed");
|
||||
static const char hex[] = "0123456789ABCDEF"; std::string text;
|
||||
for (unsigned char byte : digest) { text += hex[byte >> 4]; text += hex[byte & 15]; }
|
||||
return text;
|
||||
}
|
||||
inline std::string file_version(const std::filesystem::path& path) {
|
||||
DWORD ignored = 0; const DWORD size = GetFileVersionInfoSizeW(path.c_str(), &ignored);
|
||||
std::vector<char> data(size);
|
||||
VS_FIXEDFILEINFO* info = nullptr; UINT length = 0;
|
||||
if (!size || !GetFileVersionInfoW(path.c_str(), 0, size, data.data()) ||
|
||||
!VerQueryValueW(data.data(), L"\\", reinterpret_cast<void**>(&info), &length) || !info) return "unknown";
|
||||
return std::to_string(HIWORD(info->dwFileVersionMS)) + "." + std::to_string(LOWORD(info->dwFileVersionMS)) + "." +
|
||||
std::to_string(HIWORD(info->dwFileVersionLS)) + "." + std::to_string(LOWORD(info->dwFileVersionLS));
|
||||
}
|
||||
// Exact identity check with the fix spelled out: what was found, what is needed.
|
||||
inline void verify_dll(const std::filesystem::path& dir, const std::vector<Pin>& pins, const char* kind,
|
||||
const wchar_t* relative, const char* label, std::vector<std::string>& notes) {
|
||||
const auto path = dir / relative;
|
||||
std::string wanted;
|
||||
for (const auto& pin : pins) if (pin.kind == kind) wanted += (wanted.empty() ? "" : " or ") + pin.version;
|
||||
const auto hash = sha256(path);
|
||||
if (hash.empty())
|
||||
throw std::runtime_error(std::string(label) + " is missing. Copy NVIDIA's " + label + " (version " + wanted +
|
||||
") to d4r\\" + diag::utf8(relative) + " and restart the game.");
|
||||
for (const auto& pin : pins) if (pin.kind == kind && pin.sha256 == hash) {
|
||||
if (pin.status != "validated")
|
||||
notes.push_back(std::string(label) + " " + pin.version + " is accepted but not yet validated on Windows");
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("Wrong " + std::string(label) + ": you have version " + file_version(path) +
|
||||
" (or a modified file); d4r on Windows needs exactly " + wanted + ". Replace d4r\\" + diag::utf8(relative) +
|
||||
" and restart the game.");
|
||||
}
|
||||
|
||||
// The installer used to check these before touching the game; a drag-in
|
||||
// install checks them at startup instead.
|
||||
inline void verify_code_objects(const std::filesystem::path& dir) {
|
||||
const std::string arch = diag::HipApi::target_arch();
|
||||
std::vector<std::filesystem::path> objects{dir / (L"pixel_convert_" + diag::wide(arch) + L".hsaco")};
|
||||
const auto native = dir / L"native" / diag::wide(arch);
|
||||
if (!std::filesystem::is_directory(native)) throw std::runtime_error("d4r\\native\\" + arch + " is missing; extract the d4r ZIP again");
|
||||
for (const auto& entry : std::filesystem::directory_iterator(native))
|
||||
if (entry.path().extension() == L".hsaco") objects.push_back(entry.path());
|
||||
for (const auto& object : objects) {
|
||||
try { diag::require_code_object_target(object, arch); }
|
||||
catch (const std::exception&) {
|
||||
throw std::runtime_error(diag::utf8(object.filename().c_str()) + " is not built for " + arch +
|
||||
" or is damaged; extract the d4r ZIP again (do not rename or mix files from other GPUs)");
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void reject_translation_layer() {
|
||||
wchar_t exe[32768]{}; GetModuleFileNameW(nullptr, exe, 32768);
|
||||
const auto proxy = std::filesystem::path(exe).parent_path() / L"d3d12.dll";
|
||||
std::error_code error;
|
||||
const auto size = std::filesystem::file_size(proxy, error);
|
||||
if (error || size > 64ull << 20) return;
|
||||
auto text = lower(read_text(proxy));
|
||||
if (text.find("vkd3d") != std::string::npos || text.find("wined3d") != std::string::npos)
|
||||
throw std::runtime_error("the game folder contains a d3d12.dll from vkd3d-proton/Wine (left over from a Linux "
|
||||
"install?). Move d3d12.dll and d3d12core.dll out of the game folder; d4r on Windows uses native D3D12");
|
||||
}
|
||||
|
||||
// Update the OS block (ZLUDA/Rust, later DLLs) and this CRT's copy (getenv).
|
||||
inline void set_variable(const wchar_t* name, const std::optional<std::wstring>& value) {
|
||||
SetEnvironmentVariableW(name, value ? value->c_str() : nullptr);
|
||||
_wputenv_s(name, value ? value->c_str() : L"");
|
||||
}
|
||||
// Only when the game has no console/pipe: never steal a launcher's streams.
|
||||
inline bool unattached(DWORD stream) {
|
||||
const HANDLE current = GetStdHandle(stream);
|
||||
return !current || current == INVALID_HANDLE_VALUE;
|
||||
}
|
||||
inline void redirect(FILE* crt, DWORD stream, const std::filesystem::path& path) {
|
||||
FILE* reopened = nullptr;
|
||||
if (_wfreopen_s(&reopened, path.c_str(), L"a", crt) || !reopened) return;
|
||||
std::setvbuf(crt, nullptr, _IONBF, 0);
|
||||
SetStdHandle(stream, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(crt))));
|
||||
}
|
||||
|
||||
inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
std::filesystem::path cache = settings.cacheDir;
|
||||
if (cache.empty()) {
|
||||
const wchar_t* localAppData = _wgetenv(L"LOCALAPPDATA");
|
||||
if (!localAppData || !*localAppData) throw std::runtime_error("LOCALAPPDATA is not set; set [Paths] CacheDir in d4r.ini");
|
||||
cache = std::filesystem::path(localAppData) / L"d4r" / L"cache";
|
||||
} else if (cache.is_relative()) cache = dir / cache;
|
||||
std::filesystem::create_directories(cache);
|
||||
const auto p = [&](const std::wstring& relative) { return std::optional<std::wstring>((dir / relative).wstring()); };
|
||||
const std::optional<std::wstring> on = std::wstring(L"1"), off = std::wstring(L"0"), unset;
|
||||
const wchar_t* path = _wgetenv(L"PATH");
|
||||
// Same codegen and paths as windows-game.ps1; only diagnostics differ.
|
||||
const std::pair<const wchar_t*, std::optional<std::wstring>> values[] = {
|
||||
{L"D4R_HIP_ROOT", p(L"hip")}, {L"HIP_PATH", p(L"hip")},
|
||||
{L"D4R_NVCUDA_DLL", p(L"zluda\\nvcuda.dll")}, {L"ZLUDA_CUDA_LIB", p(L"zluda\\nvcuda.dll")},
|
||||
{L"D4R_NVAPI_DLL", p(L"nvapi\\" + diag::wide(diag::HipApi::target_arch()) + L"\\nvapi64.dll")}, {L"D4R_NVAPI_BACKEND", p(L"zluda\\nvapi64.dll")},
|
||||
{L"D4R_NGX_CORE", p(L"ngx\\_nvngx.dll")}, {L"D4R_DLSS_DLL", p(L"nvngx_dlss.dll")},
|
||||
{L"D4R_FORMAT_MODULE", p(L"pixel_convert_" + diag::wide(diag::HipApi::target_arch()) + L".hsaco")},
|
||||
{L"D4R_ZLUDA_NATIVE_DIR", p(L"native\\" + diag::wide(diag::HipApi::target_arch()))},{L"D4R_D3D12_COMMAND_BACKEND", on},
|
||||
{L"D4R_ZLUDA_WMMA", on}, {L"D4R_ZLUDA_WMMA_FP8", on}, {L"D4R_ZLUDA_WMMA_FP8_NATIVE", off},
|
||||
{L"D4R_ZLUDA_WMMA_F16_REFERENCE", on},
|
||||
{L"D4R_ZLUDA_IGNORE_DENORMAL", unset}, {L"D4R_ZLUDA_FAST_MATH", unset}, {L"D4R_ZLUDA_WMMA_F32ACC", unset},
|
||||
{L"D4R_ZLUDA_WAVE64", unset}, {L"D4R_ZLUDA_WMMA_LAYOUT", unset},
|
||||
// Per-launch kernel lines are diagnostics; ZLUDA excludes this flag from its cache key.
|
||||
{L"D4R_ZLUDA_VERBOSE", settings.verbose ? on : unset},
|
||||
{L"D4R_QUIET_API", settings.verbose ? unset : on},
|
||||
{L"D4R_VALIDATE_OUTPUT", settings.validateOutput ? on : unset},
|
||||
{L"D4R_ASYNC_INTEROP", settings.asyncInterop ? on : unset},
|
||||
{L"D4R_PROFILE_STAGES", unset}, {L"D4R_ZLUDA_PROFILE", unset}, {L"D4R_ZLUDA_PROFILE_DEFERRED", unset},
|
||||
{L"D4R_ZLUDA_PROFILE_ALLOW_LEGACY", unset}, {L"D4R_ZLUDA_PROFILE_EVERY", unset},
|
||||
{L"D4R_PROFILE_COMMAND_HOOKS", unset}, {L"D4R_ZLUDA_PROFILE_API", unset}, {L"D4R_PROFILE_GPU_BOUNDARY", unset},
|
||||
{L"D4R_DISABLE_INTEROP_LIST_CACHE", unset}, {L"D4R_BATCH_INPUT_COPIES", unset}, {L"D4R_INTEROP_VERIFY", unset},
|
||||
{L"D4R_DIAG_DIR", unset}, {L"ZLUDA_LOG_DIR", unset},
|
||||
{L"ZLUDA_CACHE_DIR", std::optional<std::wstring>(cache.wstring())},
|
||||
{L"PATH", std::optional<std::wstring>((dir / L"hip\\bin").wstring() + L";" + (dir / L"zluda").wstring() +
|
||||
(path ? std::wstring(L";") + path : std::wstring()))},
|
||||
};
|
||||
for (const auto& [name, value] : values) set_variable(name, value);
|
||||
const wchar_t* wgp = _wgetenv(L"D4R_ZLUDA_WGP");
|
||||
if (wgp && std::wstring(wgp) != L"1") set_variable(L"D4R_ZLUDA_WGP", unset);
|
||||
}
|
||||
|
||||
// Called before any path variable is read. A failure leaves DLSS unavailable
|
||||
// (the game keeps its own upscaler) and is written to d4r\d4r_nvngx.log.
|
||||
inline void ensure_environment() {
|
||||
static std::once_flag once; static std::string failure;
|
||||
std::call_once(once, [] {
|
||||
try {
|
||||
if (const char* root = std::getenv("D4R_HIP_ROOT"); root && *root) return; // Launcher/diagnostic run.
|
||||
const auto dir = module_directory();
|
||||
if (!std::filesystem::exists(dir / L"d4r.ini")) return; // Developer layout: keep the original requirement.
|
||||
auto& current = state(); current.dir = dir;
|
||||
const auto log = dir / L"d4r_nvngx.log";
|
||||
if (unattached(STD_ERROR_HANDLE)) {
|
||||
{ std::ofstream truncate(log, std::ios::trunc); } // Rewritten at every launch, as on Linux.
|
||||
redirect(stderr, STD_ERROR_HANDLE, log);
|
||||
}
|
||||
const auto settings = read_settings(parse_ini(read_text(dir / L"d4r.ini")), current.notes);
|
||||
if (settings.verbose && unattached(STD_OUTPUT_HANDLE)) redirect(stdout, STD_OUTPUT_HANDLE, log);
|
||||
const auto pins = parse_pins(read_text(dir / L"dll-pins.txt"));
|
||||
verify_dll(dir, pins, "dlss", L"nvngx_dlss.dll", "nvngx_dlss.dll", current.notes);
|
||||
verify_dll(dir, pins, "ngx", L"ngx\\_nvngx.dll", "_nvngx.dll", current.notes);
|
||||
reject_translation_layer();
|
||||
verify_code_objects(dir);
|
||||
apply(dir, settings);
|
||||
current.active = true; current.preset = settings.preset;
|
||||
std::fprintf(stderr, "d4r: player mode, model=%s validate=%u async=%u log=%s, architecture=%s\n",
|
||||
settings.preset == 13 ? "M" : "K", unsigned(settings.validateOutput), unsigned(settings.asyncInterop),
|
||||
settings.verbose ? "verbose" : "normal", diag::HipApi::target_arch());
|
||||
} catch (const std::exception& error) { failure = error.what(); }
|
||||
for (const auto& note : state().notes) std::fprintf(stderr, "d4r: %s\n", note.c_str());
|
||||
if (!failure.empty()) std::fprintf(stderr, "d4r: DLSS disabled: %s\n", failure.c_str());
|
||||
});
|
||||
if (!failure.empty()) throw std::runtime_error(failure);
|
||||
}
|
||||
|
||||
// [DLSS] Model forces one render preset for every quality mode, as the Linux
|
||||
// shim does. Applied at create and evaluate so a recreated feature cannot fall
|
||||
// back to the game's preset. Not used by launcher runs (OptiScaler sets it).
|
||||
inline void apply_preset(void* parameters) {
|
||||
const auto& current = state();
|
||||
if (!current.active || !current.preset || !parameters) return;
|
||||
for (const char* name : {"DLSS.Hint.Render.Preset.DLAA", "DLSS.Hint.Render.Preset.Quality", "DLSS.Hint.Render.Preset.Balanced",
|
||||
"DLSS.Hint.Render.Preset.Performance", "DLSS.Hint.Render.Preset.UltraPerformance", "DLSS.Hint.Render.Preset.UltraQuality"})
|
||||
d4r_ngx_set_uint(parameters, name, current.preset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Checks the drag-in d4r.ini parser and NVIDIA DLL pin decisions with
|
||||
// fixtures. No GPU work, no NVIDIA binaries.
|
||||
#include "player_config.h"
|
||||
#include <functional>
|
||||
|
||||
namespace player = d4r::win::player;
|
||||
namespace {
|
||||
int failures = 0;
|
||||
void expect(bool condition, const std::string& what) {
|
||||
std::printf("%s %s\n", condition ? "ok " : "FAIL", what.c_str());
|
||||
if (!condition) ++failures;
|
||||
}
|
||||
bool has_note(const std::vector<std::string>& notes, const std::string& text) {
|
||||
for (const auto& note : notes) if (note.find(text) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
player::Settings settings(const std::string& ini, std::vector<std::string>& notes) {
|
||||
notes.clear(); return player::read_settings(player::parse_ini(ini), notes);
|
||||
}
|
||||
std::string error_of(const std::function<void()>& fn) {
|
||||
try { fn(); } catch (const std::exception& error) { return error.what(); }
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc != 2) { std::printf("usage: player_config_probe FIXTURE_DIR\n"); return 2; }
|
||||
const std::filesystem::path dir(argv[1]);
|
||||
std::vector<std::string> notes;
|
||||
|
||||
auto s = settings("", notes);
|
||||
expect(s.preset == 11 && !s.asyncInterop && !s.validateOutput && !s.verbose && notes.empty(), "empty d4r.ini uses K, sync, no validation");
|
||||
s = settings("\xEF\xBB\xBF[DLSS]\nModel = M ; DLSS 4.5\n", notes);
|
||||
expect(s.preset == 13 && notes.empty(), "BOM and inline comment; Model = M");
|
||||
s = settings("[dlss]\nmodel = dlss4.5\n", notes);
|
||||
expect(s.preset == 13, "case-insensitive section/key and alias DLSS4.5");
|
||||
for (const char* model : {"E", "L", "game", "7"}) {
|
||||
s = settings(std::string("[DLSS]\nModel = ") + model + "\n", notes);
|
||||
expect(s.preset == 11 && has_note(notes, "not available on Windows"), std::string("Model = ") + model + " is rejected with a note and K");
|
||||
}
|
||||
s = settings("[Interop]\nAsyncInterop = yes\n[Debug]\nValidateOutput = on\nLog = verbose\n", notes);
|
||||
expect(s.asyncInterop && s.validateOutput && s.verbose && notes.empty(), "boolean spellings and Log = verbose");
|
||||
s = settings("[Debug]\nValidateOutput = maybe\nLog = loud\n", notes);
|
||||
expect(!s.validateOutput && !s.verbose && has_note(notes, "must be true or false, not 'maybe'") &&
|
||||
has_note(notes, "must be normal or verbose"), "invalid values fall back with plain notes");
|
||||
s = settings("[Kernels]\nNativeFp8 = on\n[Latency]\nFrameAge = 1\n", notes);
|
||||
expect(has_note(notes, "[Kernels] NativeFp8 is not a Windows setting") && has_note(notes, "[Latency] FrameAge"),
|
||||
"Linux-only settings are reported as ignored");
|
||||
s = settings("[Paths]\nCacheDir = D:\\d4r-cache\n", notes);
|
||||
expect(s.cacheDir == L"D:\\d4r-cache" && notes.empty(), "CacheDir path");
|
||||
|
||||
// Pins: fixture DLL contents are ordinary text; their hashes are computed here.
|
||||
std::filesystem::create_directories(dir / L"ngx");
|
||||
{ std::ofstream(dir / L"nvngx_dlss.dll", std::ios::binary) << "validated dlss fixture"; }
|
||||
{ std::ofstream(dir / L"ngx" / L"_nvngx.dll", std::ios::binary) << "unvalidated core fixture"; }
|
||||
const auto dlss = player::sha256(dir / L"nvngx_dlss.dll"), core = player::sha256(dir / L"ngx" / L"_nvngx.dll");
|
||||
const auto pins = player::parse_pins("# comment\ndlss " + dlss + " 310.9.1 validated\nngx " + core + " 32.0.15.9636 unvalidated\n"
|
||||
"dlss " + std::string(64, 'A') + " 310.7.0 unvalidated\n");
|
||||
notes.clear();
|
||||
expect(error_of([&] { player::verify_dll(dir, pins, "dlss", L"nvngx_dlss.dll", "nvngx_dlss.dll", notes); }).empty() && notes.empty(),
|
||||
"validated DLSS pin accepted silently");
|
||||
expect(error_of([&] { player::verify_dll(dir, pins, "ngx", L"ngx\\_nvngx.dll", "_nvngx.dll", notes); }).empty() &&
|
||||
has_note(notes, "not yet validated on Windows"), "unvalidated pin accepted with a note");
|
||||
{ std::ofstream(dir / L"nvngx_dlss.dll", std::ios::binary) << "changed"; }
|
||||
auto error = error_of([&] { player::verify_dll(dir, pins, "dlss", L"nvngx_dlss.dll", "nvngx_dlss.dll", notes); });
|
||||
expect(error.find("Wrong nvngx_dlss.dll: you have version") != std::string::npos && error.find("310.9.1 or 310.7.0") != std::string::npos,
|
||||
"changed DLL rejected with you-have/you-need: " + error);
|
||||
std::filesystem::remove(dir / L"nvngx_dlss.dll");
|
||||
error = error_of([&] { player::verify_dll(dir, pins, "dlss", L"nvngx_dlss.dll", "nvngx_dlss.dll", notes); });
|
||||
expect(error.find("is missing. Copy NVIDIA's nvngx_dlss.dll") != std::string::npos, "missing DLL explains where to copy it");
|
||||
expect(!error_of([] { player::parse_pins("dlss short 1 validated\n"); }).empty(), "damaged pins file rejected");
|
||||
expect(!error_of([] { player::parse_pins("other " + std::string(64, 'A') + " 1 validated\n"); }).empty(), "unknown pin kind rejected");
|
||||
|
||||
std::printf("%s PLAYER_CONFIG failures=%d\n", failures ? "FAIL" : "PASS", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user