Windows: DLSS watermark off, check script, 310.7.0 pair, shorter docs

- Set Disable.Watermark on the game path (the Linux shim already does);
  [DLSS] ShowWatermark = true keeps NVIDIA's stamp.

- Always write d4r\d4r_nvngx.log in player mode. It was skipped when the
  launcher gave the game a valid stderr pipe, e.g. Steam.

- Accept NVIDIA's 310.7.0 + _nvngx 32.0.15.9636 as "less tested" pins.
  Native kernels are still gated by PTX identity, so a different DLL
  falls back to translated kernels.

- Add d4r-check.ps1 (ok/MISSING/note lines with a fix for each; -Report
  writes a bug-report ZIP) with 15 fixture tests.

- package-windows-release.ps1: -Architectures subset, ships the check
  script, GPU registry and one GPU-query tool.

- Add a shorter WINDOWS_README.txt; trim README's Windows section and
  merge in the RX 9060 XT / distributor-DLL notes.

- Diagnostic probe accepts preset 12 (L).

Tested on RX 7700 XT without a game: loader and shim start-up, K and M
synthetic NGX frames with both DLL pairs (finite output, native kernels
used). Not tested in a game.
This commit is contained in:
ayo committed 2026-10-03 04:58:05 -04:00
1 parent df415ec828
commit 774efac524
12 files changed
+519 -70

No files matched your search

