Add automatic native Windows GPU quick-test packaging

This commit is contained in:
xdfnx-dev committed 2026-10-02 18:19:15 +03:00
1 parent 3d5f42865f
commit 21ed0a8c88
7 files changed
+502

No files matched your search

+8
View File
@@ -30,6 +30,14 @@ Only RX 9070 XT has physical validation here; all other targets are compile-test
The installer checks the real HIP target before changing game files. See The installer checks the real HIP target before changing game files. See
[Windows GPU coverage and issue #10 diagnostics](docs/windows-gpu-support.md). [Windows GPU coverage and issue #10 diagnostics](docs/windows-gpu-support.md).
For a short Windows hardware test, the fork's
[quick-test prereleases](https://github.com/xdfnx-dev/d4r/releases) provide one
ZIP that detects the GPU automatically. Extract it and double-click START-K.cmd
or START-M.cmd, select your game and local NVIDIA DLLs, then send the single
diagnostic ZIP after exiting. RESTORE-GAME.cmd restores replaced files. Read
[the short English instructions](docs/windows-quick-test.txt). Corresponding
sources are a separate release asset; the test requires no SDK installation.
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models. See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
> **Not affiliated with NVIDIA or AMD.** > **Not affiliated with NVIDIA or AMD.**
+55
View File
@@ -0,0 +1,55 @@
D4R WINDOWS QUICK TEST -- READ THIS FIRST
This is an EXPERIMENTAL Windows 11 D3D12 build for RDNA3 / RDNA3.5 / RDNA4.
Only RX 9070 XT has been tested here. Other cards may fail.
The package detects the real GPU automatically. Do not rename any DLLs.
1. EXTRACT THE WHOLE ZIP to a normal writable folder, e.g. Desktop\d4r-test.
Do not run it inside the ZIP. Do not copy files into the game manually.
2. CLOSE YOUR GAME. Have these TWO ORIGINAL NVIDIA files ready:
_nvngx.dll version 32.0.16.1714
nvngx_dlss.dll version 310.9.1
NVIDIA DLLs are not included. Other versions are rejected.
The small _nvngx.dll inside this package is d4r, NOT the original NVIDIA file.
3. DOUBLE-CLICK START-K.cmd. Pick the actual game .exe when asked.
Pick your two NVIDIA DLLs. These choices are remembered for the next run.
Next time, press Enter for the same game, or C to choose another.
If Steam/Epic is needed, keep it open. Choose a D3D12 game without anti-cheat.
If a launcher opens, type C and choose the real game executable next time.
4. IN THE GAME, select DLSS if available, otherwise FSR/XeSS for OptiScaler.
Leave frame generation OFF. First launch can compile for several minutes.
Keep the console open. Play for 30 seconds, check the image, then EXIT THE GAME.
START-M.cmd tests DLSS 4.5 preset M; START-K.cmd tests DLSS 4 preset K.
5. SEND THE SINGLE ZIP shown as "SEND THIS ONE FILE" in the console.
It is also in the results folder. Tell us your GPU, game, K/M and image result:
"correct image", "black screen", "crash" or "visual corruption".
A passing backend log does not automatically prove the image is correct.
Logs can contain local paths. Review them before posting them publicly.
TO UNINSTALL: close the game and double-click RESTORE-GAME.cmd.
Original replaced files are backed up and restored; changed files are preserved.
Restore the previous game BEFORE choosing a different game for the next test.
Always launch tests through START-K/M.cmd, not the usual game shortcut.
IF SOMETHING FAILS:
- Wrong NVIDIA DLL: select the exact versions above on the next run.
- No supported GPU / HIP failure: send the ZIP; do not spoof the GPU architecture.
- vkd3d/Wine detected: use the game's original native Windows D3D12 files.
- Access denied: extract to a writable folder. Do not disable system security.
- Do not start a second test while the first game is running.
No HIP SDK installation is required; the isolated runtime is included.
This uses synchronous interop, output checks and crash capture by default.
It is a correctness diagnostic, NOT an FPS benchmark or an optimized daily-use build.
Colour, depth, motion and output remain in VRAM in the runtime fast path.
Runtime files are in files/. Cache/settings are created in work/.
Licenses are in licenses/. Corresponding sources are a SEPARATE release download;
testers do not need to download or build them. No NVIDIA proprietary DLLs are shipped.
Report: https://github.com/countervolts/d4r/issues/10
Source: https://github.com/xdfnx-dev/d4r
+18
View File
@@ -19,6 +19,24 @@ explicit limits; the validated package keeps conservative arithmetic.
## Milestone and gates ## Milestone and gates
2026-10-02 quick-test packaging: one universal ZIP shares the identical
OptiScaler/ZLUDA/HIP runtime and selects among all eleven previously compiled
host/kernel targets using read-only HIP discovery. START-K.cmd / START-M.cmd
use file pickers, remember validated game/DLL paths, back up replaced files,
run synchronous output/crash diagnostics, and produce one report ZIP.
RESTORE-GAME.cmd works without HIP discovery or NVIDIA DLL selection. No
runtime arithmetic changes or additional hardware-support claims are made.
The short English instructions are in `windows-quick-test.txt`; developer
sources and exact patched GPL OptiScaler/submodule sources are a separate asset.
An isolated Windows PowerShell 5 fixture verifies automatic gfx1201 selection,
spaces/Unicode paths, remembered selection, exact install/restore, and rejection
of wrong NGX/DLSS DLLs, vkd3d and modified package files before game writes.
No real game is launched by these packaging checks. Reproduce packaging with
`package-windows-quick-test.ps1 -PackageRoot <new-dir> -ArchivePath <zip>` and
`tools/windows/package_quick_test_sources.py --output <source-zip>` after commit.
The quick-test build is a correctness diagnostic, not the async/private-texture
performance configuration; K optimization remains open.
2026-10-02 coverage: all eleven listed RDNA3 / RDNA3.5 / RDNA4 targets compile 2026-10-02 coverage: all eleven listed RDNA3 / RDNA3.5 / RDNA4 targets compile
with separate host/device objects and guarded packages. Only RX 9070 XT has with separate host/device objects and guarded packages. Only RX 9070 XT has
physical validation here; no other-target GPU workloads are executed. The physical validation here; no other-target GPU workloads are executed. The
@@ -0,0 +1,89 @@
[CmdletBinding()]
param(
[string]$GamePackagePrefix, [Parameter(Mandatory=$true)][string]$PackageRoot,
[string]$ArchivePath
)
$ErrorActionPreference='Stop'
. (Join-Path $PSScriptRoot 'gpu-target.ps1')
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
if (!$GamePackagePrefix) { $GamePackagePrefix=Join-Path $repo 'dist/windows-gpu-coverage-game' }
$PackageRoot=[IO.Path]::GetFullPath($PackageRoot)
if (Test-Path -LiteralPath $PackageRoot) { throw 'Use a new output directory to avoid mixing releases.' }
$commit=(& git -C $repo rev-parse HEAD).Trim()
if ($ArchivePath -and [bool](& git -C $repo status --porcelain)) { throw 'Commit source changes before creating a public ZIP.' }
New-Item -ItemType Directory -Force $PackageRoot | Out-Null
$files=[Collections.Generic.List[object]]::new()
function Stage([string]$Source,[string]$Relative) {
$destination=Join-Path $PackageRoot $Relative
New-Item -ItemType Directory -Force (Split-Path $destination) | Out-Null
Copy-Item -LiteralPath $Source -Destination $destination
$item=Get-Item -LiteralPath $destination
$entry=@{path=$Relative.Replace('\','/'); sha256=(Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash; version=$item.VersionInfo.FileVersion}
$files.Add($entry)
return $entry
}
$targets=[ordered]@{}; $common=@(); $baseline=$null
foreach ($gpu in Get-D4RGpuTargets) {
$arch=$gpu.architecture; $source="$GamePackagePrefix-$arch"
$metadata=Get-Content -LiteralPath (Join-Path $source 'package.json') -Raw | ConvertFrom-Json
if ($metadata.architecture -ne $arch -or $metadata.d4rWorkingTreeDirty) { throw "Uncommitted or wrong-target input: $source" }
$targetFiles=@()
foreach ($file in $metadata.files) {
$relative=$file.path.Replace('\','/')
if ((Get-FileHash -LiteralPath (Join-Path $source $file.path) -Algorithm SHA256).Hash -ne $file.sha256) { throw "Input file changed: $source/$relative" }
if ($relative.EndsWith('.hsaco')) { Assert-D4RCodeObjectTarget (Join-Path $source $file.path) $arch }
if ($relative -eq 'gpu-target.json' -or $relative -eq 'd4r/_nvngx.dll' -or $relative -eq 'd4r/d4r_gpu_inventory.exe' -or $relative -eq 'd4r/d4r_debug_launcher.exe' -or $relative -eq 'd4r/nvapi/nvapi64.dll' -or
$relative.StartsWith('d4r/native/') -or $relative -like 'd4r/pixel_convert_*.hsaco') {
$entry=Stage (Join-Path $source $file.path) "files/targets/$arch/$relative"
$targetFiles+=@{path=$relative; sha256=$entry.sha256; version=$entry.version}
} elseif ($relative -eq 'OptiScaler.dll' -or $relative.StartsWith('d4r/') -or $relative.StartsWith('licenses/')) {
$publicPath=if ($relative.StartsWith('licenses/')) { $relative } else { "files/common/$relative" }
if (!$baseline) {
$entry=Stage (Join-Path $source $file.path) $publicPath
if (!$relative.StartsWith('licenses/')) { $common+=@{path=$relative; sha256=$entry.sha256; version=$entry.version} }
} elseif ((Get-FileHash -LiteralPath (Join-Path $PackageRoot $publicPath) -Algorithm SHA256).Hash -ne $file.sha256) {
throw "Shared runtime differs in $arch : $relative"
}
}
}
if (@($targetFiles | Where-Object { $_.path.EndsWith('.hsaco') }).Count -ne 17) { throw "Expected 17 code objects for $arch" }
$targets[$arch]=@{binarySourceCommit=$metadata.d4rCommit; hardwareValidation=$metadata.hardwareValidation; dlssSha256=$metadata.dlssSha256; files=$targetFiles}
if (!$baseline) { $baseline=$metadata }
if ($metadata.dlssSha256 -ne $baseline.dlssSha256 -or $metadata.zludaBuild.sourceCommit -ne $baseline.zludaBuild.sourceCommit) { throw 'Input packages do not use the same validated dependencies.' }
}
foreach ($name in @('windows-game.ps1','gpu-target.ps1','gpu-preflight.ps1')) {
$entry=Stage (Join-Path $PSScriptRoot $name) "files/common/$name"
$common+=@{path=$name; sha256=$entry.sha256; version=$entry.version}
}
$entry=Stage (Join-Path $repo 'tools/windows/gpu-targets.json') 'files/common/gpu-targets.json'
$common+=@{path='gpu-targets.json'; sha256=$entry.sha256; version=$entry.version}
[void](Stage (Join-Path $PSScriptRoot 'quick-test.ps1') 'files/quick-test.ps1')
[void](Stage (Join-Path $repo 'docs/windows-quick-test.txt') 'READ-ME-FIRST.txt')
foreach ($launch in @(@{name='START-K.cmd'; arguments='-Preset 11'},@{name='START-M.cmd'; arguments='-Preset 13'},@{name='RESTORE-GAME.cmd'; arguments='-Action restore'})) {
$path=Join-Path $PackageRoot $launch.name
@('@echo off','cd /d "%~dp0"',('"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -STA -ExecutionPolicy Bypass -File "%~dp0files\quick-test.ps1" '+$launch.arguments),'pause') |
Set-Content -LiteralPath $path -Encoding ASCII
$files.Add(@{path=$launch.name; sha256=(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash; version=$null})
}
# Publicly supplied original identity, never the binary itself.
$metadata=[ordered]@{schema=1; packageCommit=$commit; binarySourceCommit=$baseline.d4rCommit;
ngxCoreSha256='66767018C36B3BAB46398DADE3ADF173DAA3730FDA75965689EA848C9BC4E79B';
dependencies=@{optiScalerSourceCommit=$baseline.optiScaler.sourceCommit; zludaSourceCommit=$baseline.zludaBuild.sourceCommit;
llvmSource=$baseline.zludaBuild.llvmSource; hipRuntime=$baseline.hipRuntime};
commonFiles=$common; targets=$targets; files=$files}
$metadata | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $PackageRoot 'files/package.json') -Encoding UTF8
if ($ArchivePath) {
$ArchivePath=[IO.Path]::GetFullPath($ArchivePath)
if ($ArchivePath.StartsWith($PackageRoot+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Place the ZIP outside the output directory.' }
New-Item -ItemType Directory -Force (Split-Path $ArchivePath) | Out-Null
Add-Type -AssemblyName System.IO.Compression,System.IO.Compression.FileSystem
$stream=[IO.File]::Open($ArchivePath,[IO.FileMode]::Create,[IO.FileAccess]::ReadWrite,[IO.FileShare]::None)
$archive=$null
try {
$archive=[IO.Compression.ZipArchive]::new($stream,[IO.Compression.ZipArchiveMode]::Create)
foreach ($relative in @($files.path)+@('files/package.json')) {
[void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive,(Join-Path $PackageRoot $relative),$relative,[IO.Compression.CompressionLevel]::Optimal)
}
} finally { if ($archive) { $archive.Dispose() }; $stream.Dispose() }
}
Write-Host "Quick-test package: $PackageRoot (11 targets, automatic GPU selection, no NVIDIA DLLs)"
+166
View File
@@ -0,0 +1,166 @@
[CmdletBinding()]
param(
[ValidateSet('run','install','restore')][string]$Action='run',
[ValidateSet(11,13)][int]$Preset=11,
[string]$GameExe, [string]$NgxCore, [string]$DlssDll
)
$ErrorActionPreference='Stop'
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$work=Join-Path $root 'work'
$settingsPath=Join-Path $work 'settings.json'
New-Item -ItemType Directory -Force $work | Out-Null
$saved=$null
if (Test-Path -LiteralPath $settingsPath) {
try { $saved=Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json }
catch { Write-Host 'Saved choices could not be read. Select the files again.' }
}
function Select-LocalFile([string]$Title, [string]$FileName, [string]$Previous) {
if ($Previous -and (Test-Path -LiteralPath $Previous -PathType Leaf)) { return (Get-Item -LiteralPath $Previous).FullName }
Add-Type -AssemblyName System.Windows.Forms
$dialog=New-Object Windows.Forms.OpenFileDialog
try {
$dialog.Title=$Title; $dialog.FileName=$FileName
$dialog.Filter=if ($FileName -eq '*.exe') { 'Game executable (*.exe)|*.exe' } else { "$FileName|$FileName" }
if ($dialog.ShowDialog() -ne [Windows.Forms.DialogResult]::OK) { throw 'File selection cancelled. No test was launched.' }
return $dialog.FileName
} finally { $dialog.Dispose() }
}
function Copy-Payload([string]$Source, [string]$Destination, [string]$Relative) {
$path=[IO.Path]::GetFullPath((Join-Path $Destination $Relative))
if (!$path.StartsWith($Destination+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Invalid package path.' }
New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null
Copy-Item -LiteralPath (Join-Path $Source $Relative) -Destination $path -Force
}
if ($Action -eq 'restore') {
$GameExe=Select-LocalFile 'Select the SAME game executable used for the test' '*.exe' $(if ($GameExe) { $GameExe } elseif ($saved) { $saved.game })
& (Join-Path $PSScriptRoot 'common/windows-game.ps1') -Action restore -GameExe $GameExe
Write-Host 'RESTORED. Launch the game normally again.' -ForegroundColor Green
return
}
$output=Join-Path $root ('results/test-'+(Get-Date -Format 'yyyyMMdd-HHmmss-fff'))
New-Item -ItemType Directory -Force $output | Out-Null
$status=[ordered]@{action=$Action; preset=$Preset; status='starting'; hardwareValidationCompleted=$false}
$selectedPackage=$null
try {
Write-Host 'd4r Windows quick test - keep this window open until you EXIT the game.'
Write-Host 'Checking package and detecting the GPU...'
$manifest=Get-Content -LiteralPath (Join-Path $PSScriptRoot 'package.json') -Raw | ConvertFrom-Json
foreach ($file in $manifest.files) {
$path=[IO.Path]::GetFullPath((Join-Path $root $file.path))
if (!$path.StartsWith($root+'\',[StringComparison]::OrdinalIgnoreCase) -or
(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $file.sha256) { throw "Package is incomplete or changed. Extract the ZIP again. File: $($file.path)" }
}
$status.packageCommit=$manifest.packageCommit
$inventoryDir=Join-Path $output 'gpu'
New-Item -ItemType Directory -Force $inventoryDir | Out-Null
$info=New-Object Diagnostics.ProcessStartInfo
$info.FileName=Join-Path $PSScriptRoot 'targets/gfx1201/d4r/d4r_gpu_inventory.exe'
$info.Arguments='--hip-root "'+(Join-Path $PSScriptRoot 'common/d4r/hip')+'" --output-dir "'+$inventoryDir+'"'
$info.UseShellExecute=$false; $info.CreateNoWindow=$true
$info.EnvironmentVariables['D4R_DIAG_DIR']=$inventoryDir
$info.RedirectStandardOutput=$true; $info.RedirectStandardError=$true
$process=New-Object Diagnostics.Process; $process.StartInfo=$info
try {
if (!$process.Start()) { throw 'GPU detection could not start.' }
$stdout=$process.StandardOutput.ReadToEndAsync(); $stderr=$process.StandardError.ReadToEndAsync()
$timedOut=!$process.WaitForExit(30000)
if ($timedOut) { $process.Kill(); $process.WaitForExit() }
$stdout.GetAwaiter().GetResult() | Set-Content (Join-Path $inventoryDir 'stdout.log') -Encoding UTF8
$stderr.GetAwaiter().GetResult() | Set-Content (Join-Path $inventoryDir 'stderr.log') -Encoding UTF8
$status.inventoryExit='0x{0:X8}' -f [BitConverter]::ToUInt32([BitConverter]::GetBytes([int]$process.ExitCode),0)
if ($timedOut -or $process.ExitCode) { throw 'HIP could not detect your GPU. The diagnostic ZIP contains the details.' }
} finally { $process.Dispose() }
$inventory=Get-Content -LiteralPath (Join-Path $inventoryDir 'gpu-inventory.json') -Raw | ConvertFrom-Json
$devices=@($inventory.devices | Where-Object { $_.buildEligible -and $_.wavefrontSize -eq 32 -and $manifest.targets.PSObject.Properties.Name -contains $_.architecture } | Sort-Object vramBytes -Descending)
if (!$devices.Count) { throw 'No supported RDNA3/RDNA4 HIP device was detected. See the GPU inventory in the diagnostic ZIP.' }
$device=$devices[0]
if (@($devices.architecture | Select-Object -Unique).Count -gt 1) {
for ($i=0; $i -lt $devices.Count; $i++) { Write-Host "$($i+1): $($devices[$i].name) ($($devices[$i].architecture))" }
$choice=Read-Host 'Select the GPU used by your game (press Enter for 1)'
if ($choice) {
$index=0
if (![int]::TryParse($choice,[ref]$index) -or $index -lt 1 -or $index -gt $devices.Count) { throw 'Invalid GPU selection.' }
$device=$devices[$index-1]
}
}
$arch=$device.architecture; $target=$manifest.targets.$arch
$status.gpu=$device; $status.binarySourceCommit=$target.binarySourceCommit
Write-Host "Detected: $($device.name) / $arch" -ForegroundColor Cyan
Write-Host 'Experimental build. This run is a correctness test, not an FPS benchmark.'
$previousGame=if ($saved) { $saved.game } else { $null }
if (!$GameExe -and $previousGame -and (Test-Path -LiteralPath $previousGame)) {
Write-Host "Remembered game: $previousGame"
$reuse=Read-Host 'Press Enter to use this game, or type C to choose another (restore the old game first)'
if ($reuse -match '^(?i)c$') { $previousGame=$null }
elseif ($reuse) { throw 'Invalid choice. Press Enter or type C on the next run.' }
}
$GameExe=Select-LocalFile 'Select the actual D3D12 game .exe (not Steam or a launcher)' '*.exe' $(if ($GameExe) { $GameExe } else { $previousGame })
$NgxCore=Select-LocalFile 'Select your ORIGINAL NVIDIA _nvngx.dll (32.0.16.1714)' '_nvngx.dll' $(if ($NgxCore) { $NgxCore } elseif ($saved) { $saved.ngxCore })
$DlssDll=Select-LocalFile 'Select your NVIDIA nvngx_dlss.dll (310.9.1)' 'nvngx_dlss.dll' $(if ($DlssDll) { $DlssDll } elseif ($saved) { $saved.dlssDll })
$status.game=$GameExe
$status.localDlls=@($NgxCore,$DlssDll | ForEach-Object { $item=Get-Item -LiteralPath $_; @{path=$item.FullName; version=$item.VersionInfo.FileVersion; sha256=(Get-FileHash -LiteralPath $_ -Algorithm SHA256).Hash} })
if ((Get-FileHash -LiteralPath $NgxCore -Algorithm SHA256).Hash -ne $manifest.ngxCoreSha256) {
throw 'Wrong _nvngx.dll. This test needs the original NVIDIA 32.0.16.1714 DLL, not the d4r shim. Select the correct file on the next run.'
}
if ((Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash -ne $target.dlssSha256) {
throw 'Wrong nvngx_dlss.dll. This test needs exactly NVIDIA DLSS 310.9.1. Select the correct file on the next run.'
}
if ($GameExe.StartsWith($root+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Select the installed GAME executable, outside this extracted package.' }
# Save only validated choices. Wrong DLLs are never remembered.
@{game=$GameExe; ngxCore=$NgxCore; dlssDll=$DlssDll; architecture=$arch} | ConvertTo-Json | Set-Content -LiteralPath $settingsPath -Encoding UTF8
$selectedPackage=Join-Path $work "package-$arch"
New-Item -ItemType Directory -Force $selectedPackage | Out-Null
$selectedFiles=@()
foreach ($file in $manifest.commonFiles) {
Copy-Payload (Join-Path $PSScriptRoot 'common') $selectedPackage $file.path
$selectedFiles+=$file
}
foreach ($file in $target.files) {
Copy-Payload (Join-Path $PSScriptRoot "targets/$arch") $selectedPackage $file.path
$selectedFiles+=$file
}
@{architecture=$arch; d4rCommit=$target.binarySourceCommit; packageCommit=$manifest.packageCommit;
dlssSha256=$target.dlssSha256; hardwareValidation=$target.hardwareValidation; files=$selectedFiles} |
ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $selectedPackage 'package.json') -Encoding UTF8
Write-Host 'Original game files will be backed up. RESTORE-GAME.cmd reverses the installation.'
Write-Host 'In the game: select DLSS if available; otherwise select FSR/XeSS to let OptiScaler intercept it.'
Write-Host 'The first launch compiles shaders and can take several minutes. Then play for 30 seconds and EXIT the game normally.'
& (Join-Path $selectedPackage 'windows-game.ps1') -Action $Action -GameExe $GameExe -NgxCore $NgxCore -DlssDll $DlssDll `
-Preset $Preset -ValidateOutput -CaptureExceptions -OutputDirectory $output
if ($Action -eq 'install') { $status.status='installed-only' }
else {
$summary=Get-Content -LiteralPath (Join-Path $output 'summary.json') -Raw | ConvertFrom-Json
$status.gameSummary=$summary
$required=Get-Content -LiteralPath (Join-Path $selectedPackage 'd4r/native/d4r-kernels.txt') | Where-Object {
if ($Preset -eq 11) { $_ -match '^dltss_pwin_' } else { $_ -match '^rrlite_' }
} | ForEach-Object { $_.Split(' ')[0] }
$hits=@($summary.kernels | Where-Object { $_.backend -eq 'native' -and $_.launches -gt 0 } | ForEach-Object { $_.kernel })
$status.missingNativeKernels=@($required | Where-Object { $hits -notcontains $_ })
$status.translatedNativeKernels=@($summary.kernels | Where-Object { $_.backend -eq 'translated' -and $required -contains $_.kernel } | ForEach-Object { $_.kernel })
$passed=$summary.exitCode -eq '0x0' -and $summary.completedFrames -gt 0 -and $summary.nativeLaunches -gt 0 -and
$summary.outputGpuChecks -gt 0 -and $summary.failures -eq 0 -and $summary.nonfiniteOutputs -eq 0 -and
$summary.previousFrameOutputs -eq 0 -and $summary.cpuImageCopyFrames -eq 0 -and
@($required).Count -eq $(if ($Preset -eq 11) { 11 } else { 5 }) -and
$status.missingNativeKernels.Count -eq 0 -and $status.translatedNativeKernels.Count -eq 0
$status.hardwareValidationCompleted=[bool]$passed
$status.status=if ($passed) { 'backend-checks-passed-image-still-needs-user-review' } else { 'backend-checks-failed-or-no-dlss-frames' }
Write-Host $(if ($passed) { 'Backend checks PASSED. Please also report whether the image looked correct.' } else { 'Test did not pass. Send the diagnostic ZIP; do not guess from the FPS.' }) -ForegroundColor $(if ($passed) { 'Green' } else { 'Yellow' })
}
} catch {
$status.status='failed'; $status.error=$_.Exception.Message
$_ | Out-String | Set-Content -LiteralPath (Join-Path $output 'error.txt') -Encoding UTF8
Write-Host "TEST STOPPED: $($status.error)" -ForegroundColor Red
} finally {
if ($selectedPackage) {
$preflights=Join-Path $selectedPackage 'results'
$latest=Get-ChildItem -LiteralPath $preflights -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
if ($latest) { Copy-Item -LiteralPath $latest.FullName -Destination (Join-Path $output 'preflight') -Recurse -Force }
}
try { Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion,DriverDate | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $output 'driver.json') -Encoding UTF8 } catch {}
$status | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $output 'quick-test.json') -Encoding UTF8
Compress-Archive -LiteralPath $output -DestinationPath ($output+'.zip') -Force
Write-Host "SEND THIS ONE FILE: $output.zip" -ForegroundColor Cyan
Write-Host 'Also tell us: GPU model, game name, K or M, and whether the image was correct.'
Write-Host 'Close the game before running RESTORE-GAME.cmd.'
}
if ($status.status -eq 'failed' -or $status.status -eq 'backend-checks-failed-or-no-dlss-frames') { exit 1 }
@@ -0,0 +1,67 @@
# Isolated installer checks; never starts a real game or launches GPU kernels.
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$PackageRoot,
[Parameter(Mandatory=$true)][string]$NgxCore,
[Parameter(Mandatory=$true)][string]$DlssDll,
[string]$OutputDirectory
)
$ErrorActionPreference='Stop'
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
$PackageRoot=(Get-Item -LiteralPath $PackageRoot).FullName
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo ('test-results/windows-quick-test/'+(Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
function Invoke-Child([string]$Name,[string[]]$Arguments,[int]$ExpectedExit=0) {
$log=Join-Path $OutputDirectory "$Name.log"
& "$env:SystemRoot/System32/WindowsPowerShell/v1.0/powershell.exe" -NoProfile -STA -ExecutionPolicy Bypass `
-File (Join-Path $PackageRoot 'files/quick-test.ps1') @Arguments *> $log
if ($LASTEXITCODE -ne $ExpectedExit) { throw "Unexpected exit $LASTEXITCODE for $Name; see $log" }
}
function Fixture([string]$Name) {
$dir=Join-Path $OutputDirectory $Name
New-Item -ItemType Directory -Force $dir | Out-Null
$exe=Join-Path $dir 'Fixture.exe'
Set-Content -LiteralPath $exe 'installer fixture; do not execute' -Encoding ASCII
return $exe
}
$results=@()
$exe=Fixture ('space GPU '+[char]0x03a9+' test')
$game=Split-Path $exe
$before=@{}
foreach ($name in @('dxgi.dll','OptiScaler.ini')) {
Set-Content -LiteralPath (Join-Path $game $name) "original $name" -Encoding ASCII
$before[$name]=(Get-FileHash -LiteralPath (Join-Path $game $name)).Hash
}
Invoke-Child 'install' @('-Action','install','-GameExe',$exe,'-NgxCore',$NgxCore,'-DlssDll',$DlssDll)
$saved=Get-Content -LiteralPath (Join-Path $PackageRoot 'work/settings.json') -Raw | ConvertFrom-Json
if ($saved.game -ne $exe -or $saved.architecture -ne 'gfx1201') { throw 'GPU detection or remembered selection failed on this RX9070XT fixture.' }
# Restore uses remembered game selection and needs neither HIP nor NVIDIA DLLs.
Invoke-Child 'restore' @('-Action','restore')
foreach ($name in $before.Keys) { if ((Get-FileHash -LiteralPath (Join-Path $game $name)).Hash -ne $before[$name]) { throw 'Original game bytes not restored.' } }
if (Test-Path -LiteralPath (Join-Path $game 'd4r')) { throw 'Runtime directory remains after restore.' }
$results+=@{case='auto-gfx1201-install-remember-restore'; passed=$true; gpuKernelsExecuted=$false}
foreach ($case in @('wrong-core','wrong-dlss','vkd3d')) {
$exe=Fixture $case; $game=Split-Path $exe
$core=if ($case -eq 'wrong-core') { Join-Path $PackageRoot 'files/targets/gfx1201/d4r/_nvngx.dll' } else { $NgxCore }
$dlss=if ($case -eq 'wrong-dlss') { $NgxCore } else { $DlssDll }
if ($case -eq 'vkd3d') { Set-Content -LiteralPath (Join-Path $game 'd3d12.dll') 'vkd3d-proton fixture; do not load' -Encoding ASCII }
Invoke-Child $case @('-Action','install','-GameExe',$exe,'-NgxCore',$core,'-DlssDll',$dlss) 1
if ((Test-Path -LiteralPath (Join-Path $game '.d4r-backup')) -or (Test-Path -LiteralPath (Join-Path $game 'dxgi.dll'))) { throw "$case changed game files before rejection." }
$message=Get-Content -LiteralPath (Join-Path $OutputDirectory "$case.log") -Raw
$match=[regex]::Match($message,'SEND THIS ONE FILE: (.+\.zip)')
if (!$match.Success -or !(Test-Path -LiteralPath $match.Groups[1].Value.Trim())) { throw "$case did not produce its single report ZIP." }
$results+=@{case=$case; passed=$true; rejectedBeforeGameChanges=$true; gpuKernelsExecuted=$false}
}
$readme=Join-Path $PackageRoot 'READ-ME-FIRST.txt'
$original=[IO.File]::ReadAllBytes($readme)
try {
Add-Content -LiteralPath $readme 'tampered fixture'
$exe=Fixture 'tampered-package'
Invoke-Child 'tampered-package' @('-Action','install','-GameExe',$exe,'-NgxCore',$NgxCore,'-DlssDll',$DlssDll) 1
$game=Split-Path $exe
if ((Test-Path -LiteralPath (Join-Path $game '.d4r-backup')) -or (Test-Path -LiteralPath (Join-Path $game 'dxgi.dll'))) { throw 'Tampered package changed game files.' }
$results+=@{case='tampered-package'; passed=$true; rejectedBeforeGameChanges=$true; gpuKernelsExecuted=$false}
} finally { [IO.File]::WriteAllBytes($readme,$original) }
$results | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8
Write-Host "PASS QUICK_TEST_FIXTURES positive=1 negative=4 real_game_untouched=1 Report: $OutputDirectory"
@@ -0,0 +1,99 @@
"""Separate corresponding-source asset for the lightweight Windows test ZIP.
Reads committed trees only, including the exact patched GPL OptiScaler tree and
its pinned submodules. Never copies a working directory or NVIDIA binaries.
"""
import argparse
import hashlib
import io
import json
from pathlib import Path
import subprocess
import zipfile
def git(root, *args):
return subprocess.check_output(["git", "-C", str(root), *args])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[2])
args = parser.parse_args()
repo = args.repo.resolve()
info = json.loads((repo / "dist/optiscaler-windows-d4r/build-info.json").read_text(encoding="utf-8-sig"))
opti = repo / "external/OptiScaler"
if info["sourceDirty"] or git(opti, "status", "--porcelain").strip():
raise RuntimeError("OptiScaler must match its clean binary source tree")
if git(opti, "rev-parse", "HEAD").decode().strip() != info["sourceCommit"]:
raise RuntimeError("OptiScaler source does not match the binary")
if git(repo, "status", "--porcelain").strip():
raise RuntimeError("Commit the packaging sources before publishing")
args.output.parent.mkdir(parents=True, exist_ok=True)
if args.output.exists():
raise RuntimeError("Use a new output archive")
provenance = {"d4rCommit": git(repo, "rev-parse", "HEAD").decode().strip(),
"optiScalerBinary": info["dllSha256"], "sourceTrees": [], "excludedNvidiaBinaries": []}
with zipfile.ZipFile(args.output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as output:
def tree(root, revision, prefix):
provenance["sourceTrees"].append({"path": prefix, "commit": revision})
raw = git(root, "archive", "--format=zip", revision)
with zipfile.ZipFile(io.BytesIO(raw)) as source:
for item in source.infolist():
if item.is_dir():
continue
name = prefix + "/" + item.filename
lowered = name.lower()
nvidia_binary = ("/nvapi/" in lowered or "nvngx" in Path(lowered).name) and Path(lowered).suffix in {
".dll", ".exe", ".lib", ".bin", ".cubin", ".fatbin", ".pdb"}
if nvidia_binary:
provenance["excludedNvidiaBinaries"].append(name)
continue
output.writestr(name, source.read(item))
for entry in git(root, "ls-tree", "-rz", revision).split(b"\0"):
if not entry:
continue
header, path = entry.split(b"\t", 1)
mode, _, commit = header.split()
if mode != b"160000":
continue
child = path.decode("utf-8")
child_root = root / child
child_commit = commit.decode()
if git(child_root, "rev-parse", "HEAD").decode().strip() != child_commit:
raise RuntimeError(f"Submodule does not match committed source: {child}")
if git(child_root, "status", "--porcelain").strip():
raise RuntimeError(f"Dirty source submodule: {child}")
tree(child_root, child_commit, prefix + "/" + child)
tree(repo, provenance["d4rCommit"], "d4r")
tree(opti, info["sourceCommit"], "OptiScaler")
output.writestr("source-provenance.json", json.dumps(provenance, indent=2) + "\n")
output.writestr("READ-ME-SOURCES.txt", """CORRESPONDING SOURCES -- NOT NEEDED TO TEST THE GAME
d4r/ contains the committed Windows port, build scripts, dependency patches
and licenses. OptiScaler/ contains the exact modified GPL-3.0 source tree and
its pinned source submodules corresponding to the distributed OptiScaler.dll.
See source-provenance.json for exact commits and the binary SHA256.
Build instructions: d4r/docs/windows-rdna4-port.md and
d4r/patches/optiscaler/README.md. The normal build script retrieves the pinned
upstream checkout, submodules and applies the exported source patches.
NVIDIA import-library binaries are intentionally excluded from this archive;
the pinned public NVIDIA/nvapi SDK used by the builder supplies them locally.
No proprietary NVIDIA runtime DLL or NVIDIA-derived private kernel is included.
The separately bundled ZLUDA/LLVM changes are in d4r/patches/zluda and
d4r/patches/zluda-llvm, with upstream source commits and build instructions.
""")
with args.output.open("rb") as stream:
sha256 = hashlib.file_digest(stream, "sha256").hexdigest()
print(json.dumps({"archive": str(args.output), "bytes": args.output.stat().st_size,
"sha256": sha256,
"sourceTrees": len(provenance["sourceTrees"])}))
if __name__ == "__main__":
main()