+271
View File
@@ -0,0 +1,271 @@
# Checks a d4r install on Windows. It only reads files and prints what it finds.
# Run it from the game folder (the one holding the game's .exe and d4r\), or give that folder:
# powershell -ExecutionPolicy Bypass -File d4r\d4r-check.ps1 [GAME_FOLDER]
# Add -Report to also write one ZIP (logs, this output, GPU and driver) to send with a bug report.
[CmdletBinding()]
param(
[string]$GameFolder,
[switch]$Report,
[string]$InventoryFile, # tests: a gpu-inventory.json to use instead of running the GPU query
[string]$ReportDirectory # tests: where -Report writes its ZIP (default: your Desktop)
)
$ErrorActionPreference = 'Stop'
if (!$GameFolder) {
$GameFolder = if ((Split-Path $PSScriptRoot -Leaf) -eq 'd4r') { Split-Path $PSScriptRoot -Parent } else { (Get-Location).Path }
}
$game = [IO.Path]::GetFullPath($GameFolder).TrimEnd('\')
$d4r = Join-Path $game 'd4r'
$problems = 0
$lines = [Collections.Generic.List[string]]::new()
function Say([string]$text) { $lines.Add($text); Write-Host $text }
function Ok([string]$text) { Say (' ok ' + $text) }
function Bad([string]$text) { Say (' MISSING ' + $text); $script:problems++ }
function Note([string]$text) { Say (' note ' + $text) }
function Hash([string]$path) { (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash }
function Version([string]$path) {
try { $v = (Get-Item -LiteralPath $path).VersionInfo; if ($v.FileVersion) { return ($v.FileVersion -replace ',\s*', '.') } } catch {}
return 'unknown'
}
function Read-TextFile([string]$path) { [IO.File]::ReadAllText($path) }
Say "d4r install in $game"
if (!(Test-Path -LiteralPath $d4r -PathType Container)) {
Say 'no d4r folder here; run this from the folder with the game .exe (extract the d4r ZIP there first)'
exit 1
}
if (@(Get-ChildItem -LiteralPath $game -Filter '*.exe' -File -ErrorAction SilentlyContinue).Count) { Ok 'game executable next to d4r\' }
else { Note 'no .exe next to d4r\ (the d4r files must sit in the folder of the game''s main .exe; for Unreal Engine games that is <game>\<Project>\Binaries\Win64)' }
# --- the install's own file list ---------------------------------------------------------------
$manifest = $null
$manifestPath = Join-Path $d4r 'package.json'
if (Test-Path -LiteralPath $manifestPath) {
try { $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } catch { $manifest = $null }
}
if (!$manifest) { Bad 'd4r\package.json is missing or damaged (extract the d4r ZIP again)' }
# --- files --------------------------------------------------------------------------------------
$required = @('dxgi.dll', 'OptiScaler.ini', 'd4r\_nvngx.dll', 'd4r\d4r.ini', 'd4r\dll-pins.txt',
'd4r\hip\bin\amdhip64_7.dll', 'd4r\zluda\nvcuda.dll', 'd4r\zluda\nvapi64.dll')
foreach ($file in $required) {
if (Test-Path -LiteralPath (Join-Path $game $file) -PathType Leaf) { Ok $file }
else { Bad "$file (extract the d4r ZIP again over the game folder)" }
}
if ($manifest) {
$edited = @('OptiScaler.ini', 'd4r/d4r.ini') # players are meant to edit these
$damaged = 0
foreach ($entry in $manifest.files) {
$path = Join-Path $game ($entry.path -replace '/', '\')
if (!(Test-Path -LiteralPath $path -PathType Leaf)) {
if ($required -notcontains ($entry.path -replace '/', '\')) { Bad "$($entry.path) (extract the d4r ZIP again)" }
continue
}
if ((Hash $path) -ne $entry.sha256) {
if ($entry.path -eq 'dxgi.dll') { Note 'dxgi.dll is not the one that came with d4r (another OptiScaler or ReShade?). Move it away and extract the d4r ZIP again' }
elseif ($edited -contains $entry.path) { Note "$($entry.path) has been edited (fine if you did it)" }
else { Bad "$($entry.path) differs from the release (damaged or replaced; extract the d4r ZIP again)"; $damaged++ }
}
}
if (!$damaged) { Ok 'every other d4r file matches the release' }
}
# --- OptiScaler.ini ---------------------------------------------------------------------------
$optiIni = Join-Path $game 'OptiScaler.ini'
if (Test-Path -LiteralPath $optiIni) {
$text = Read-TextFile $optiIni
if ($text -notmatch '(?im)^\s*AllowExternalBackend\s*=\s*true') { Bad 'OptiScaler.ini: [DLSS] AllowExternalBackend must be true (this is not the d4r OptiScaler.ini)' }
if ($text -notmatch '(?im)^\s*Dx12Upscaler\s*=\s*dlss') { Note 'OptiScaler.ini: [Upscalers] Dx12Upscaler is not dlss; the game will not use d4r' }
$m = [regex]::Match($text, '(?im)^\s*NvngxPath\s*=\s*(\S+)')
if (!$m.Success) { Bad 'OptiScaler.ini: [Libraries] NvngxPath is missing (it must name the d4r folder)' }
else {
$folder = $m.Groups[1].Value
$resolved = if ([IO.Path]::IsPathRooted($folder)) { $folder } else { Join-Path $game $folder }
if (Test-Path -LiteralPath (Join-Path $resolved '_nvngx.dll')) { Ok "OptiScaler.ini NvngxPath = $folder" }
else { Bad "OptiScaler.ini NvngxPath = $folder does not hold _nvngx.dll (use NvngxPath=d4r, or the full path of the d4r folder)" }
}
}
# --- NVIDIA DLLs (never shipped; the exact versions come from dll-pins.txt) -----------------------
$pins = @()
$pinsPath = Join-Path $d4r 'dll-pins.txt'
if (Test-Path -LiteralPath $pinsPath) {
foreach ($line in Get-Content -LiteralPath $pinsPath) {
$line = $line.Trim()
if (!$line -or $line.StartsWith('#')) { continue }
$f = $line -split '\s+'
if ($f.Count -ge 4 -and $f[1].Length -eq 64) { $pins += [pscustomobject]@{ kind = $f[0]; sha = $f[1].ToUpperInvariant(); version = $f[2]; status = $f[3] } }
}
}
function Check-Nvidia([string]$kind, [string]$relative, [string]$label, [string]$source) {
$path = Join-Path $game $relative
$wanted = @($pins | Where-Object { $_.kind -eq $kind } | ForEach-Object { $_.version }) -join ' or '
if (!$wanted) { Note "$label cannot be checked (d4r\dll-pins.txt is missing or damaged)"; return }
if (!(Test-Path -LiteralPath $path -PathType Leaf)) { Bad "${relative}: copy NVIDIA's $label here (version $wanted; $source)"; return }
$hash = Hash $path
$pin = @($pins | Where-Object { $_.kind -eq $kind -and $_.sha -eq $hash }) | Select-Object -First 1
if (!$pin) { Bad "$relative is version $(Version $path), which d4r does not accept. d4r on Windows needs exactly $wanted ($source)"; return }
if ($pin.status -eq 'validated') { Ok "$relative ($label $($pin.version), tested on Windows)" }
else { Ok "$relative ($label $($pin.version))"; Note "$label $($pin.version) is accepted but has had less Windows testing" }
}
Check-Nvidia 'dlss' 'd4r\nvngx_dlss.dll' 'nvngx_dlss.dll' 'many games ship one in their folder; see WINDOWS_README.txt'
Check-Nvidia 'ngx' 'd4r\ngx\_nvngx.dll' '_nvngx.dll' 'it is part of NVIDIA''s display driver package; see WINDOWS_README.txt'
# --- GPU and the build for it --------------------------------------------------------------------
$registryPath = Join-Path $d4r 'gpu-targets.json'
$registry = @()
if (Test-Path -LiteralPath $registryPath) { $registry = @((Get-Content -LiteralPath $registryPath -Raw | ConvertFrom-Json).targets) }
$inventory = $null
if ($InventoryFile) { $inventory = Get-Content -LiteralPath $InventoryFile -Raw | ConvertFrom-Json }
elseif (Test-Path -LiteralPath (Join-Path $d4r 'd4r_gpu_inventory.exe')) {
$work = Join-Path ([IO.Path]::GetTempPath()) ('d4r-check-' + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $work | Out-Null
try {
$info = New-Object Diagnostics.ProcessStartInfo
$info.FileName = Join-Path $d4r 'd4r_gpu_inventory.exe'
$info.Arguments = '--hip-root "' + (Join-Path $d4r 'hip') + '" --output-dir "' + $work + '"'
$info.UseShellExecute = $false; $info.CreateNoWindow = $true
$info.RedirectStandardOutput = $true; $info.RedirectStandardError = $true
$info.EnvironmentVariables['D4R_DIAG_DIR'] = $work
$process = New-Object Diagnostics.Process; $process.StartInfo = $info
[void]$process.Start()
$null = $process.StandardOutput.ReadToEndAsync(); $null = $process.StandardError.ReadToEndAsync()
if (!$process.WaitForExit(30000)) { $process.Kill(); $process.WaitForExit() }
if (Test-Path -LiteralPath (Join-Path $work 'gpu-inventory.json')) { $inventory = Get-Content -LiteralPath (Join-Path $work 'gpu-inventory.json') -Raw | ConvertFrom-Json }
} catch {} finally {
$process.Dispose()
Get-ChildItem -LiteralPath $work -File -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -LiteralPath $_.FullName }
Remove-Item -LiteralPath $work -ErrorAction SilentlyContinue
}
}
$arch = $null
if (!$inventory) { Note 'could not ask the AMD runtime about your GPU (d4r_gpu_inventory.exe is missing, or the AMD graphics driver is not installed or is too old)' }
else {
$supported = @($registry | ForEach-Object { $_.architecture })
$devices = @($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 -and ($supported -contains $_.architecture -or !$supported.Count) })
if (!$devices.Count) {
$seen = @($inventory.devices | ForEach-Object { "$($_.name) ($($_.architecture))" }) -join ', '
Bad ("no supported RDNA3/RDNA4 GPU found by the AMD runtime. It sees: " + $(if ($seen) { $seen } else { 'nothing' }) + '. Update the AMD Adrenalin driver; RDNA2 and older are not supported')
} else {
foreach ($device in $devices) {
$a = $device.architecture
$missing = @()
foreach ($rel in @("d4r\_nvngx_$a.dll", "d4r\pixel_convert_$a.hsaco", "d4r\nvapi\$a\nvapi64.dll", "d4r\native\$a\d4r-kernels.txt")) {
if (!(Test-Path -LiteralPath (Join-Path $game $rel))) { $missing += $rel }
}
$native = @(Get-ChildItem -LiteralPath (Join-Path $d4r "native\$a") -Filter '*.hsaco' -ErrorAction SilentlyContinue).Count
if ($missing.Count) { Bad "GPU $($device.name) ($a): this release has no complete build for it (missing: $($missing -join ', '))" }
else {
Ok "GPU $($device.name) ($a): build present, $native native kernels"
if (!$arch) { $arch = $a }
}
if ($a -ne 'gfx1201') { Note "only the RX 9070 XT (gfx1201) has been tested on real hardware; $a is compile-tested" }
}
}
if (@($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 }).Count -gt 1) { Note 'several GPUs found; d4r uses the one the game renders on' }
}
if ($arch -and $registry.Count) {
$info = @($registry | Where-Object { $_.architecture -eq $arch }) | Select-Object -First 1
$bad = 0
$objects = @(Get-Item -LiteralPath (Join-Path $d4r "pixel_convert_$arch.hsaco") -ErrorAction SilentlyContinue) +
@(Get-ChildItem -LiteralPath (Join-Path $d4r "native\$arch") -Filter '*.hsaco' -ErrorAction SilentlyContinue)
foreach ($object in $objects) {
$stream = [IO.File]::OpenRead($object.FullName)
try { $header = New-Object byte[] 64; [void]$stream.Read($header, 0, 64) } finally { $stream.Dispose() }
if ($header[0] -ne 127 -or $header[1] -ne 69 -or $header[2] -ne 76 -or $header[3] -ne 70 -or $header[48] -ne $info.elfMachine) {
Bad "$($object.Name) is not built for $arch (do not rename or mix files from other GPUs; extract the d4r ZIP again)"; $bad++
}
}
if ($objects.Count -and !$bad) { Ok "all $($objects.Count) GPU code objects are built for $arch" }
}
# --- Linux / Wine leftovers ------------------------------------------------------------------------
$proxyFound = $false
foreach ($name in @('d3d12.dll', 'd3d12core.dll')) {
$proxy = Join-Path $game $name
if (Test-Path -LiteralPath $proxy -PathType Leaf) {
$item = Get-Item -LiteralPath $proxy
if ($item.Length -le 64MB -and [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($proxy)) -match '(?i)vkd3d|wined3d') {
Bad "$name in the game folder is vkd3d-proton/Wine (left over from a Linux install?). Move it out; d4r on Windows uses native D3D12"
$proxyFound = $true
}
}
}
if (!$proxyFound) { Ok 'no vkd3d/Wine d3d12.dll in the game folder' }
# --- d4r.ini ---------------------------------------------------------------------------------------
$iniPath = Join-Path $d4r 'd4r.ini'
$model = 'K'
if (Test-Path -LiteralPath $iniPath) {
$section = ''; $iniProblems = 0
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1 }
$boolean = @('dlss.showwatermark', 'interop.asyncinterop', 'debug.validateoutput')
foreach ($raw in (Read-TextFile $iniPath).TrimStart([char]0xFEFF) -split "`n") {
$line = $raw.Trim()
if (!$line -or $line.StartsWith(';') -or $line.StartsWith('#')) { continue }
if ($line.StartsWith('[')) { $section = $line.Trim('[', ']').Trim(); continue }
$eq = $line.IndexOf('='); if ($eq -lt 0) { continue }
$key = $line.Substring(0, $eq).Trim()
$value = ($line.Substring($eq + 1) -replace '\s[;#].*$', '').Trim()
$name = ("$section.$key").ToLowerInvariant()
$v = $value.ToLowerInvariant()
if (!$known.ContainsKey($name)) { Note "d4r.ini: [$section] $key is not a Windows setting; ignored"; $iniProblems++ }
elseif ($name -eq 'dlss.model') {
if (@('', 'auto', 'k', 'dlss4', '11') -contains $v) { $model = 'K' }
elseif (@('m', 'dlss4.5', '13') -contains $v) { $model = 'M' }
else { Note "d4r.ini: [DLSS] Model '$value' is not available on Windows (use K or M); K will be used"; $iniProblems++ }
}
elseif ($boolean -contains $name) {
if (@('', 'auto', '1', 'true', 'yes', 'on', '0', 'false', 'no', 'off') -notcontains $v) { Note "d4r.ini: [$section] $key must be true or false, not '$value'; the default will be used"; $iniProblems++ }
}
elseif ($name -eq 'debug.log' -and @('', 'auto', 'normal', 'verbose') -notcontains $v) { Note "d4r.ini: [Debug] Log must be normal or verbose, not '$value'; normal will be used"; $iniProblems++ }
}
if (!$iniProblems) { Ok "d4r.ini is valid (model $model)" }
}
# --- what the last game start said -----------------------------------------------------------------
$log = Join-Path $d4r 'd4r_nvngx.log'
if (Test-Path -LiteralPath $log) {
$messages = @(Get-Content -LiteralPath $log | Where-Object { $_ -match '^d4r: ' })
$failed = @($messages | Where-Object { $_ -match 'DLSS disabled' })
if ($failed.Count) { foreach ($m in $failed | Select-Object -First 3) { Bad ('last game start: ' + $m.Substring(5)) } }
elseif ($messages.Count) { Ok ('last game start: ' + $messages[0].Substring(5)) }
else { Note 'd4r\d4r_nvngx.log has no d4r summary line yet (the game may not have chosen DLSS)' }
foreach ($m in $messages | Where-Object { $_ -notmatch 'DLSS disabled|player mode' } | Select-Object -First 5) { Note ('last game start: ' + $m.Substring(5)) }
} else { Note 'no d4r\d4r_nvngx.log yet: start the game once and choose DLSS (or FSR/XeSS) in its graphics settings' }
if (Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($game + '\', [StringComparison]::OrdinalIgnoreCase) -and $_.ProcessName -notmatch '^(powershell|pwsh)$' }) {
Note 'the game is running; its log is rewritten at every start'
}
Say ''
if ($problems -eq 0) { Say 'Everything d4r needs is in place.' } else { Say "$problems thing(s) to fix above." }
# --- bug-report ZIP ------------------------------------------------------------------------------
if ($Report) {
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$destination = if ($ReportDirectory) { $ReportDirectory } else { [Environment]::GetFolderPath('Desktop') }
$staging = Join-Path ([IO.Path]::GetTempPath()) ("d4r-report-$stamp")
New-Item -ItemType Directory -Force -Path $staging | Out-Null
try {
$lines | Set-Content -LiteralPath (Join-Path $staging 'check.txt') -Encoding UTF8
foreach ($file in @('d4r\d4r_nvngx.log', 'd4r\d4r.ini', 'OptiScaler.ini', 'OptiScaler.log')) {
$source = Join-Path $game $file
if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $staging (Split-Path $file -Leaf)) }
}
Get-ChildItem -LiteralPath $game -Filter 'OptiScaler*.log' -File | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $staging -Force }
try { Get-CimInstance Win32_VideoController | Select-Object Name, DriverVersion, DriverDate | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $staging 'driver.json') -Encoding UTF8 } catch {}
if ($inventory) { $inventory | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $staging 'gpu-inventory.json') -Encoding UTF8 }
Get-ChildItem -LiteralPath $game -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName.StartsWith($d4r + '\') -or $_.Name -in @('dxgi.dll', 'OptiScaler.ini') } |
ForEach-Object { '{0} {1,12} {2}' -f (Hash $_.FullName), $_.Length, $_.FullName.Substring($game.Length + 1) } |
Set-Content -LiteralPath (Join-Path $staging 'files.sha256.txt') -Encoding UTF8
$zip = Join-Path $destination "d4r-report-$stamp.zip"
Compress-Archive -Path (Join-Path $staging '*') -DestinationPath $zip -Force
Write-Host ''
Write-Host "Bug report written: $zip" -ForegroundColor Cyan
Write-Host 'Also tell us: GPU, game, K or M, and what the image looked like. The logs can contain local folder names; look before posting publicly.'
} finally {
Get-ChildItem -LiteralPath $staging -File -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -LiteralPath $_.FullName }
Remove-Item -LiteralPath $staging -ErrorAction SilentlyContinue
}
}
if ($problems) { exit 1 }