1945 lines
94 KiB
PowerShell
1945 lines
94 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Verifies a DLSS5-Feeder installation in a game folder and reports what is right and
|
|
what is wrong.
|
|
|
|
.DESCRIPTION
|
|
DLSS5-Feeder is a ReShade add-on that manufactures a synthetic NVIDIA DLSS contract for
|
|
games that have no DLSS of their own, so that a separate "neural consumer" add-on (Deep
|
|
Fried Chicken, or the RenoDX DLSS 5 add-on) can perform neural rendering on top of it.
|
|
|
|
Getting that working requires a fiddly, mutually-consistent set of files, and most of the
|
|
ways to get it wrong fail *silently* -- the game launches, ReShade loads, frames are
|
|
delivered, and neural rendering simply does nothing. This script inspects a game folder
|
|
and checks, read-only:
|
|
|
|
1. The game's architecture (parsed straight out of the PE header) and render API.
|
|
2. The ReShade install -- local DLL or machine-wide Vulkan layer -- and its version.
|
|
3. The feeder's own files: the add-on, the 32-bit host helper, the shader and its
|
|
framework headers.
|
|
4. The neural consumer: exactly one must be present, and never a conflicting second.
|
|
5. The NVIDIA NGX runtimes (nvngx_dlssnr.dll, nvngx_dlss.dll).
|
|
6. The d3dcompiler_47.dll trap: a Windows 8.1-SDK-era copy in the game folder silently
|
|
kills the neural pass.
|
|
7. The GPU (DLSS needs an RTX card).
|
|
8. A short digest of the feeder's own logs, if it has run.
|
|
9. The motion-vector provider, which is a per-effect ReShade key that people routinely
|
|
set in the wrong file.
|
|
|
|
The script NEVER writes, moves or deletes anything. It exits with code 1 if any check
|
|
failed, 0 otherwise, so it can be used from another script.
|
|
|
|
.PARAMETER GamePath
|
|
The game folder to check, or the path to the game's .exe (the folder is then taken from
|
|
it). Defaults to the current directory.
|
|
|
|
.PARAMETER Exe
|
|
Overrides the automatic choice of the game executable. Useful when a folder holds several
|
|
(for example DOOM's DOOMx64.exe and DOOMx64vk.exe). May be a bare file name or a path.
|
|
|
|
.PARAMETER Quiet
|
|
Print only failures and the closing summary.
|
|
|
|
.EXAMPLE
|
|
.\Verify-DLSS5Feeder.ps1 -GamePath "G:\Games\Dusk"
|
|
|
|
Check a 64-bit Direct3D 11 install.
|
|
|
|
.EXAMPLE
|
|
.\Verify-DLSS5Feeder.ps1 "E:\SteamLibrary\steamapps\common\DOOM" -Exe DOOMx64vk.exe
|
|
|
|
Check a Vulkan install, pointing the script at the Vulkan executable rather than letting
|
|
it guess.
|
|
|
|
.EXAMPLE
|
|
.\Verify-DLSS5Feeder.ps1
|
|
|
|
Check the current directory.
|
|
|
|
.NOTES
|
|
Windows PowerShell 5.1 compatible. Read-only. See docs/DEPLOY-DEV.md in the DLSS5-Feeder repo
|
|
for the full install runbook that these checks are derived from.
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Position = 0)]
|
|
[string] $GamePath,
|
|
|
|
[string] $Exe,
|
|
|
|
[switch] $Quiet,
|
|
|
|
# Skip the "Press Enter to exit" pause at the end. The pause exists for the
|
|
# right-click > "Run with PowerShell" case, where the window would otherwise close
|
|
# before the output can be read; -Quiet implies it.
|
|
[switch] $NoPause
|
|
)
|
|
|
|
function Exit-Verifier {
|
|
param([int] $Code)
|
|
if (-not $Quiet -and -not $NoPause) {
|
|
Write-Host ''
|
|
try { [void](Read-Host ' Press Enter to exit') } catch { }
|
|
}
|
|
exit $Code
|
|
}
|
|
|
|
Set-StrictMode -Version 2.0
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Output plumbing
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
$script:CountOk = 0
|
|
$script:CountWarn = 0
|
|
$script:CountFail = 0
|
|
# DLSS5_MV_PROVIDER as the add-on itself resolved it in the last run, out of dlss5-feed.log.
|
|
# Better evidence than any ini, because it is read after ReShade has done the overriding.
|
|
$script:FeedLogProvider = $null
|
|
$script:Actions = New-Object System.Collections.ArrayList
|
|
|
|
# The first NVIDIA driver that ships DLSS 5 neural rendering at all. Below it NGX answers
|
|
# FeatureNotSupported (0xBAD00001) and nothing here works, so it is a hard failure -- see
|
|
# issue #47, where a machine on 596.36 passed this script with 0 failures.
|
|
$kMinDriver = '616.56'
|
|
$kMinDriverDigits = 61656
|
|
|
|
# Does this host support colour at all? A transcript, a redirected stream or an exotic host
|
|
# may not. Probe once and fall back to plain text rather than throwing per line.
|
|
$script:UseColour = $true
|
|
try {
|
|
if ($null -eq $Host -or $null -eq $Host.UI -or $null -eq $Host.UI.RawUI) { $script:UseColour = $false }
|
|
else { $null = $Host.UI.RawUI.ForegroundColor }
|
|
}
|
|
catch { $script:UseColour = $false }
|
|
|
|
function Write-Chunk
|
|
{
|
|
param([string] $Text, [string] $Colour, [switch] $NoNewline)
|
|
|
|
try {
|
|
if ($script:UseColour -and $Colour) { Write-Host $Text -ForegroundColor $Colour -NoNewline:$NoNewline }
|
|
else { Write-Host $Text -NoNewline:$NoNewline }
|
|
}
|
|
catch {
|
|
# A host that accepted the probe but rejects this colour: never let painting fail a run.
|
|
$script:UseColour = $false
|
|
Write-Host $Text -NoNewline:$NoNewline
|
|
}
|
|
}
|
|
|
|
function Write-Banner
|
|
{
|
|
if ($Quiet) { return }
|
|
|
|
$box = 'DarkGray'
|
|
$w = 68
|
|
Write-Host ''
|
|
Write-Chunk (' ' + [char]0x2554 + ([string][char]0x2550) * $w + [char]0x2557) $box
|
|
|
|
# The logo's mark: three rounded squares stacked back-to-front, offset down and left.
|
|
$blk = [string][char]0x2588 # full block
|
|
$upr = [string][char]0x2580 # upper half block
|
|
$lwr = [string][char]0x2584 # lower half block
|
|
|
|
$rows = @(
|
|
@{ Mark = ' ' + ($lwr * 5); Shade = 'DarkGreen'; Text = ''; TextColour = '' },
|
|
@{ Mark = ' ' + ($lwr * 2) + ($blk * 5); Shade = 'Green'; Text = ' DLSS5-Feeder'; TextColour = 'White' },
|
|
@{ Mark = ' ' + ($blk * 6) + ($upr * 2); Shade = 'Green'; Text = ' Neural rendering bridge for ReShade'; TextColour = 'DarkGray' },
|
|
@{ Mark = ' ' + ($upr * 6); Shade = 'DarkGreen'; Text = ''; TextColour = '' }
|
|
)
|
|
|
|
foreach ($r in $rows) {
|
|
Write-Chunk (' ' + [char]0x2551) $box -NoNewline
|
|
Write-Chunk $r.Mark $r.Shade -NoNewline
|
|
if ($r.Text) { Write-Chunk $r.Text $r.TextColour -NoNewline }
|
|
$used = $r.Mark.Length + $r.Text.Length
|
|
if ($used -lt $w) { Write-Chunk ((' ') * ($w - $used)) $null -NoNewline }
|
|
Write-Chunk ([string][char]0x2551) $box
|
|
}
|
|
|
|
Write-Chunk (' ' + [char]0x255A + ([string][char]0x2550) * $w + [char]0x255D) $box
|
|
Write-Host ''
|
|
}
|
|
|
|
function Write-Section
|
|
{
|
|
param([string] $Title)
|
|
if ($Quiet) { return }
|
|
Write-Host ''
|
|
Write-Chunk (' ' + [char]0x2500 + [char]0x2500 + ' ') 'Green' -NoNewline
|
|
Write-Chunk $Title 'White' -NoNewline
|
|
$pad = 62 - $Title.Length
|
|
if ($pad -lt 1) { $pad = 1 }
|
|
Write-Chunk (' ' + ([string][char]0x2500) * $pad) 'Green'
|
|
}
|
|
|
|
# The one place a check result is recorded. $Status is Ok / Warn / Fail / Na.
|
|
function Report
|
|
{
|
|
param(
|
|
[ValidateSet('Ok', 'Warn', 'Fail', 'Na')]
|
|
[string] $Status,
|
|
[string] $Text,
|
|
[string] $Detail,
|
|
[string] $Action
|
|
)
|
|
|
|
switch ($Status) {
|
|
'Ok' { $glyph = '[ OK ]'; $colour = 'Green'; $script:CountOk++ }
|
|
'Warn' { $glyph = '[WARN]'; $colour = 'Yellow'; $script:CountWarn++ }
|
|
'Fail' { $glyph = '[FAIL]'; $colour = 'Red'; $script:CountFail++ }
|
|
'Na' { $glyph = '[ -- ]'; $colour = 'DarkGray' }
|
|
}
|
|
|
|
if ($Status -eq 'Fail' -and $Action) { $null = $script:Actions.Add($Action) }
|
|
|
|
if ($Quiet -and $Status -ne 'Fail') { return }
|
|
|
|
Write-Chunk (' ' + $glyph + ' ') $colour -NoNewline
|
|
if ($Status -eq 'Na') { Write-Chunk $Text 'DarkGray' } else { Write-Host $Text }
|
|
if ($Detail) {
|
|
foreach ($line in ($Detail -split "`n")) {
|
|
if ($line.Trim()) { Write-Chunk (' ' + $line.Trim()) 'DarkGray' }
|
|
}
|
|
}
|
|
if ($Status -eq 'Fail' -and $Action) {
|
|
$first = $true
|
|
foreach ($line in ($Action -split "`n")) {
|
|
if (-not $line.Trim()) { continue }
|
|
if ($first) { Write-Chunk (' ' + [char]0x2192 + ' ' + $line.Trim()) 'DarkYellow'; $first = $false }
|
|
else { Write-Chunk (' ' + $line.Trim()) 'DarkYellow' }
|
|
}
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Small, defensive helpers. Every one of these returns $null rather than throwing.
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
function Join-Safe
|
|
{
|
|
param([string] $Parent, [string] $Child)
|
|
try { return [IO.Path]::Combine($Parent, $Child) } catch { return $null }
|
|
}
|
|
|
|
function Test-FileHere
|
|
{
|
|
param([string] $Path)
|
|
if (-not $Path) { return $false }
|
|
try { return (Test-Path -LiteralPath $Path -PathType Leaf) } catch { return $false }
|
|
}
|
|
|
|
function Test-DirHere
|
|
{
|
|
param([string] $Path)
|
|
if (-not $Path) { return $false }
|
|
try { return (Test-Path -LiteralPath $Path -PathType Container) } catch { return $false }
|
|
}
|
|
|
|
# Case-insensitive lookup of a file in a folder; returns the real path or $null. Used so the
|
|
# script does not care whether a game shipped D3D9.dll or d3d9.dll, ReShade.ini or reshade.ini.
|
|
function Find-FileIn
|
|
{
|
|
param([string] $Dir, [string] $Name)
|
|
if (-not (Test-DirHere $Dir)) { return $null }
|
|
$p = Join-Safe $Dir $Name
|
|
if (Test-FileHere $p) { return $p }
|
|
try {
|
|
$hit = Get-ChildItem -LiteralPath $Dir -File -Filter $Name -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($hit) { return $hit.FullName }
|
|
}
|
|
catch { }
|
|
return $null
|
|
}
|
|
|
|
# Every match, not just the first. The RenoDX add-on ships under versioned names as well
|
|
# ('renodx-dlss5-4.7.addon64'), and ReShade loads EVERY *.addon64 in the folder -- so "is one
|
|
# present" and "which ones are present" are different questions, and the second is the one that
|
|
# matters when two copies would both hook NGX. The C++ side has matched the prefix since #1
|
|
# (FindRenodxAddon, src/dlss5-feed.cpp:320); this script did not, and reported a perfectly good
|
|
# versioned install as "no neural consumer found".
|
|
function Find-FilesIn
|
|
{
|
|
param([string] $Dir, [string] $Name)
|
|
if (-not (Test-DirHere $Dir)) { return @() }
|
|
try {
|
|
$hits = @(Get-ChildItem -LiteralPath $Dir -File -Filter $Name -ErrorAction SilentlyContinue)
|
|
return $hits
|
|
}
|
|
catch { return @() }
|
|
}
|
|
|
|
# Case-insensitive recursive search under a folder. ReShade's EffectSearchPaths normally ends
|
|
# in "**", so a header sitting in Shaders\CrosireMaster\ is found by the compiler and must be
|
|
# treated as present here too.
|
|
function Find-FileUnder
|
|
{
|
|
param([string] $Dir, [string] $Name)
|
|
if (-not (Test-DirHere $Dir)) { return $null }
|
|
try {
|
|
$hit = Get-ChildItem -LiteralPath $Dir -File -Filter $Name -Recurse -ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
if ($hit) { return $hit.FullName }
|
|
}
|
|
catch { }
|
|
return $null
|
|
}
|
|
|
|
function Get-FileVersionSafe
|
|
{
|
|
param([string] $Path)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
try {
|
|
$vi = (Get-Item -LiteralPath $Path -ErrorAction Stop).VersionInfo
|
|
if ($vi -and $vi.FileVersion) {
|
|
# Some vendors (NVIDIA's NGX DLLs among them) write "310,8,0,0".
|
|
return ($vi.FileVersion.Trim() -replace '\s*,\s*', '.')
|
|
}
|
|
}
|
|
catch { }
|
|
return $null
|
|
}
|
|
|
|
function Get-ProductNameSafe
|
|
{
|
|
param([string] $Path)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
try {
|
|
$vi = (Get-Item -LiteralPath $Path -ErrorAction Stop).VersionInfo
|
|
$bits = @()
|
|
if ($vi.ProductName) { $bits += $vi.ProductName }
|
|
if ($vi.FileDescription) { $bits += $vi.FileDescription }
|
|
if ($vi.InternalName) { $bits += $vi.InternalName }
|
|
return ($bits -join ' | ')
|
|
}
|
|
catch { }
|
|
return $null
|
|
}
|
|
|
|
# Parse the PE header ourselves: no shelling out, and it works on a file a running game holds
|
|
# open (FileShare ReadWrite).
|
|
function Get-PeInfo
|
|
{
|
|
param([string] $Path)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
|
|
$fs = $null
|
|
$br = $null
|
|
try {
|
|
$fs = New-Object IO.FileStream($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
|
if ($fs.Length -lt 0x40) { return $null }
|
|
$br = New-Object IO.BinaryReader($fs)
|
|
|
|
if ($br.ReadUInt16() -ne 0x5A4D) { return $null } # 'MZ'
|
|
$fs.Position = 0x3C
|
|
$peOff = $br.ReadInt32()
|
|
if ($peOff -le 0 -or ($peOff + 24) -ge $fs.Length) { return $null }
|
|
|
|
$fs.Position = $peOff
|
|
if ($br.ReadUInt32() -ne 0x00004550) { return $null } # 'PE\0\0'
|
|
|
|
$machine = $br.ReadUInt16()
|
|
$null = $br.ReadUInt16() # NumberOfSections
|
|
$timeStamp = $br.ReadUInt32()
|
|
|
|
switch ($machine) {
|
|
0x014C { $bits = 32; $arch = 'x86' }
|
|
0x8664 { $bits = 64; $arch = 'x64' }
|
|
0xAA64 { $bits = 64; $arch = 'ARM64' }
|
|
0x01C4 { $bits = 32; $arch = 'ARM' }
|
|
default { $bits = 0; $arch = ('unknown machine 0x{0:X4}' -f $machine) }
|
|
}
|
|
|
|
$built = $null
|
|
try { $built = ([datetime]'1970-01-01Z').ToUniversalTime().AddSeconds($timeStamp) } catch { }
|
|
|
|
return New-Object psobject -Property @{
|
|
Machine = $machine
|
|
Bits = $bits
|
|
Arch = $arch
|
|
Built = $built
|
|
}
|
|
}
|
|
catch { return $null }
|
|
finally {
|
|
if ($br) { try { $br.Close() } catch { } }
|
|
if ($fs) { try { $fs.Dispose() } catch { } }
|
|
}
|
|
}
|
|
|
|
# Does this executable look like something that renders? A renderer names a graphics runtime
|
|
# somewhere in its binary; a command-line tool that ships beside the game does not. Used only
|
|
# to break a tie when the game exe is being guessed (#60) -- never to fail anything, so a
|
|
# string scan is enough and no import-table walk is needed. Capped, like the installer's.
|
|
function Test-ExeLooksGraphical
|
|
{
|
|
param([string] $Path)
|
|
try {
|
|
$fi = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if ($fi.Length -eq 0 -or $fi.Length -gt 268435456) { return $false }
|
|
$bytes = [IO.File]::ReadAllBytes($Path)
|
|
$ascii = [Text.Encoding]::ASCII.GetString($bytes)
|
|
$wide = [Text.Encoding]::Unicode.GetString($bytes)
|
|
foreach ($n in @('vulkan-1.dll', 'dxgi.dll', 'd3d12.dll', 'd3d11.dll', 'd3d10_1.dll', 'd3d10.dll', 'd3d9.dll', 'd3d8.dll', 'opengl32.dll')) {
|
|
$re = '(?i)(?<![\w.])' + [regex]::Escape($n)
|
|
if ([regex]::IsMatch($ascii, $re) -or [regex]::IsMatch($wide, $re)) { return $true }
|
|
}
|
|
}
|
|
catch { }
|
|
return $false
|
|
}
|
|
|
|
# RVA -> file offset, given a PE's section table. Split out from the reader below so the
|
|
# lookup does not depend on PowerShell's nested-function scoping.
|
|
function Convert-RvaToOffset
|
|
{
|
|
param($Sections, [uint32] $Rva, [long] $Length)
|
|
foreach ($s in $Sections) {
|
|
if ($Rva -ge $s.V -and $Rva -lt ($s.V + $s.Span)) {
|
|
$o = [long]$s.Raw + ([long]$Rva - [long]$s.V)
|
|
if ($o -ge 0 -and $o -lt $Length) { return [long]$o }
|
|
return [long](-1)
|
|
}
|
|
}
|
|
return [long](-1)
|
|
}
|
|
|
|
# The names in a PE's export directory. $null when the file is not a readable PE; an empty
|
|
# array when it is one and exports nothing.
|
|
function Get-PeExportNames
|
|
{
|
|
param([string] $Path, [int] $Max = 8192)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
|
|
$fs = $null
|
|
$br = $null
|
|
try {
|
|
$fs = New-Object IO.FileStream($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
|
if ($fs.Length -lt 0x40) { return $null }
|
|
$br = New-Object IO.BinaryReader($fs)
|
|
|
|
if ($br.ReadUInt16() -ne 0x5A4D) { return $null } # 'MZ'
|
|
$fs.Position = 0x3C
|
|
$peOff = $br.ReadInt32()
|
|
if ($peOff -le 0 -or ($peOff + 24) -ge $fs.Length) { return $null }
|
|
|
|
$fs.Position = $peOff
|
|
if ($br.ReadUInt32() -ne 0x00004550) { return $null } # 'PE\0\0'
|
|
$null = $br.ReadUInt16() # Machine
|
|
$nSections = $br.ReadUInt16()
|
|
$null = $br.ReadUInt32() # TimeDateStamp
|
|
$null = $br.ReadUInt32() # PointerToSymbolTable
|
|
$null = $br.ReadUInt32() # NumberOfSymbols
|
|
$optSize = $br.ReadUInt16()
|
|
$null = $br.ReadUInt16() # Characteristics
|
|
|
|
$optOff = [long]$peOff + 24
|
|
if ($optSize -lt 96 -or ($optOff + $optSize) -gt $fs.Length) { return $null }
|
|
$fs.Position = $optOff
|
|
$magic = $br.ReadUInt16()
|
|
# The data directories follow the optional header's fixed part: 96 bytes for PE32,
|
|
# 112 for PE32+ (four fields widened to 64-bit).
|
|
if ($magic -eq 0x20B) { $dirOff = $optOff + 112 }
|
|
elseif ($magic -eq 0x10B) { $dirOff = $optOff + 96 }
|
|
else { return $null }
|
|
if (($dirOff + 8) -gt $fs.Length) { return $null }
|
|
$fs.Position = $dirOff
|
|
$expRva = $br.ReadUInt32() # DataDirectory[0] = exports
|
|
$null = $br.ReadUInt32() # its size
|
|
if ($expRva -eq 0) { return ,([string[]] @()) }
|
|
|
|
$secOff = $optOff + $optSize
|
|
$sections = @()
|
|
for ($i = 0; $i -lt $nSections; $i++) {
|
|
$p = $secOff + ([long]$i * 40)
|
|
if (($p + 40) -gt $fs.Length) { break }
|
|
$fs.Position = $p + 8 # past the 8-byte name
|
|
$vsize = $br.ReadUInt32()
|
|
$vaddr = $br.ReadUInt32()
|
|
$rsize = $br.ReadUInt32()
|
|
$raw = $br.ReadUInt32()
|
|
$span = if ($vsize -gt 0) { $vsize } else { $rsize }
|
|
$sections += New-Object psobject -Property @{ V = $vaddr; Span = $span; Raw = $raw }
|
|
}
|
|
if ($sections.Count -eq 0) { return $null }
|
|
|
|
$eo = Convert-RvaToOffset $sections $expRva $fs.Length
|
|
if ($eo -lt 0 -or ($eo + 40) -gt $fs.Length) { return $null }
|
|
|
|
# IMAGE_EXPORT_DIRECTORY: NumberOfFunctions +20, NumberOfNames +24,
|
|
# AddressOfFunctions +28, AddressOfNames +32.
|
|
$fs.Position = $eo + 20
|
|
$null = $br.ReadUInt32()
|
|
$nNames = $br.ReadUInt32()
|
|
$null = $br.ReadUInt32()
|
|
$namesRva = $br.ReadUInt32()
|
|
if ($nNames -eq 0 -or $namesRva -eq 0) { return ,([string[]] @()) }
|
|
if ($nNames -gt $Max) { $nNames = $Max }
|
|
|
|
$no = Convert-RvaToOffset $sections $namesRva $fs.Length
|
|
if ($no -lt 0) { return $null }
|
|
|
|
$names = New-Object 'System.Collections.Generic.List[string]'
|
|
for ($i = 0; $i -lt $nNames; $i++) {
|
|
$p = $no + ([long]$i * 4)
|
|
if (($p + 4) -gt $fs.Length) { break }
|
|
$fs.Position = $p
|
|
$so = Convert-RvaToOffset $sections ($br.ReadUInt32()) $fs.Length
|
|
if ($so -lt 0) { continue }
|
|
$fs.Position = $so
|
|
$sb = New-Object Text.StringBuilder
|
|
for ($k = 0; $k -lt 256; $k++) {
|
|
$b = $fs.ReadByte()
|
|
if ($b -le 0) { break }
|
|
$null = $sb.Append([char]$b)
|
|
}
|
|
if ($sb.Length -gt 0) { $null = $names.Add($sb.ToString()) }
|
|
}
|
|
return ,([string[]] $names.ToArray())
|
|
}
|
|
catch { return $null }
|
|
finally {
|
|
if ($br) { try { $br.Close() } catch { } }
|
|
if ($fs) { try { $fs.Dispose() } catch { } }
|
|
}
|
|
}
|
|
|
|
# Does this ReShade build support add-ons?
|
|
#
|
|
# ReShade ships two builds. They carry the same version number and the same ProductName, so
|
|
# nothing this script checked could tell them apart -- and an install with the wrong one looks
|
|
# perfectly healthy while the add-on is never loaded at all (issue #53). It bites hardest on
|
|
# Vulkan, where the layer is machine-wide: anyone who has ever run ReShade's plain setup for
|
|
# any Vulkan game already has one, and it is reused for every game after that.
|
|
#
|
|
# The exports settle it, and not by inference: ReShade's own add-on API finds the ReShade
|
|
# module in a process by testing GetProcAddress for exactly "ReShadeRegisterAddon" and
|
|
# "ReShadeUnregisterAddon" (reshade.hpp). A build that does not export those two cannot load
|
|
# an add-on, because that is the mechanism by which add-ons find it.
|
|
#
|
|
# $null means the file could not be read -- never report that as a failure.
|
|
function Test-ReShadeHasAddons
|
|
{
|
|
param([string] $Path)
|
|
$names = Get-PeExportNames $Path
|
|
if ($null -eq $names) { return $null }
|
|
return [bool](($names -contains 'ReShadeRegisterAddon') -and ($names -contains 'ReShadeUnregisterAddon'))
|
|
}
|
|
|
|
# Scan a binary for an ASCII marker string. Deliberately capped: the NGX DLLs are 160 MB and
|
|
# there is never a reason to slurp one.
|
|
function Get-BinaryMarker
|
|
{
|
|
param([string] $Path, [string] $Pattern, [int] $MaxBytes = 33554432)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
try {
|
|
$fi = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if ($fi.Length -gt $MaxBytes -or $fi.Length -eq 0) { return $null }
|
|
|
|
$fs = New-Object IO.FileStream($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
|
try {
|
|
$buf = New-Object byte[] $fi.Length
|
|
$read = 0
|
|
while ($read -lt $buf.Length) {
|
|
$n = $fs.Read($buf, $read, $buf.Length - $read)
|
|
if ($n -le 0) { break }
|
|
$read += $n
|
|
}
|
|
}
|
|
finally { $fs.Dispose() }
|
|
|
|
$text = [Text.Encoding]::ASCII.GetString($buf, 0, $read)
|
|
$m = [regex]::Match($text, $Pattern)
|
|
if ($m.Success) {
|
|
if ($m.Groups.Count -gt 1 -and $m.Groups[1].Success) { return $m.Groups[1].Value }
|
|
return $m.Value
|
|
}
|
|
}
|
|
catch { }
|
|
return $null
|
|
}
|
|
|
|
function Read-TextSafe
|
|
{
|
|
param([string] $Path, [int] $MaxBytes = 4194304)
|
|
if (-not (Test-FileHere $Path)) { return $null }
|
|
try {
|
|
$fi = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if ($fi.Length -gt $MaxBytes) {
|
|
# Only the tail of a huge log is interesting.
|
|
$fs = New-Object IO.FileStream($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
|
try {
|
|
$fs.Position = $fi.Length - $MaxBytes
|
|
$buf = New-Object byte[] $MaxBytes
|
|
$n = $fs.Read($buf, 0, $MaxBytes)
|
|
return (Remove-Bom ([Text.Encoding]::UTF8.GetString($buf, 0, $n)))
|
|
}
|
|
finally { $fs.Dispose() }
|
|
}
|
|
$fs = New-Object IO.FileStream($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
|
|
try {
|
|
$buf = New-Object byte[] $fi.Length
|
|
$read = 0
|
|
while ($read -lt $buf.Length) {
|
|
$n = $fs.Read($buf, $read, $buf.Length - $read)
|
|
if ($n -le 0) { break }
|
|
$read += $n
|
|
}
|
|
return (Remove-Bom ([Text.Encoding]::UTF8.GetString($buf, 0, $read)))
|
|
}
|
|
finally { $fs.Dispose() }
|
|
}
|
|
catch { return $null }
|
|
}
|
|
|
|
# Files written by ReShade's installer start with a UTF-8 BOM. U+FEFF is not matched by \s in
|
|
# .NET regex, so a "^\s*Apps=" pattern silently fails on it -- strip it once, here.
|
|
function Remove-Bom
|
|
{
|
|
param([string] $Text)
|
|
if ($Text -and $Text.Length -gt 0 -and [int]$Text[0] -eq 0xFEFF) { return $Text.Substring(1) }
|
|
return $Text
|
|
}
|
|
|
|
function Read-LinesSafe
|
|
{
|
|
param([string] $Path)
|
|
$t = Read-TextSafe $Path
|
|
if ($null -eq $t) { return $null }
|
|
return ($t -split "`r?`n")
|
|
}
|
|
|
|
# Value of one key in one section of an ini. Pass '' for $Section to read the ROOT
|
|
# (section-less) block at the top of the file, which is where ReShade keeps a preset's
|
|
# Techniques= and its preset-wide PreprocessorDefinitions=. Last occurrence wins, as
|
|
# ReShade's own parser does.
|
|
function Get-IniValue
|
|
{
|
|
param([string] $Path, [string] $Section, [string] $Key)
|
|
$lines = Read-LinesSafe $Path
|
|
if ($null -eq $lines) { return $null }
|
|
$cur = ''
|
|
$value = $null
|
|
$rx = '(?i)^' + [regex]::Escape($Key) + '\s*=\s*(.*)$'
|
|
foreach ($ln in $lines) {
|
|
$t = $ln.Trim()
|
|
if ($t -match '^\[(.+)\]$') { $cur = $Matches[1]; continue }
|
|
if (-not ($cur -ieq $Section)) { continue }
|
|
if ($t -match $rx) { $value = $Matches[1] }
|
|
}
|
|
return $value
|
|
}
|
|
|
|
# One NAME=VALUE out of a ReShade PreprocessorDefinitions list (comma separated).
|
|
function Get-PreprocessorDefinition
|
|
{
|
|
param([string] $Defs, [string] $Name)
|
|
if (-not $Defs) { return $null }
|
|
$m = [regex]::Match($Defs, '(?i)(?:^|[,;\s])' + [regex]::Escape($Name) + '\s*=\s*([^,;\s]+)')
|
|
if ($m.Success) { return $m.Groups[1].Value }
|
|
return $null
|
|
}
|
|
|
|
function Format-Size
|
|
{
|
|
param([long] $Bytes)
|
|
if ($Bytes -ge 1073741824) { return ('{0:N1} GB' -f ($Bytes / 1073741824)) }
|
|
if ($Bytes -ge 1048576) { return ('{0:N1} MB' -f ($Bytes / 1048576)) }
|
|
if ($Bytes -ge 1024) { return ('{0:N0} KB' -f ($Bytes / 1024)) }
|
|
return ('{0} B' -f $Bytes)
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 0. Resolve the target folder and executable
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Banner
|
|
|
|
if (-not $GamePath -or -not $GamePath.Trim()) {
|
|
$GamePath = (Get-Location).Path
|
|
}
|
|
|
|
$gameDir = $null
|
|
$exePath = $null
|
|
|
|
try {
|
|
$resolved = (Resolve-Path -LiteralPath $GamePath -ErrorAction Stop).ProviderPath
|
|
}
|
|
catch {
|
|
$resolved = $null
|
|
}
|
|
|
|
if (-not $resolved) {
|
|
Report -Status 'Fail' -Text ('Path not found: ' + $GamePath) `
|
|
-Action 'Pass a game folder, or the game''s .exe, that actually exists.'
|
|
Write-Host ''
|
|
Exit-Verifier 1
|
|
}
|
|
|
|
if (Test-DirHere $resolved) {
|
|
$gameDir = $resolved
|
|
}
|
|
elseif (Test-FileHere $resolved) {
|
|
$gameDir = [IO.Path]::GetDirectoryName($resolved)
|
|
if ([IO.Path]::GetExtension($resolved) -match '(?i)^\.exe$') { $exePath = $resolved }
|
|
}
|
|
|
|
if (-not $gameDir) {
|
|
Report -Status 'Fail' -Text ('Cannot make a game folder out of: ' + $GamePath) `
|
|
-Action 'Point -GamePath at the folder holding the game''s real .exe.'
|
|
Write-Host ''
|
|
Exit-Verifier 1
|
|
}
|
|
|
|
if (-not $Quiet) {
|
|
Write-Chunk (' Folder ') 'DarkGray' -NoNewline
|
|
Write-Host $gameDir
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 1. Game architecture and render API
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'Game executable and render API'
|
|
|
|
# -Exe wins. Otherwise pick the largest .exe that does not look like a launcher/helper.
|
|
$exeGuessed = $false
|
|
if ($Exe) {
|
|
$cand = $Exe
|
|
if (-not [IO.Path]::IsPathRooted($cand)) { $cand = Join-Safe $gameDir $Exe }
|
|
if (Test-FileHere $cand) { $exePath = (Resolve-Path -LiteralPath $cand).ProviderPath }
|
|
else {
|
|
Report -Status 'Fail' -Text ('-Exe "' + $Exe + '" not found in the game folder.') `
|
|
-Action 'Check the spelling, or drop -Exe and let the script pick.'
|
|
}
|
|
}
|
|
|
|
if (-not $exePath) {
|
|
$skip = '(?i)(launcher|unins|setup|crash|redist|vcredist|dxsetup|dxwebsetup|dgvoodoocpl|touchup|prereq|activation|helper|updater|report)'
|
|
# #60: "the largest exe that is not a launcher" picked studiomdl.exe -- the Source engine's
|
|
# model compiler -- out of a Garry's Mod bin\win64 folder, and every check after that
|
|
# described an install relative to a tool that never renders anything. Engine and SDK
|
|
# tooling ships beside the game and is often bigger than it, so size alone cannot decide.
|
|
$skipTool = '(?i)^(studiomdl|vbsp|vvis|vrad|hlmv|hlfaceposer|glview|height2ssbump|vtex|vtf2tga|tgadiff|motionmapper|qc_eyes|scenemanager|captioncompiler|shadercompile|bugreporter\w*|phonemeextractor|vice|vpk|dmxconvert|dmxedit|smd\w*)\.exe$'
|
|
$exes = $null
|
|
try {
|
|
# @() so a single match is still an array: StrictMode makes .Count on a bare
|
|
# FileInfo a hard error, and a folder with exactly one exe is the common case.
|
|
$exes = @(Get-ChildItem -LiteralPath $gameDir -File -Filter '*.exe' -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -notmatch $skip -and $_.Name -notmatch $skipTool } |
|
|
Sort-Object Length -Descending)
|
|
}
|
|
catch { }
|
|
|
|
# Prefer an exe that actually imports a graphics runtime: a renderer imports d3d9/d3d11/
|
|
# d3d12/dxgi/opengl32/vulkan-1, and a command-line compiler does not. The installer already
|
|
# scans imports for the same reason (Install-DLSS5Feeder.ps1, Get-PeImports).
|
|
$exeAmbiguous = $false
|
|
if ($exes) {
|
|
$gfx = @($exes | Where-Object { Test-ExeLooksGraphical $_.FullName })
|
|
if ($gfx.Count -ge 1) {
|
|
$exePath = $gfx[0].FullName
|
|
$exeAmbiguous = $gfx.Count -gt 1
|
|
}
|
|
else {
|
|
$exePath = $exes[0].FullName
|
|
$exeAmbiguous = $exes.Count -gt 1
|
|
}
|
|
$exeGuessed = $true
|
|
}
|
|
|
|
# Say so when the pick was a coin toss, instead of reporting the rest of the run as fact.
|
|
if ($exeGuessed -and $exeAmbiguous) {
|
|
Report -Status 'Warn' -Text ('The game executable was guessed: ' + [IO.Path]::GetFileName($exePath)) `
|
|
-Detail 'Several executables in this folder could be the game, and everything below is measured against this one.' `
|
|
-Action 'If that is the wrong one, re-run with -Exe "<the game exe>".'
|
|
}
|
|
}
|
|
|
|
$gameBits = 0
|
|
$peGame = $null
|
|
|
|
if ($exePath) {
|
|
$peGame = Get-PeInfo $exePath
|
|
$exeName = [IO.Path]::GetFileName($exePath)
|
|
$exeSize = ''
|
|
try { $exeSize = ', ' + (Format-Size (Get-Item -LiteralPath $exePath).Length) } catch { }
|
|
|
|
if ($null -eq $peGame) {
|
|
Report -Status 'Warn' -Text ('Executable: ' + $exeName + ' -- PE header unreadable') `
|
|
-Detail 'Cannot determine 32-bit vs 64-bit; assuming 64-bit for the checks below.'
|
|
$gameBits = 64
|
|
}
|
|
elseif ($peGame.Bits -eq 0) {
|
|
Report -Status 'Warn' -Text ('Executable: ' + $exeName + ' -- ' + $peGame.Arch) `
|
|
-Detail 'Not an x86/x64 image; DLSS5-Feeder does not support this architecture.'
|
|
$gameBits = 64
|
|
}
|
|
else {
|
|
$gameBits = $peGame.Bits
|
|
$note = ''
|
|
if ($exeGuessed) { $note = "`nPicked automatically as the largest non-launcher .exe -- use -Exe to override." }
|
|
Report -Status 'Ok' -Text ('Executable: ' + $exeName + ' (' + $peGame.Arch + ', ' + $gameBits + '-bit' + $exeSize + ')') `
|
|
-Detail $note.TrimStart()
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'No game executable found in this folder.' `
|
|
-Detail 'Checks that depend on the game''s architecture will fall back to the local ReShade DLL, or to 64-bit.'
|
|
}
|
|
|
|
# Which local DLL, if any, is ReShade? A game's own d3d9.dll (dgVoodoo2's, say) is not.
|
|
function Test-IsReShade
|
|
{
|
|
param([string] $Path)
|
|
if (-not (Test-FileHere $Path)) { return $false }
|
|
$n = Get-ProductNameSafe $Path
|
|
if ($n -and $n -match '(?i)reshade') { return $true }
|
|
# ReShade's own builds always carry the product name; if version info is missing entirely,
|
|
# fall back to a marker scan rather than guessing from the file name.
|
|
$m = Get-BinaryMarker -Path $Path -Pattern 'ReShade [0-9]+\.[0-9]+' -MaxBytes 16777216
|
|
return [bool]$m
|
|
}
|
|
|
|
$reshadeLocal = $null
|
|
$reshadeLocalName = $null
|
|
foreach ($n in @('dxgi.dll', 'opengl32.dll', 'd3d11.dll', 'd3d9.dll', 'ddraw.dll', 'dinput8.dll')) {
|
|
$p = Find-FileIn $gameDir $n
|
|
if ($p -and (Test-IsReShade $p)) { $reshadeLocal = $p; $reshadeLocalName = [IO.Path]::GetFileName($p); break }
|
|
}
|
|
|
|
$dgVoodooConf = Find-FileIn $gameDir 'dgVoodoo.conf'
|
|
$d3d9Local = Find-FileIn $gameDir 'd3d9.dll'
|
|
|
|
$api = 'unknown'
|
|
$apiDetail = ''
|
|
$isVulkan = $false
|
|
|
|
# #60: "a d3d9.dll plus a dgVoodoo.conf" is not proof of dgVoodoo2. The DLL can be ReShade's
|
|
# own Direct3D 9 hook, and the conf can be a leftover from an install that was later disabled
|
|
# (#56 is exactly that state). Ask the file who it is before claiming the whole route.
|
|
$dgVoodooWrapper = $false
|
|
if ($d3d9Local) {
|
|
$d9name = Get-ProductNameSafe $d3d9Local
|
|
if ($d9name -and $d9name -match '(?i)dgvoodoo') { $dgVoodooWrapper = $true }
|
|
elseif (-not $d9name) {
|
|
$dgVoodooWrapper = [bool](Get-BinaryMarker -Path $d3d9Local -Pattern 'dgVoodoo' -MaxBytes 16777216)
|
|
}
|
|
}
|
|
|
|
if ($d3d9Local -and $dgVoodooConf -and $dgVoodooWrapper) {
|
|
$api = 'Direct3D 9 via dgVoodoo2 (translated to D3D11)'
|
|
$apiDetail = 'The local d3d9.dll identifies itself as dgVoodoo2 and dgVoodoo.conf is present. ReShade hooks the D3D11 device dgVoodoo2 creates, so ReShade itself is the local dxgi.dll.'
|
|
}
|
|
elseif ($d3d9Local -and $dgVoodooConf -and -not $dgVoodooWrapper) {
|
|
$api = 'Direct3D 9 (dgVoodoo.conf present, but the local d3d9.dll is not dgVoodoo2)'
|
|
$apiDetail = 'dgVoodoo.conf is here but the d3d9.dll beside it does not identify itself as dgVoodoo2 -- most likely a leftover conf from an install that was replaced or disabled.'
|
|
Report -Status 'Fail' -Text 'The dgVoodoo2 wrapper is not actually in place.' `
|
|
-Detail $apiDetail `
|
|
-Action 'Re-run Install-DLSS5Feeder.ps1 for this game, or copy dgVoodoo2''s D3D9.dll (D3D8.dll for a Direct3D 8 game) next to the exe.'
|
|
}
|
|
elseif ($reshadeLocalName -eq 'opengl32.dll') {
|
|
$api = 'OpenGL'
|
|
$apiDetail = 'Inferred from a local ReShade opengl32.dll.'
|
|
}
|
|
elseif ($reshadeLocalName -eq 'dxgi.dll') {
|
|
$api = 'Direct3D 10/11/12'
|
|
$apiDetail = 'Inferred from a local ReShade dxgi.dll.'
|
|
}
|
|
elseif ($reshadeLocalName -eq 'd3d9.dll') {
|
|
# #60: this used to report a bland "Direct3D (hooked via d3d9.dll)" and carry on, so a
|
|
# Direct3D 9 game with no wrapper -- which this project cannot touch at all -- looked like
|
|
# a normal install right up to the shader's compile error. Say it here instead.
|
|
$api = 'Direct3D 9 (ReShade is on its D3D9 backend)'
|
|
$apiDetail = 'The local ReShade DLL is d3d9.dll, so ReShade is running its Direct3D 9 backend.'
|
|
Report -Status 'Fail' -Text 'Direct3D 9 is not supported directly.' `
|
|
-Detail 'This project attaches to Direct3D 10/11/12, OpenGL and Vulkan. On ReShade''s D3D9 backend there is no D3D11/D3D12 device to share textures with, and DLSS5_Feed.fx refuses to compile (it reports this in plain language). A 64-bit D3D9 game is best served by ShortFuse''s renodx-dlss standalone, which needs no feeder.' `
|
|
-Action 'For a 32-bit D3D9 game, install dgVoodoo2 so the game runs on D3D11 and point ReShade at dxgi.dll -- Install-DLSS5Feeder.ps1 does this. Otherwise use renodx-dlss on its own.'
|
|
}
|
|
elseif ($reshadeLocalName) {
|
|
$api = 'Direct3D (hooked via ' + $reshadeLocalName + ')'
|
|
$apiDetail = 'Inferred from the local ReShade DLL name.'
|
|
}
|
|
else {
|
|
$api = 'Vulkan (machine-wide ReShade implicit layer)'
|
|
$apiDetail = 'Guessed: there is no local ReShade DLL next to the exe, which is what the Vulkan install looks like. If the game is not a Vulkan game, ReShade is simply not installed here.'
|
|
$isVulkan = $true
|
|
}
|
|
|
|
Report -Status 'Na' -Text ('Render API: ' + $api) -Detail $apiDetail
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 2. ReShade
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'ReShade'
|
|
|
|
function Test-ReShadeVersion
|
|
{
|
|
param([string] $Version)
|
|
# Returns $true when >= 6.8 (RESHADE_API_VERSION 20, which this add-on needs).
|
|
if (-not $Version) { return $null }
|
|
$m = [regex]::Match($Version, '^\s*(\d+)\.(\d+)')
|
|
if (-not $m.Success) { return $null }
|
|
$maj = [int]$m.Groups[1].Value
|
|
$min = [int]$m.Groups[2].Value
|
|
if ($maj -gt 6) { return $true }
|
|
if ($maj -eq 6 -and $min -ge 8) { return $true }
|
|
return $false
|
|
}
|
|
|
|
function Report-ReShadeDll
|
|
{
|
|
param([string] $Path, [string] $Label)
|
|
|
|
$ver = Get-FileVersionSafe $Path
|
|
if (-not $ver) {
|
|
Report -Status 'Warn' -Text ($Label + ': version info unreadable') `
|
|
-Detail $Path
|
|
return
|
|
}
|
|
# Add-on support first: a new-enough ReShade WITHOUT it is the more confusing failure,
|
|
# because every other check passes and nothing in any log says the add-on was refused --
|
|
# it is simply never looked for (issue #53).
|
|
$addons = Test-ReShadeHasAddons $Path
|
|
if ($addons -eq $false) {
|
|
Report -Status 'Fail' -Text ($Label + ': ' + $ver + ' -- built WITHOUT add-on support') `
|
|
-Detail ('This is ReShade''s plain build. It does not export ReShadeRegisterAddon, which is how an add-on finds ReShade, so dlss5-feed is never loaded and nothing anywhere says so. The version number and ProductName are identical to the add-on build, which is why this is easy to miss. On Vulkan the layer is machine-wide, so one plain install from any earlier game is reused for every game after it.') `
|
|
-Action ('Re-run the ReShade installer over ' + $Path + ' and tick "Enable loading of add-ons" (the ReShade_Setup_*_Addon.exe download).')
|
|
return
|
|
}
|
|
|
|
$ok = Test-ReShadeVersion $ver
|
|
if ($ok -eq $true) {
|
|
$d = $Path
|
|
if ($null -eq $addons) { $d = $Path + "`nAdd-on support could not be read from this file; assuming it is present." }
|
|
Report -Status 'Ok' -Text ($Label + ': ' + $ver + $(if ($addons -eq $true) { ' (add-on build)' } else { '' })) -Detail $d
|
|
}
|
|
elseif ($ok -eq $false) {
|
|
Report -Status 'Fail' -Text ($Label + ': ' + $ver + ' -- too old') `
|
|
-Detail 'The add-on needs ReShade API version 20, i.e. ReShade 6.8 or newer. A 6.7.x install loads silently and then refuses the add-on.' `
|
|
-Action ('Reinstall ReShade 6.8+ (add-on support enabled) over ' + $Path)
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($Label + ': unrecognised version string "' + $ver + '"') -Detail $Path
|
|
}
|
|
}
|
|
|
|
if ($reshadeLocal) {
|
|
Report-ReShadeDll -Path $reshadeLocal -Label ('Local ReShade (' + $reshadeLocalName + ')')
|
|
|
|
if ($gameBits -gt 0 -and $peGame) {
|
|
$peRs = Get-PeInfo $reshadeLocal
|
|
if ($peRs -and $peRs.Bits -gt 0 -and $peRs.Bits -ne $gameBits) {
|
|
Report -Status 'Fail' -Text ('ReShade DLL is ' + $peRs.Bits + '-bit but the game is ' + $gameBits + '-bit') `
|
|
-Action ('Replace ' + $reshadeLocalName + ' with the ' + $gameBits + '-bit ReShade build.')
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Na' -Text 'No local ReShade DLL next to the exe (expected for the Vulkan install).'
|
|
}
|
|
|
|
$pdRoot = 'C:\ProgramData\ReShade'
|
|
$needVulkanCheck = ($isVulkan -or -not $reshadeLocal)
|
|
|
|
if ($needVulkanCheck) {
|
|
if ($gameBits -eq 32) { $pdDllName = 'ReShade32.dll' } else { $pdDllName = 'ReShade64.dll' }
|
|
$pdDll = Find-FileIn $pdRoot $pdDllName
|
|
|
|
if ($pdDll) {
|
|
Report-ReShadeDll -Path $pdDll -Label ('Vulkan layer (' + $pdDllName + ')')
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text ('Machine-wide ' + $pdDllName + ' not found under ' + $pdRoot) `
|
|
-Detail 'Without it, ReShade never loads into a Vulkan game (and there is no local DLL here either).' `
|
|
-Action 'Run the ReShade installer, pick the game''s exe, choose Vulkan, and enable add-on support.'
|
|
}
|
|
|
|
$appsIni = Find-FileIn $pdRoot 'ReShadeApps.ini'
|
|
if (-not $appsIni) {
|
|
Report -Status 'Warn' -Text 'ReShadeApps.ini not found -- cannot confirm this game is allowed to load ReShade.' `
|
|
-Detail ($pdRoot + '\ReShadeApps.ini')
|
|
}
|
|
else {
|
|
$appsText = Read-TextSafe $appsIni
|
|
if ($null -eq $appsText) {
|
|
Report -Status 'Warn' -Text 'ReShadeApps.ini is unreadable (permissions?).' -Detail $appsIni
|
|
}
|
|
elseif (-not $exePath) {
|
|
Report -Status 'Warn' -Text 'No game exe identified, so the ReShadeApps.ini gate cannot be checked.'
|
|
}
|
|
else {
|
|
$m = [regex]::Match($appsText, '(?im)^\s*Apps\s*=\s*(.*)$')
|
|
$entries = @()
|
|
if ($m.Success) {
|
|
# @() so a single surviving entry is still an array. Without it the pipeline
|
|
# unrolls to a bare String, and $entries.Count below is a hard error under the
|
|
# Set-StrictMode -Version 2.0 at the top of this script -- on the SUCCESS
|
|
# branch, so it only ever killed installs that were correct (#13).
|
|
$entries = @($m.Groups[1].Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
}
|
|
$listed = $false
|
|
foreach ($e in $entries) {
|
|
if ($e -and ($e.TrimEnd('\') -ieq $exePath.TrimEnd('\'))) { $listed = $true; break }
|
|
}
|
|
if ($listed) {
|
|
Report -Status 'Ok' -Text 'Game exe is on the ReShadeApps.ini Apps= list.' `
|
|
-Detail ($entries.Count.ToString() + ' entries in ' + $appsIni)
|
|
}
|
|
else {
|
|
$stale = @()
|
|
$leaf = [IO.Path]::GetFileName($exePath)
|
|
foreach ($e in $entries) { if ([IO.Path]::GetFileName($e) -ieq $leaf) { $stale += $e } }
|
|
$d = 'ReShade only activates for exes on that comma-separated list.'
|
|
if ($stale.Count -gt 0) {
|
|
$d += "`nA stale entry for the same exe name is present (the game probably moved drives): " + ($stale -join '; ')
|
|
}
|
|
$d += "`nThat file is not writable without elevation, so this script only reports it."
|
|
$q = [char]0x27
|
|
$fix = 'From an ELEVATED PowerShell, back up ' + $appsIni +
|
|
' and append this exe to its Apps= line:' + "`n" +
|
|
'$p = ' + $q + $appsIni + $q + '; Copy-Item $p "$p.bak"; ' +
|
|
'$c = (Get-Content -Raw $p).TrimEnd() + ' + $q + ',' + $exePath + $q + '; ' +
|
|
'[IO.File]::WriteAllText($p, $c, (New-Object Text.UTF8Encoding $true))'
|
|
Report -Status 'Fail' -Text 'Game exe is NOT on the ReShadeApps.ini Apps= list.' -Detail $d `
|
|
-Action $fix
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Na' -Text 'Vulkan implicit-layer checks skipped (this is a local-DLL install).'
|
|
}
|
|
|
|
$reshadeIni = Find-FileIn $gameDir 'ReShade.ini'
|
|
if ($reshadeIni) { Report -Status 'Ok' -Text 'ReShade.ini present.' }
|
|
else { Report -Status 'Warn' -Text 'No ReShade.ini in the game folder -- ReShade will create defaults, which may not find the shaders.' }
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 3. The feeder's own files
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'DLSS5-Feeder'
|
|
|
|
if ($gameBits -eq 32) { $addonName = 'dlss5-feed.addon32'; $wrongAddon = 'dlss5-feed.addon64' }
|
|
else { $addonName = 'dlss5-feed.addon64'; $wrongAddon = 'dlss5-feed.addon32' }
|
|
|
|
$addonPath = Find-FileIn $gameDir $addonName
|
|
$feedVersion = $null
|
|
|
|
if ($addonPath) {
|
|
$feedVersion = Get-BinaryMarker -Path $addonPath -Pattern 'DLSS 5 Feed (?:\(32-bit\) )?(\d[\w.\-+]*)'
|
|
$peAddon = Get-PeInfo $addonPath
|
|
$d = ''
|
|
if ($peAddon -and $peAddon.Bits -gt 0) { $d = $peAddon.Arch + ' image' }
|
|
if ($feedVersion) {
|
|
Report -Status 'Ok' -Text ($addonName + ': version ' + $feedVersion) -Detail $d
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($addonName + ': present, but no version string found inside it.') `
|
|
-Detail ('Expected an exported NAME of "DLSS 5 Feed <version>". ' + $d).Trim()
|
|
}
|
|
if ($peAddon -and $peAddon.Bits -gt 0 -and $gameBits -gt 0 -and $peAddon.Bits -ne $gameBits) {
|
|
Report -Status 'Fail' -Text ($addonName + ' is a ' + $peAddon.Bits + '-bit image but the game is ' + $gameBits + '-bit.') `
|
|
-Action ('Deploy the ' + $gameBits + '-bit add-on build.')
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text ($addonName + ' is missing.') `
|
|
-Detail ('This is the feeder itself; without it nothing else here does anything. Expected next to ' + $gameDir) `
|
|
-Action ('Copy ' + $addonName + ' into the game folder.')
|
|
}
|
|
|
|
$strayAddon = Find-FileIn $gameDir $wrongAddon
|
|
if ($strayAddon) {
|
|
Report -Status 'Warn' -Text ($wrongAddon + ' is also present, and is for the other architecture.') `
|
|
-Detail 'Harmless (ReShade will not load it) but it makes the install confusing -- remove it.'
|
|
}
|
|
|
|
# The 32-bit split-process path needs the 64-bit host helper.
|
|
$hostDir = Join-Safe $gameDir 'host64'
|
|
$hostExe = $null
|
|
|
|
if ($gameBits -eq 32) {
|
|
$hostExe = Find-FileIn $hostDir 'dlss5-feed-host64.exe'
|
|
if ($hostExe) {
|
|
$peHost = Get-PeInfo $hostExe
|
|
$d = ''
|
|
if ($peHost -and $peHost.Built) { $d = 'linked ' + $peHost.Built.ToString('yyyy-MM-dd HH:mm') + ' UTC' }
|
|
Report -Status 'Ok' -Text 'host64\dlss5-feed-host64.exe present.' -Detail $d
|
|
|
|
# The host exe carries no version string, so compare PE link timestamps: the two halves
|
|
# are built together, and a gap means they came from different releases. The host also
|
|
# reports a real protocol mismatch in its log, which the log section below picks up.
|
|
if ($addonPath) {
|
|
$peAddon2 = Get-PeInfo $addonPath
|
|
if ($peAddon2 -and $peHost -and $peAddon2.Built -and $peHost.Built) {
|
|
$gap = [math]::Abs(($peAddon2.Built - $peHost.Built).TotalHours)
|
|
if ($gap -gt 24) {
|
|
Report -Status 'Warn' -Text 'The add-on and the host64 helper look like different builds.' `
|
|
-Detail ('dlss5-feed.addon32 linked ' + $peAddon2.Built.ToString('yyyy-MM-dd HH:mm') +
|
|
' UTC, host64 linked ' + $peHost.Built.ToString('yyyy-MM-dd HH:mm') + ' UTC (' +
|
|
[math]::Round($gap / 24, 1) + ' days apart).' +
|
|
"`nA version mismatch across the IPC pipe is a real failure mode -- the host refuses the connection and neural rendering never starts. Redeploy both halves from the same release.")
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text 'Add-on and host64 helper are from the same build (link timestamps agree).'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'host64\dlss5-feed-host64.exe is missing.' `
|
|
-Detail 'A 32-bit game needs the 64-bit helper process -- the x86 add-on cannot talk to NGX itself.' `
|
|
-Action 'Create host64\ next to the game exe and copy dlss5-feed-host64.exe into it.'
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Na' -Text 'host64\ helper not applicable (64-bit game runs in-process).'
|
|
}
|
|
|
|
# Shader + framework headers.
|
|
$shaderDir = Join-Safe $gameDir 'reshade-shaders\Shaders'
|
|
$fx = Find-FileUnder $shaderDir 'DLSS5_Feed.fx'
|
|
if ($fx) {
|
|
$d = ''
|
|
$parent = [IO.Path]::GetDirectoryName($fx)
|
|
if ($parent -and -not ($parent -ieq $shaderDir)) { $d = 'in a sub-folder: ' + $parent }
|
|
Report -Status 'Ok' -Text 'reshade-shaders\Shaders\DLSS5_Feed.fx present.' -Detail $d
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'reshade-shaders\Shaders\DLSS5_Feed.fx is missing.' `
|
|
-Detail 'This is the effect that hands the add-on colour, depth and motion vectors.' `
|
|
-Action 'Copy DLSS5_Feed.fx into reshade-shaders\Shaders\.'
|
|
}
|
|
|
|
# Headers may legitimately sit in a sub-folder (Shaders\CrosireMaster\, say): ReShade's
|
|
# default EffectSearchPaths ends in "**", so the compiler finds them there.
|
|
$missingHeaders = @()
|
|
foreach ($h in @('ReShade.fxh', 'ReShadeUI.fxh', 'DrawText.fxh')) {
|
|
if (-not (Find-FileUnder $shaderDir $h)) { $missingHeaders += $h }
|
|
}
|
|
if ($missingHeaders.Count -eq 0) {
|
|
Report -Status 'Ok' -Text 'ReShade framework headers present (ReShade.fxh, ReShadeUI.fxh, DrawText.fxh).'
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text ('Missing framework header(s): ' + ($missingHeaders -join ', ')) `
|
|
-Detail 'Every .fx includes these; without them DLSS5_Feed.fx fails to compile and the add-on is fed nothing.' `
|
|
-Action 'Copy the missing .fxh files from crosire/reshade-shaders into reshade-shaders\Shaders\.'
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 4. The neural consumer
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'Neural consumer'
|
|
|
|
if ($gameBits -eq 32) {
|
|
$consumerDir = $hostDir
|
|
$consumerWhere = 'host64\'
|
|
}
|
|
else {
|
|
$consumerDir = $gameDir
|
|
$consumerWhere = 'the game folder'
|
|
}
|
|
|
|
$dfcAddon = Find-FileIn $consumerDir 'deep-fried-chicken.addon64'
|
|
$renoAddon = Find-FileIn $consumerDir 'renodx-dlss5*.addon64'
|
|
$toolkit = Find-FileIn $consumerDir 'alexs-toolkit.addon64'
|
|
$dx11Bridge = Find-FileIn $consumerDir 'dlss5-dx11-bridge.addon64'
|
|
|
|
# OptiScaler, under any of the names it installs as. The DLSS-NR fork (Dagherbou/OptiScaler_DLSSNR)
|
|
# is a supported consumer; stock OptiScaler is not -- it takes the feeder's NGX calls, upscales, and
|
|
# never runs a neural pass. Told apart by the neural forwarder's file name, a literal only the fork has.
|
|
$optiDll = $null
|
|
$optiFork = $false
|
|
foreach ($n in @('winmm.dll', 'version.dll', 'dbghelp.dll', 'winhttp.dll', 'wininet.dll', 'd3d12.dll', 'OptiScaler.dll', 'OptiScaler.asi')) {
|
|
$p = Find-FileIn $consumerDir $n
|
|
if (-not $p) { continue }
|
|
if (Get-BinaryMarker -Path $p -Pattern 'nvngx\.dll_dlssnr\.dll') { $optiDll = $p; $optiFork = $true; break }
|
|
if (Get-BinaryMarker -Path $p -Pattern 'OptiScaler\.ini') { $optiDll = $p; break }
|
|
}
|
|
|
|
if ($gameBits -eq 32) {
|
|
# A 64-bit add-on beside a 32-bit exe is the single most common 32-bit deploy mistake.
|
|
foreach ($n in @('deep-fried-chicken.addon64', 'renodx-dlss5*.addon64', 'alexs-toolkit.addon64')) {
|
|
$stray = Find-FileIn $gameDir $n
|
|
if ($stray) {
|
|
$n = [IO.Path]::GetFileName($stray) # the versioned name, not the pattern
|
|
Report -Status 'Fail' -Text ($n + ' is next to the 32-bit game exe -- wrong place.') `
|
|
-Detail 'This game is 32-bit, so the neural consumer must live in host64\ where the 64-bit helper process loads it. A 64-bit add-on beside an x86 exe is never loaded by anything.' `
|
|
-Action ('Move ' + $n + ' into ' + $hostDir)
|
|
}
|
|
}
|
|
$strayOpti = Find-FileIn $gameDir 'OptiScaler.ini'
|
|
if ($strayOpti) {
|
|
Report -Status 'Fail' -Text 'The OptiScaler set is next to the 32-bit game exe -- wrong place.' `
|
|
-Detail 'OptiScaler is 64-bit. For a 32-bit game it goes into host64\ (OptiScaler.dll renamed winmm.dll beside dlss5-feed-host64.exe), where the DLSS work happens. A 64-bit winmm.dll or version.dll beside a 32-bit exe stops the game from starting at all.' `
|
|
-Action ('Move OptiScaler.ini, the OptiScaler DLL, nvngx.dll_dlssnr.dll and the OptiScaler\ folder into ' + $hostDir)
|
|
}
|
|
# And the mirror image of it: the feeder's own 64-bit add-on inside host64\. Unlike a
|
|
# stray consumer beside the exe this one does load -- host64\ is a 64-bit ReShade
|
|
# install, so the helper picks it up and runs the add-on meant for a 64-bit GAME inside
|
|
# the very process that is serving the 32-bit one. It opens a second NGX session on its
|
|
# own private device and detours nvngx over the consumer's hooks, in a process where
|
|
# none of that is tested. Recent builds recognise the helper and stay inert, but an
|
|
# older one does not, and either way the file should not be there.
|
|
$strayFeeder = Find-FileIn $hostDir 'dlss5-feed.addon64'
|
|
if ($strayFeeder) {
|
|
Report -Status 'Fail' -Text 'host64\dlss5-feed.addon64 should not be there.' `
|
|
-Detail 'That is the add-on for a 64-bit GAME. The helper''s own ReShade loads every add-on in host64\, so this one ends up inside the helper process, where it can do nothing useful and gets in the way of the neural consumer. host64\ takes dlss5-feed-host64.exe, a 64-bit ReShade dxgi.dll, the neural consumer and the nvngx runtimes -- nothing else.' `
|
|
-Action ('Delete ' + $strayFeeder)
|
|
}
|
|
if (-not (Test-DirHere $hostDir)) {
|
|
Report -Status 'Fail' -Text 'host64\ does not exist, so there is nowhere for the neural consumer to live.' `
|
|
-Action 'Create host64\ and deploy the helper, the neural consumer and the NVIDIA DLLs into it.'
|
|
}
|
|
}
|
|
|
|
$consumers = @()
|
|
if ($dfcAddon) { $consumers += 'Deep Fried Chicken' }
|
|
if ($renoAddon) { $consumers += 'RenoDX DLSS 5' }
|
|
if ($optiDll) { $consumers += ('OptiScaler (' + [IO.Path]::GetFileName($optiDll) + ')') }
|
|
if ($consumers.Count -ge 2) {
|
|
if ($optiDll) {
|
|
$why = 'OptiScaler captures every nvngx load in the process: another consumer beside it either talks to OptiScaler instead of the driver (Chicken''s own deep-fried-chicken-nvngx.dll ends in nvngx.dll) or runs its neural pass a second time inside OptiScaler''s DLSS backend.'
|
|
}
|
|
else {
|
|
$why = 'Deep Fried Chicken goes completely inert for the whole process while a RenoDX neural provider is loaded. Everything still looks healthy -- frames are delivered, no errors -- and neural rendering does nothing.'
|
|
}
|
|
Report -Status 'Fail' -Text ('More than one neural consumer is present: ' + ($consumers -join ', ') + '.') `
|
|
-Detail $why `
|
|
-Action ('Keep exactly one in ' + $consumerDir + ' and remove or rename the rest.')
|
|
}
|
|
elseif ($optiDll) {
|
|
$optiName = [IO.Path]::GetFileName($optiDll)
|
|
if (-not $optiFork) {
|
|
Report -Status 'Fail' -Text ($optiName + ' is OptiScaler, but NOT the DLSS-NR fork.') `
|
|
-Detail 'Stock OptiScaler takes the feeder''s NGX calls and upscales, and no neural pass ever runs -- the picture never changes while every log reads healthy.' `
|
|
-Action 'Use the Dagherbou/OptiScaler_DLSSNR release, or remove OptiScaler and install Deep Fried Chicken.'
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text ('OptiScaler DLSS-NR present as ' + $optiName + ' (supported alternative).') `
|
|
-Detail ('in ' + $consumerWhere + ' -- it answers the feeder''s NGX calls itself, upscales, then runs the neural pass in place. Its menu is on Insert.')
|
|
if ($gameBits -eq 32 -and $optiName -notmatch '(?i)^(winmm|version)\.dll$') {
|
|
Report -Status 'Fail' -Text ('host64\' + $optiName + ' is never loaded by the helper.') `
|
|
-Detail 'dlss5-feed-host64.exe imports winmm.dll and version.dll at start; under any other name OptiScaler is not in the process when the first NGX call is made, and the driver answers instead.' `
|
|
-Action ('Rename ' + $optiName + ' to winmm.dll in ' + $hostDir)
|
|
}
|
|
if (Find-FileIn $consumerDir 'nvngx.dll_dlssnr.dll') {
|
|
Report -Status 'Ok' -Text 'nvngx.dll_dlssnr.dll (the neural forwarder) present.'
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'nvngx.dll_dlssnr.dll is missing.' `
|
|
-Detail 'The neural model refuses any caller whose module path does not contain nvngx.dll; this 100 KB shim from the OptiScaler_DLSSNR zip is what satisfies it. Without it OptiScaler.log says "nvngx.dll_dlssnr.dll not found" and no neural pass runs.' `
|
|
-Action ('Extract nvngx.dll_dlssnr.dll from the OptiScaler_DLSSNR zip into ' + $consumerDir)
|
|
}
|
|
if (-not (Test-DirHere (Join-Safe $consumerDir 'OptiScaler'))) {
|
|
Report -Status 'Warn' -Text 'The OptiScaler\ runtime folder is missing.' `
|
|
-Detail 'It holds libxess, the FidelityFX runtimes and the Agility SDK. The dlss backend does not need them, but OptiScaler preloads them and logs their absence.'
|
|
}
|
|
$optiIni = Join-Safe $consumerDir 'OptiScaler.ini'
|
|
if (Test-FileHere $optiIni) {
|
|
$en = Get-IniValue -Path $optiIni -Section 'DlssNr' -Key 'Enabled'
|
|
if ($en -and $en.Trim() -imatch '^(true|1)$') {
|
|
Report -Status 'Ok' -Text 'OptiScaler.ini: [DlssNr] Enabled=true.'
|
|
}
|
|
else {
|
|
$shown = if ($en) { $en.Trim() } else { '(absent)' }
|
|
Report -Status 'Fail' -Text ('OptiScaler.ini: [DlssNr] Enabled=' + $shown + ' -- the neural pass is OFF.') `
|
|
-Detail 'auto means false: the fork ships with the pass off. OptiScaler will take the feeder''s calls and only upscale.' `
|
|
-Action 'Set Enabled=true under [DlssNr] in OptiScaler.ini (or tick it in OptiScaler''s menu, Insert) and restart.'
|
|
}
|
|
$up = Get-IniValue -Path $optiIni -Section 'Upscalers' -Key 'Dx12Upscaler'
|
|
$upShown = if ($up) { $up.Trim() } else { 'auto' }
|
|
Report -Status 'Na' -Text ('OptiScaler.ini: [Upscalers] Dx12Upscaler=' + $upShown + ' (dlss keeps the feed''s DLAA; auto picks DLSS on an RTX with nvngx_dlss.dll beside it).')
|
|
$se = Get-IniValue -Path $optiIni -Section 'DlssNr' -Key 'ScanExposure'
|
|
if ($se -and $se.Trim() -imatch '^(true|1)$') {
|
|
Report -Status 'Warn' -Text 'OptiScaler.ini: [DlssNr] ScanExposure=true.' `
|
|
-Detail 'The scan hooks resource creation on the feeder''s device looking for an exposure buffer the feed never offers.' `
|
|
-Action 'Set ScanExposure=false.'
|
|
}
|
|
$di = Get-IniValue -Path $optiIni -Section 'Inputs' -Key 'EnableDlssInputs'
|
|
$ho = Get-IniValue -Path $optiIni -Section 'Hooks' -Key 'HookOriginalNvngxOnly'
|
|
if (($di -and $di.Trim() -imatch '^(false|0)$') -or ($ho -and $ho.Trim() -imatch '^(true|1)$')) {
|
|
Report -Status 'Fail' -Text 'OptiScaler.ini defeats the nvngx redirect.' `
|
|
-Detail ('[Inputs] EnableDlssInputs=' + $di + ', [Hooks] HookOriginalNvngxOnly=' + $ho + '. With these the feeder''s NGX calls reach the driver, not OptiScaler, and nothing neural happens.') `
|
|
-Action 'Set EnableDlssInputs=true (or auto) and HookOriginalNvngxOnly=false (or auto).'
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'OptiScaler.ini is missing beside OptiScaler.' `
|
|
-Action ('Extract it from the zip into ' + $consumerDir + ' and set [DlssNr] Enabled=true.')
|
|
}
|
|
$dlssDllForSig = Find-FileIn $consumerDir 'nvngx_dlss.dll'
|
|
if ($dlssDllForSig) {
|
|
try {
|
|
$sig = Get-AuthenticodeSignature -LiteralPath $dlssDllForSig -ErrorAction Stop
|
|
if ($sig.Status -eq 'Valid') {
|
|
Report -Status 'Ok' -Text 'nvngx_dlss.dll carries a valid signature (OptiScaler redirects the NGX SDK''s trust check to it).'
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ('nvngx_dlss.dll signature: ' + $sig.Status + '.') `
|
|
-Detail 'OptiScaler points the NGX SDK''s signature check at this file; an unsigned or tampered copy makes the SDK refuse the core ("failed to load NGXCore").'
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
elseif ($dfcAddon) {
|
|
$dfcVer = Get-BinaryMarker -Path $dfcAddon -Pattern 'Deep Fried Chicken (\d[\w.\-+]*)'
|
|
if ($dfcVer) { $t = 'Deep Fried Chicken ' + $dfcVer + ' (recommended default).' }
|
|
else { $t = 'Deep Fried Chicken present (version string not found).' }
|
|
Report -Status 'Ok' -Text $t -Detail ('in ' + $consumerWhere)
|
|
|
|
foreach ($f in @('deep-fried-chicken-nvngx.dll', 'deep-fried-chicken.cfg')) {
|
|
if (Find-FileIn $consumerDir $f) {
|
|
Report -Status 'Ok' -Text ($f + ' present.')
|
|
}
|
|
elseif ($f -match 'nvngx') {
|
|
Report -Status 'Fail' -Text ($f + ' is missing.') `
|
|
-Detail 'This is Chicken''s private NGX bridge (a few KB, not a copy of NVIDIA''s). Without it Chicken cannot attach.' `
|
|
-Action ('Copy ' + $f + ' into ' + $consumerDir)
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($f + ' is missing -- Chicken will fall back to its built-in defaults.')
|
|
}
|
|
}
|
|
}
|
|
elseif ($renoAddon) {
|
|
Report -Status 'Ok' -Text ([IO.Path]::GetFileName($renoAddon) + ' present (supported alternative).') `
|
|
-Detail ('in ' + $consumerWhere + '. Deep Fried Chicken is the recommended default.')
|
|
# ReShade loads every *.addon64 in the folder, so a second copy is not redundant -- both
|
|
# hook NGX and the result is undefined.
|
|
$renoAll = @(Find-FilesIn $consumerDir 'renodx-dlss5*.addon64')
|
|
if ($renoAll.Count -gt 1) {
|
|
Report -Status 'Warn' -Text 'More than one renodx-dlss5 add-on is present.' `
|
|
-Detail (($renoAll | ForEach-Object { $_.Name }) -join ', ') `
|
|
-Action ('Keep one of them in ' + $consumerDir + ' and remove or rename the rest.')
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'No neural consumer found.' `
|
|
-Detail ('Expected deep-fried-chicken.addon64 (recommended), renodx-dlss5.addon64, or the OptiScaler DLSS-NR set (winmm.dll + OptiScaler.ini + nvngx.dll_dlssnr.dll) in ' + $consumerDir + '. The feeder publishes a synthetic DLSS contract; without a consumer, nothing acts on it.') `
|
|
-Action ('Copy deep-fried-chicken.addon64 (+ deep-fried-chicken-nvngx.dll and deep-fried-chicken.cfg) into ' + $consumerDir)
|
|
}
|
|
|
|
if ($toolkit) {
|
|
if ($dfcAddon) {
|
|
Report -Status 'Warn' -Text 'alexs-toolkit.addon64 is present alongside Deep Fried Chicken.' `
|
|
-Detail 'That is a third interposer on the same NGX module. Chicken''s own test notes ask for the toolkit to be removed -- do not combine them.'
|
|
}
|
|
elseif ($optiDll) {
|
|
Report -Status 'Warn' -Text 'alexs-toolkit.addon64 is present alongside OptiScaler.' `
|
|
-Detail 'The toolkit is a cascade over the RenoDX add-on. With OptiScaler as the consumer there is nothing for it to attach to, and it is one more interposer on the NGX module OptiScaler is redirecting.' `
|
|
-Action ('Remove alexs-toolkit.addon64 from ' + $consumerDir)
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'alexs-toolkit.addon64 is present (optional multi-pass cascade).' `
|
|
-Detail 'Fine with the RenoDX provider; check alexs-toolkit.log for "cascade interception is now armed" to confirm it won the load-order race.'
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Na' -Text 'Alex''s Toolkit not installed (optional; off the default path).'
|
|
}
|
|
|
|
if ($dx11Bridge) {
|
|
Report -Status 'Fail' -Text 'dlss5-dx11-bridge.addon64 is present.' `
|
|
-Detail 'This must never be combined with DLSS5-Feeder -- the feeder already provides the contract, and the bridge fights it.' `
|
|
-Action ('Delete dlss5-dx11-bridge.addon64 from ' + $consumerDir)
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 5. NVIDIA runtimes
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'NVIDIA NGX runtimes'
|
|
|
|
foreach ($n in @('nvngx_dlssnr.dll', 'nvngx_dlss.dll')) {
|
|
$p = Find-FileIn $consumerDir $n
|
|
if ($p) {
|
|
$v = Get-FileVersionSafe $p
|
|
if ($v) { $t = $n + ': ' + $v } else { $t = $n + ': present (no version info)' }
|
|
# NVIDIA's own build and ShortFuse's .SF repack both carry file version 310.8.0.0, so
|
|
# the version alone cannot tell them apart -- and that is exactly the distinction
|
|
# issue #47 turns on. The product/description strings do differ, so print them.
|
|
$d = ('in ' + $consumerWhere)
|
|
$pn = Get-ProductNameSafe $p
|
|
if ($pn) { $d = $d + "`n" + $pn }
|
|
# Two more fields, and neither is asserted to be decisive. This used to call
|
|
# OriginalFilename ("CL 38718415") the sharpest tell there is; #50's reporter says the
|
|
# changelist is identical on both builds and what differs is the stated FileVersion
|
|
# STRING -- "310,8,0,0" from NVIDIA, "310.8.SF.0" from the repack. That string is a
|
|
# separate field from the version quad above and was never being read. Print both and
|
|
# let whoever compares two machines decide.
|
|
try {
|
|
$vi = (Get-Item -LiteralPath $p -ErrorAction Stop).VersionInfo
|
|
if ($vi.OriginalFilename) { $d = $d + "`nbuild: " + $vi.OriginalFilename.Trim() }
|
|
# Only when it disagrees with the quad: on a stock runtime it is the same numbers
|
|
# with commas, and repeating it would train people to skip the line.
|
|
if ($vi.FileVersion) {
|
|
$stated = ($vi.FileVersion -replace '\s', '') -replace ',', '.'
|
|
if ($stated -and $stated -ne $v) { $d = $d + "`nstated FileVersion: " + $vi.FileVersion.Trim() }
|
|
}
|
|
}
|
|
catch { }
|
|
Report -Status 'Ok' -Text $t -Detail $d
|
|
}
|
|
else {
|
|
if ($n -eq 'nvngx_dlssnr.dll') { $why = 'This is the neural-rendering model itself -- DLSS 5 cannot run without it.' }
|
|
else { $why = 'The DLSS super-resolution runtime; the neural consumer expects it beside the NR model.' }
|
|
Report -Status 'Fail' -Text ($n + ' is missing from ' + $consumerWhere + '.') -Detail $why `
|
|
-Action ('Copy ' + $n + ' into ' + $consumerDir)
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 6. The d3dcompiler_47.dll trap
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'd3dcompiler_47.dll'
|
|
|
|
$dcDirs = @(@{ Dir = $gameDir; Label = 'game folder' })
|
|
if ($gameBits -eq 32 -and (Test-DirHere $hostDir)) { $dcDirs += @{ Dir = $hostDir; Label = 'host64\' } }
|
|
|
|
$foundAny = $false
|
|
foreach ($e in $dcDirs) {
|
|
$p = Find-FileIn $e.Dir 'd3dcompiler_47.dll'
|
|
if (-not $p) { continue }
|
|
$foundAny = $true
|
|
$v = Get-FileVersionSafe $p
|
|
if (-not $v) {
|
|
Report -Status 'Warn' -Text ('A d3dcompiler_47.dll sits in the ' + $e.Label + ' but its version is unreadable.') `
|
|
-Detail ($p + "`nWindows loads this copy in preference to System32's. If it predates Shader Model 5.1 the neural pass silently fails every frame.")
|
|
}
|
|
elseif ($v -match '^6\.3\.') {
|
|
Report -Status 'Fail' -Text ('Windows 8.1-era d3dcompiler_47.dll in the ' + $e.Label + ': ' + $v) `
|
|
-Detail ($p + "`nThis copy wins the DLL search order over System32's and knows nothing past Shader Model 5.0. The neural pass compiles as cs_5_1, so it fails EVERY FRAME, silently: the log still reports frames delivered and neural rendering does nothing. ReShade.log shows: error X3506: unrecognized compiler target 'cs_5_1'.") `
|
|
-Action ('Delete or rename ' + $p + ' (System32''s copy is then used, which is fine).')
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text ('d3dcompiler_47.dll in the ' + $e.Label + ': ' + $v + ' -- new enough for cs_5_1.') -Detail $p
|
|
}
|
|
}
|
|
if (-not $foundAny) {
|
|
Report -Status 'Ok' -Text 'No local d3dcompiler_47.dll -- System32''s copy is used, which is correct.'
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 7. GPU
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'Direct3D 12 runtime'
|
|
|
|
# A game-local D3D12 folder is an Agility SDK redist path. If the game's exe exports
|
|
# D3D12SDKVersion/D3D12SDKPath, every D3D12 device created in that process -- including the
|
|
# feeder's private one -- loads D3D12Core.dll from there. An empty or incomplete folder then
|
|
# fails D3D12CreateDevice with 0x887E0003 (D3D12_ERROR_INVALID_REDIST), which reads as a
|
|
# feeder bug and is not one. Issue #61 arrived with exactly that code and an empty folder.
|
|
$agilityDir = Join-Safe $gameDir 'D3D12'
|
|
if (-not (Test-Path -LiteralPath $agilityDir -PathType Container)) {
|
|
Report -Status 'Ok' -Text 'No game-local D3D12\ (Agility SDK) folder; the system Direct3D 12 runtime is used.'
|
|
}
|
|
else {
|
|
$agilityFiles = @(Get-ChildItem -LiteralPath $agilityDir -File -ErrorAction SilentlyContinue)
|
|
$agilityCore = @($agilityFiles | Where-Object { $_.Name -ieq 'D3D12Core.dll' })
|
|
$agilityInventory = ($agilityFiles | ForEach-Object {
|
|
$fv = $_.VersionInfo.FileVersion
|
|
$_.Name + ' version=' + $(if ($fv) { $fv } else { '?' }) + ' bytes=' + $_.Length
|
|
}) -join '; '
|
|
if ($agilityCore.Count -gt 0) {
|
|
Report -Status 'Ok' -Text ('Game-local D3D12\ (Agility SDK) folder with D3D12Core.dll (' + $agilityFiles.Count + ' file(s)).') `
|
|
-Detail $agilityInventory
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ('Game-local D3D12\ folder with ' + $agilityFiles.Count + ' file(s) and NO D3D12Core.dll.') `
|
|
-Detail 'If the game points Direct3D 12 at this folder, every device created in the process fails with 0x887E0003 (D3D12_ERROR_INVALID_REDIST) -- including the feeder private device, which then reports "D3D12CreateDevice failed".' `
|
|
-Action 'If the feeder log shows D3D12CreateDevice failed 0x887E0003, rename the game D3D12 folder and relaunch.'
|
|
}
|
|
}
|
|
|
|
Write-Section 'GPU'
|
|
|
|
$gpus = $null
|
|
try { $gpus = Get-CimInstance -ClassName Win32_VideoController -ErrorAction Stop }
|
|
catch {
|
|
try { $gpus = Get-WmiObject -Class Win32_VideoController -ErrorAction Stop } catch { $gpus = $null }
|
|
}
|
|
|
|
if (-not $gpus) {
|
|
Report -Status 'Warn' -Text 'Could not query the display adapters (WMI/CIM unavailable).' `
|
|
-Detail 'DLSS needs an NVIDIA RTX card; verify manually.'
|
|
}
|
|
else {
|
|
$rtx = @($gpus | Where-Object { $_.Name -and $_.Name -match '(?i)nvidia' -and $_.Name -match '(?i)(RTX|TITAN RTX)' })
|
|
$names = @($gpus | ForEach-Object { $_.Name }) -join '; '
|
|
if ($rtx.Count -gt 0) {
|
|
Report -Status 'Ok' -Text ('NVIDIA RTX adapter found: ' + (($rtx | ForEach-Object { $_.Name }) -join '; '))
|
|
|
|
# The driver version. Win32_VideoController already carries it, so this costs no
|
|
# extra query -- and without it "the driver is too old" and issue #47's NGX
|
|
# 0xBAD00001 are indistinguishable from this script's output. One reporter passed
|
|
# with 14 OK / 0 failures on 596.36, a configuration that cannot work at all.
|
|
# NVIDIA's branding is the last five digits of the Windows version, dotted.
|
|
$drv = @($rtx | ForEach-Object { $_.DriverVersion } | Where-Object { $_ })
|
|
if ($drv.Count -eq 0) {
|
|
Report -Status 'Warn' -Text 'The NVIDIA driver version could not be read.' `
|
|
-Detail ('DLSS 5 neural rendering needs ' + $kMinDriver + ' or newer; check it in the NVIDIA app.')
|
|
}
|
|
else {
|
|
# Compare the five digits as an INTEGER, never as a decimal: [double] '616.56'
|
|
# goes through the current culture in PowerShell 5.1, and on a comma-decimal
|
|
# machine that silently reads as 61656 while the literal reads as 616.56.
|
|
$digits = ($drv[0] -replace '[^0-9]', '')
|
|
$shown = $drv[0]
|
|
$num = $null
|
|
if ($digits.Length -ge 5) {
|
|
$tail = $digits.Substring($digits.Length - 5)
|
|
$shown = $tail.Substring(0, 3) + '.' + $tail.Substring(3)
|
|
$num = [int]$tail
|
|
}
|
|
if ($null -eq $num) {
|
|
Report -Status 'Na' -Text ('NVIDIA driver: ' + $shown) `
|
|
-Detail ('Could not compare against the ' + $kMinDriver + ' minimum; check it by hand.')
|
|
}
|
|
elseif ($num -lt $kMinDriverDigits) {
|
|
Report -Status 'Fail' -Text ('NVIDIA driver ' + $shown + ' is older than ' + $kMinDriver + '.') `
|
|
-Detail 'DLSS 5 neural rendering ships in the driver, and NGX reports FeatureNotSupported (0xBAD00001) on older ones -- which looks exactly like a bug in this project.' `
|
|
-Action ('Update the NVIDIA driver to ' + $kMinDriver + ' or newer, then re-run this script.')
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text ('NVIDIA driver: ' + $shown) -Detail ('Minimum for neural rendering is ' + $kMinDriver + '.')
|
|
}
|
|
}
|
|
|
|
if (@($gpus).Count -gt 1) {
|
|
Report -Status 'Na' -Text ('Other adapters present: ' + $names) `
|
|
-Detail 'On a hybrid machine, confirm the game actually renders on the RTX GPU -- the interop extensions do not exist on an iGPU.'
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'No NVIDIA RTX adapter detected.' `
|
|
-Detail ('Adapters: ' + $names + "`nDLSS/NGX requires an RTX card; nothing below will work without one.")
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 8. Logs
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'Logs'
|
|
|
|
function Report-FeedLog
|
|
{
|
|
# $Half is '' for the ordinary in-process case, or 'game'/'host' for the 32-bit split
|
|
# path, where the two halves each only ever log one side of the story.
|
|
param([string] $Path, [string] $Label, [string] $Half = '')
|
|
|
|
$lines = Read-LinesSafe $Path
|
|
if ($null -eq $lines) {
|
|
Report -Status 'Warn' -Text ($Label + ' exists but could not be read.') -Detail $Path
|
|
return
|
|
}
|
|
|
|
# Strip the "HH:MM:SS.mmm [feed] " / "[host] " prefix so the digest reads as prose.
|
|
function Strip-Prefix { param([string] $S) return (($S -replace '^\s*[\d:.]+\s+', '') -replace '^\[(feed|host)\]\s*', '').Trim() }
|
|
|
|
$verLine = @($lines | Where-Object { $_ -match '(?i)dlss5-feed\S*\s.*\(built' }) | Select-Object -Last 1
|
|
if (-not $verLine) { $verLine = @($lines | Where-Object { $_ -match '(?i)dlss5-feed\S*\s.*attached\.' }) | Select-Object -Last 1 }
|
|
if ($verLine) { Report -Status 'Na' -Text ($Label + ': ' + (Strip-Prefix $verLine)) }
|
|
else { Report -Status 'Na' -Text ($Label + ': present (' + $lines.Count + ' lines), no version banner found.') }
|
|
|
|
$ready = @($lines | Where-Object { $_ -match '(?i)feature ready' }) | Select-Object -Last 1
|
|
if ($ready) {
|
|
Report -Status 'Ok' -Text (Strip-Prefix $ready)
|
|
}
|
|
elseif ($Half -eq 'game') {
|
|
Report -Status 'Na' -Text ($Label + ': no "feature ready" line -- expected here; the host64 helper creates the feature.')
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($Label + ': no "feature ready" line -- the DLSS feature was never created in the last run.')
|
|
}
|
|
|
|
$delivered = @($lines | Where-Object { $_ -match '(?i)frame \d+ delivered' }) | Select-Object -Last 1
|
|
if ($delivered) {
|
|
$m = [regex]::Match($delivered, 'frame (\d+) delivered')
|
|
if ($m.Success) { Report -Status 'Ok' -Text ('frames delivered: last seen frame ' + $m.Groups[1].Value + '.') }
|
|
else { Report -Status 'Ok' -Text 'frames delivered.' }
|
|
}
|
|
elseif ($Half -eq 'host') {
|
|
Report -Status 'Na' -Text ($Label + ': no "frame N delivered" line -- expected here; the game-side add-on counts delivered frames.')
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($Label + ': no "frame N delivered" line -- nothing was ever fed to the consumer.')
|
|
}
|
|
|
|
# The "[feed] effects:" line carries the provider the effect was actually COMPILED with,
|
|
# which is what section 9 should believe over any of the three ini levels (issue #50).
|
|
$effects = @($lines | Where-Object { $_ -match '(?i)DLSS5_MV_PROVIDER\s*=\s*\d' }) | Select-Object -Last 1
|
|
if ($effects) {
|
|
$m = [regex]::Match($effects, '(?i)DLSS5_MV_PROVIDER\s*=\s*(\d+)')
|
|
if ($m.Success -and -not $script:FeedLogProvider) { $script:FeedLogProvider = $m.Groups[1].Value }
|
|
}
|
|
|
|
$bad = @($lines | Where-Object { $_ -match '(?i)(WARNING|not loaded|disabl|TOO OLD|different releases|refus)' } |
|
|
Select-Object -Unique)
|
|
|
|
# "DLSS5_Feed.fx is not loaded" is logged once at attach, before ReShade has compiled any
|
|
# effect. If that same run went on to deliver frames, it was only the start-up transient.
|
|
if ($delivered) { $bad = @($bad | Where-Object { $_ -notmatch '(?i)DLSS5_Feed\.fx is not loaded' }) }
|
|
|
|
# The overlay's Enabled checkbox (saved as enabled= in dlss5-feed.cfg) is the user's own
|
|
# switch, not a fault. Only its last state counts, and it needs the way back (#102).
|
|
$toggle = @($lines | Where-Object { $_ -match '(?i)(disabled from the overlay|enabled=0: no frames are fed|enabled from the overlay|enabled=1 read back from dlss5-feed\.cfg)' }) | Select-Object -Last 1
|
|
$switchedOff = [bool]($toggle -and ($toggle -match '(?i)(disabled from the overlay|enabled=0: no frames are fed)'))
|
|
$bad = @($bad | Where-Object { $_ -notmatch '(?i)disabled from the overlay' })
|
|
if ($switchedOff) {
|
|
Report -Status 'Fail' -Text ($Label + ': the add-on is switched off (enabled=0), so nothing is fed to the neural consumer.') `
|
|
-Action 'In the game: ReShade overlay > Add-ons > DLSS 5 Feed > tick Enabled (or set enabled=1 in dlss5-feed.cfg beside the add-on), then restart the game.'
|
|
}
|
|
$bad = @($bad | Select-Object -Last 5)
|
|
|
|
if ($bad.Count -gt 0) {
|
|
foreach ($b in $bad) {
|
|
$txt = Strip-Prefix $b
|
|
if ($txt.Length -gt 160) { $txt = $txt.Substring(0, 157) + '...' }
|
|
if ($b -match '(?i)(different releases|TOO OLD)') {
|
|
Report -Status 'Fail' -Text ($Label + ': ' + $txt) `
|
|
-Action 'Redeploy both halves from the same release / fix the flagged file, then re-run the game once.'
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text ($Label + ': ' + $txt)
|
|
}
|
|
}
|
|
}
|
|
elseif (-not $switchedOff) {
|
|
Report -Status 'Ok' -Text ($Label + ': no warnings or disable reasons in the log.')
|
|
}
|
|
}
|
|
|
|
$anyLog = $false
|
|
|
|
if ($gameBits -eq 32) { $gameHalf = 'game' } else { $gameHalf = '' }
|
|
|
|
$feedLog = Find-FileIn $gameDir 'dlss5-feed.log'
|
|
if ($feedLog) { $anyLog = $true; Report-FeedLog -Path $feedLog -Label 'dlss5-feed.log' -Half $gameHalf }
|
|
|
|
if ($gameBits -eq 32 -and (Test-DirHere $hostDir)) {
|
|
$hostLog = Find-FileIn $hostDir 'dlss5-feed-host.log'
|
|
if ($hostLog) { $anyLog = $true; Report-FeedLog -Path $hostLog -Label 'host64\dlss5-feed-host.log' -Half 'host' }
|
|
}
|
|
|
|
if (-not $anyLog) {
|
|
Report -Status 'Na' -Text 'No feeder log yet -- launch the game once and re-run this script for a runtime verdict.'
|
|
}
|
|
|
|
$dfcLog = Find-FileIn $consumerDir 'deep-fried-chicken.log'
|
|
# A log left behind by a consumer that is no longer installed says nothing about this
|
|
# install, and reading it out as [ OK ] is worse than saying nothing: switching consumers
|
|
# leaves the old log in place, so the folder would report two of them working at once.
|
|
if ($dfcLog -and -not $dfcAddon) {
|
|
Report -Status 'Na' -Text 'deep-fried-chicken.log is here but Deep Fried Chicken is not installed any more -- the log is from an earlier run and is not read.'
|
|
}
|
|
elseif ($dfcLog) {
|
|
$lines = Read-LinesSafe $dfcLog
|
|
if ($null -eq $lines) {
|
|
Report -Status 'Warn' -Text 'deep-fried-chicken.log exists but could not be read.' -Detail $dfcLog
|
|
}
|
|
else {
|
|
$marker = @($lines | Where-Object { $_ -match 'feeder_marker=' }) | Select-Object -Last 1
|
|
if ($marker -and $marker -match 'feeder_marker=1') {
|
|
$extra = ''
|
|
if ($marker -match 'legacy_exact=(\d)') { $extra = 'legacy_exact=' + $Matches[1] }
|
|
Report -Status 'Ok' -Text 'Chicken accepted the interop marker (feeder_marker=1).' -Detail $extra
|
|
}
|
|
elseif ($marker) {
|
|
Report -Status 'Fail' -Text 'Chicken did NOT accept the interop marker (feeder_marker=0).' `
|
|
-Detail ($marker.Trim() + "`nChicken either never saw our DFC.Feeder.* parameters, or fell back to recognising us by file name.") `
|
|
-Action 'Make sure the add-on and Chicken are both recent enough to speak interop ABI 1 (feeder 0.10.0+, Chicken 1.4.0-alpha+).'
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'deep-fried-chicken.log has no feeder_marker= line yet.' `
|
|
-Detail 'That line only appears once a native DLSS create goes through. Run the game long enough to reach gameplay and re-check.'
|
|
}
|
|
|
|
$state = @($lines | Where-Object { $_ -match 'interop_state=' }) | Select-Object -Last 1
|
|
if ($state -and $state -match 'interop_state=(\w+)') {
|
|
$s = $Matches[1]
|
|
if ($s -match '(?i)^(ARMED|CLAIMING)$') { Report -Status 'Ok' -Text ('Chicken interop_state=' + $s + '.') }
|
|
else { Report -Status 'Warn' -Text ('Chicken interop_state=' + $s + ' -- not armed.') }
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'deep-fried-chicken.log has no interop_state= line.'
|
|
}
|
|
}
|
|
}
|
|
elseif ($dfcAddon) {
|
|
Report -Status 'Na' -Text 'No deep-fried-chicken.log yet -- Chicken has not run here.'
|
|
}
|
|
|
|
if ($optiDll) {
|
|
# Two lines in the feeder's own logs decide it: the probe fingerprint ("routed through
|
|
# OptiScaler") and the module check after the first evaluate ("neural model (feature 18)
|
|
# loaded"). OptiScaler.log beside the DLL then names the upscaler and the pass.
|
|
$ownLogs = @()
|
|
if ($feedLog) { $ownLogs += $feedLog }
|
|
if ($gameBits -eq 32) {
|
|
$hl = Find-FileIn $hostDir 'dlss5-feed-host.log'
|
|
if ($hl) { $ownLogs += $hl }
|
|
}
|
|
$routed = $null
|
|
$notRouted = $null
|
|
$model = $null
|
|
foreach ($lp in $ownLogs) {
|
|
$ls = Read-LinesSafe $lp
|
|
if ($null -eq $ls) { continue }
|
|
$r = @($ls | Where-Object { $_ -match 'routed through OptiScaler' }) | Select-Object -Last 1
|
|
if ($r) { $routed = $r }
|
|
$nr = @($ls | Where-Object { $_ -match 'DRIVER answered the NGX probe' }) | Select-Object -Last 1
|
|
if ($nr) { $notRouted = $nr }
|
|
$m = @($ls | Where-Object { $_ -match 'neural model \(feature 18\) (NOT )?loaded' }) | Select-Object -Last 1
|
|
if ($m) { $model = $m }
|
|
}
|
|
if ($routed) {
|
|
Report -Status 'Ok' -Text 'The feeder''s NGX calls were routed through OptiScaler (probe fingerprint).'
|
|
}
|
|
elseif ($notRouted) {
|
|
Report -Status 'Fail' -Text 'OptiScaler is loaded but the DRIVER answered the feeder''s NGX probe.' `
|
|
-Detail 'OptiScaler''s nvngx redirect did not take. See [Inputs] EnableDlssInputs / [Hooks] HookOriginalNvngxOnly above, and OptiScaler.log for "returning this dll!".'
|
|
}
|
|
elseif ($ownLogs.Count -gt 0) {
|
|
Report -Status 'Warn' -Text 'The feeder log has no OptiScaler routing line yet.' `
|
|
-Detail 'It is written when the NGX session opens. Run the game to gameplay and re-check.'
|
|
}
|
|
if ($model) {
|
|
if ($model -match 'NOT loaded') {
|
|
Report -Status 'Fail' -Text 'The neural model (feature 18) was never created inside OptiScaler.' `
|
|
-Detail (($model -replace '^\s*[\d:.]+\s+', '').Trim()) `
|
|
-Action 'OptiScaler.log says why. Check [DlssNr] Enabled=true, and nvngx_dlssnr.dll plus nvngx.dll_dlssnr.dll beside OptiScaler.'
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text 'The neural model (feature 18) was created inside OptiScaler.'
|
|
}
|
|
}
|
|
$optiLog = Find-FileIn (Split-Path -Parent $optiDll) 'OptiScaler.log'
|
|
if ($optiLog) {
|
|
$ol = Read-LinesSafe $optiLog
|
|
if ($null -ne $ol) {
|
|
$strip = '^\[[^\]]*\]\s*\[\w\]\s*'
|
|
$run = @($ol | Where-Object { $_ -match 'DLSS-NR running at' }) | Select-Object -Last 1
|
|
$bad = @($ol | Where-Object { $_ -match 'DLSS-NR create failed|nvngx\.dll_dlssnr\.dll not found|DLSS-NR did not run|DLSS-NR unavailable' }) | Select-Object -Last 1
|
|
$noDl = @($ol | Where-Object { $_ -match 'nvngx_dlss\.dll not found, disabling DLSS' }) | Select-Object -Last 1
|
|
$ups = @($ol | Where-Object { $_ -match 'Creating \S+ upscaler feature|Creating XeSS|Creating FSR' }) | Select-Object -Last 1
|
|
if ($run) { Report -Status 'Ok' -Text ('OptiScaler.log: ' + ($run -replace $strip, '').Trim()) }
|
|
if ($bad) { Report -Status 'Fail' -Text ('OptiScaler.log: ' + ($bad -replace $strip, '').Trim()) }
|
|
if ($noDl) {
|
|
Report -Status 'Warn' -Text 'OptiScaler.log: nvngx_dlss.dll was not found, so OptiScaler disabled its DLSS side.' `
|
|
-Detail 'It builds FSR 2.1.2 in place of DLSS and still reports success. Put nvngx_dlss.dll beside OptiScaler.'
|
|
}
|
|
if ($ups) { Report -Status 'Na' -Text ('OptiScaler.log: ' + ($ups -replace $strip, '').Trim()) }
|
|
if (-not $run -and -not $bad) {
|
|
Report -Status 'Warn' -Text 'OptiScaler.log has no DLSS-NR line yet.' `
|
|
-Detail 'The pass logs "DLSS-NR running at WxH" on its first frame. Run the game to gameplay and re-check; the log tail can also be cut short when the helper exits.'
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Na' -Text 'No OptiScaler.log yet beside OptiScaler ([Log] LogToFile=true writes one).'
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# 9. Motion vectors
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Section 'Motion vectors'
|
|
|
|
$providerNames = @{
|
|
'0' = 'texMotionVectors (shared: DRME, qUINT, dh_uber_motion, ...)'
|
|
'1' = 'iMMERSE Launchpad (Deferred::MotionVectorsTex)'
|
|
'2' = 'VORT (MotVectTexVort)'
|
|
'3' = 'LumeniteFX Kernel (Kernel::tFlow)'
|
|
'4' = 'LumeniteFX QuantMotion (QuantMotion::tFlow)'
|
|
}
|
|
$providerFx = @{
|
|
'1' = 'MartysMods_LAUNCHPAD.fx'
|
|
'2' = 'vort_Motion.fx'
|
|
'3' = 'lumenite_Kernel.fx'
|
|
'4' = 'lumenite_QuantMotion.fx'
|
|
}
|
|
|
|
# Which preset is actually in use: ReShade.ini's [GENERAL] PresetPath names it, and it is
|
|
# not required to be called ReShadePreset.ini or to sit next to the game (issue #50).
|
|
$preset = $null
|
|
if ($reshadeIni) {
|
|
$presetPath = Get-IniValue $reshadeIni 'GENERAL' 'PresetPath'
|
|
if ($presetPath) {
|
|
$presetPath = $presetPath.Trim()
|
|
if (-not [System.IO.Path]::IsPathRooted($presetPath)) {
|
|
$presetPath = Join-Path (Split-Path -Parent $reshadeIni) $presetPath
|
|
}
|
|
if (Test-FileHere $presetPath) { $preset = (Resolve-Path -LiteralPath $presetPath).Path }
|
|
}
|
|
}
|
|
if (-not $preset) { $preset = Find-FileIn $gameDir 'ReShadePreset.ini' }
|
|
if (-not $preset) {
|
|
Report -Status 'Fail' -Text 'ReShadePreset.ini is missing.' `
|
|
-Detail 'The motion-vector provider is stored there, per effect. Without it DLSS5_Feed.fx falls back to provider 0 and no technique is enabled.' `
|
|
-Action 'Copy the project''s ReShadePreset.ini template into the game folder.'
|
|
}
|
|
else {
|
|
$lines = Read-LinesSafe $preset
|
|
if ($null -eq $lines) {
|
|
Report -Status 'Warn' -Text 'ReShadePreset.ini could not be read.' -Detail $preset
|
|
}
|
|
else {
|
|
$techniques = Get-IniValue $preset '' 'Techniques'
|
|
|
|
# ReShade assembles an effect's preprocessor definitions from THREE levels, in
|
|
# runtime.cpp's load_effect: ReShade.ini's [GENERAL] PreprocessorDefinitions is the
|
|
# base list, the preset's ROOT (section-less) PreprocessorDefinitions applies to
|
|
# every effect, and a per-effect [DLSS5_Feed.fx] section overrides both. This check
|
|
# used to read only the third, call a missing section a FAILURE and call the other
|
|
# two levels mistakes -- so it declared "this install will not work as it stands" on
|
|
# installs whose own add-on log read "-> Lumenite_Kernel (enabled)" (issue #50).
|
|
# Most specific first; all three are valid places to set it.
|
|
$mvLevels = @(
|
|
@{ Where = 'the [DLSS5_Feed.fx] section of the preset'
|
|
Value = (Get-PreprocessorDefinition (Get-IniValue $preset 'DLSS5_Feed.fx' 'PreprocessorDefinitions') 'DLSS5_MV_PROVIDER') },
|
|
@{ Where = "the preset's root PreprocessorDefinitions (applies to every effect)"
|
|
Value = (Get-PreprocessorDefinition (Get-IniValue $preset '' 'PreprocessorDefinitions') 'DLSS5_MV_PROVIDER') },
|
|
@{ Where = "ReShade.ini's [GENERAL] PreprocessorDefinitions (the base list)"
|
|
Value = $(if ($reshadeIni) { Get-PreprocessorDefinition (Get-IniValue $reshadeIni 'GENERAL' 'PreprocessorDefinitions') 'DLSS5_MV_PROVIDER' } else { $null }) }
|
|
)
|
|
$set = @($mvLevels | Where-Object { $_.Value })
|
|
$provider = $null
|
|
$mvWhere = $null
|
|
if ($set.Count -gt 0) { $provider = $set[0].Value; $mvWhere = $set[0].Where }
|
|
|
|
# The add-on's own log is better evidence than any of the three: it reports what
|
|
# ReShade actually resolved at compile time, after all the overriding is done.
|
|
if ($script:FeedLogProvider -and $script:FeedLogProvider -ne $provider) {
|
|
Report -Status 'Na' -Text ('dlss5-feed.log resolved DLSS5_MV_PROVIDER=' + $script:FeedLogProvider + ' at compile time.') `
|
|
-Detail 'That is what the effect was actually built with in the last run. Where it disagrees with the ini files, believe the log.'
|
|
$provider = $script:FeedLogProvider
|
|
$mvWhere = 'the add-on''s own log line from the last run'
|
|
}
|
|
|
|
if ($provider) {
|
|
if ($providerNames.ContainsKey($provider)) { $pn = $providerNames[$provider] } else { $pn = 'unknown provider id' }
|
|
Report -Status 'Ok' -Text ('DLSS5_MV_PROVIDER=' + $provider + ' -- ' + $pn) -Detail ('Read from ' + $mvWhere + '.')
|
|
# More than one level carrying a value is legal, and the most specific wins --
|
|
# but it is worth naming, because editing the losing one changes nothing.
|
|
if ($set.Count -gt 1) {
|
|
$shadowed = @($set | Select-Object -Skip 1 | ForEach-Object { $_.Where + ' = ' + $_.Value }) -join '; '
|
|
Report -Status 'Na' -Text 'DLSS5_MV_PROVIDER is set at more than one level.' `
|
|
-Detail ('The most specific wins, so ' + $mvWhere + ' is the one in force. Also set, and overridden: ' + $shadowed)
|
|
}
|
|
}
|
|
else {
|
|
Report -Status 'Warn' -Text 'DLSS5_MV_PROVIDER is not set anywhere -- the effect defaults to provider 0.' `
|
|
-Detail 'Checked the [DLSS5_Feed.fx] section of the preset, the preset''s root PreprocessorDefinitions and ReShade.ini''s [GENERAL] PreprocessorDefinitions; any of the three is valid. Provider 0 reads the shared texMotionVectors texture, so it only works if another effect (DRME, qUINT, dh_uber_motion) writes it. The recommended setting is DLSS5_MV_PROVIDER=3 (LumeniteFX Kernel).'
|
|
$provider = '0'
|
|
}
|
|
|
|
# Techniques= line: is DLSS5_Feed on, and is the provider's own effect on too?
|
|
if (-not $techniques) {
|
|
Report -Status 'Fail' -Text 'ReShadePreset.ini has no Techniques= line -- no effect is enabled.' `
|
|
-Action 'Enable DLSS5_Feed (and the motion-vector provider effect) in the ReShade overlay.'
|
|
}
|
|
else {
|
|
$techList = @($techniques -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
|
$feedIdx = -1
|
|
for ($i = 0; $i -lt $techList.Count; $i++) { if ($techList[$i] -match '(?i)DLSS5_Feed\.fx$') { $feedIdx = $i; break } }
|
|
|
|
if ($feedIdx -lt 0) {
|
|
Report -Status 'Fail' -Text 'DLSS5_Feed is not in the enabled Techniques= list.' `
|
|
-Detail ('Techniques=' + $techniques) `
|
|
-Action 'Tick "DLSS 5 Feed" in the ReShade overlay (Home key) and it will be saved into the preset.'
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text ('DLSS5_Feed is enabled (position ' + ($feedIdx + 1) + ' of ' + $techList.Count + ' in Techniques=).')
|
|
}
|
|
|
|
if ($provider -and $providerFx.ContainsKey($provider)) {
|
|
$wantFx = $providerFx[$provider]
|
|
$provIdx = -1
|
|
for ($i = 0; $i -lt $techList.Count; $i++) { if ($techList[$i] -match ('(?i)' + [regex]::Escape($wantFx) + '$')) { $provIdx = $i; break } }
|
|
|
|
if ($provIdx -lt 0) {
|
|
Report -Status 'Fail' -Text ('Provider ' + $provider + ' is selected but no technique from ' + $wantFx + ' is enabled.') `
|
|
-Detail ('Techniques=' + $techniques + "`nThe provider effect must run, and must run BEFORE DLSS5_Feed, or the feeder gets an empty motion-vector texture.") `
|
|
-Action ('Enable the ' + $wantFx + ' technique in the ReShade overlay, above DLSS 5 Feed.')
|
|
}
|
|
elseif ($feedIdx -ge 0 -and $provIdx -gt $feedIdx) {
|
|
Report -Status 'Fail' -Text ('The ' + $wantFx + ' technique runs AFTER DLSS5_Feed.') `
|
|
-Detail 'DLSS5_Feed would read last frame''s (or an empty) motion-vector texture.' `
|
|
-Action ('Drag ' + $wantFx + ' above DLSS 5 Feed in the ReShade technique list.')
|
|
}
|
|
else {
|
|
Report -Status 'Ok' -Text ($wantFx + ' is enabled and runs before DLSS5_Feed.')
|
|
}
|
|
}
|
|
elseif ($provider -eq '0') {
|
|
$writers = @('MotionEstimation.fx', 'qUINT_motionvectors.fx', 'dh_uber_motion.fx', 'vort_Motion.fx')
|
|
$found = @()
|
|
foreach ($w in $writers) { foreach ($t in $techList) { if ($t -match ('(?i)' + [regex]::Escape($w) + '$')) { $found += $w } } }
|
|
if ($found.Count -gt 0) {
|
|
Report -Status 'Ok' -Text ('Provider 0: a texMotionVectors writer is enabled (' + (($found | Select-Object -Unique) -join ', ') + ').')
|
|
}
|
|
else {
|
|
Report -Status 'Fail' -Text 'Provider 0 is selected but no known texMotionVectors writer is enabled.' `
|
|
-Detail ('Techniques=' + $techniques + "`nProvider 0 only reads the shared texture -- something has to fill it.") `
|
|
-Action 'Enable a motion-vector effect (DRME/MotionEstimation.fx, qUINT, dh_uber_motion), or switch to DLSS5_MV_PROVIDER=3 with lumenite_Kernel.fx.'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Summary
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
Write-Host ''
|
|
Write-Chunk (' ' + ([string][char]0x2500) * 68) 'Green'
|
|
|
|
Write-Chunk ' ' $null -NoNewline
|
|
Write-Chunk ($script:CountOk.ToString() + ' OK') 'Green' -NoNewline
|
|
Write-Chunk ' ' $null -NoNewline
|
|
Write-Chunk ($script:CountWarn.ToString() + ' warning' + $(if ($script:CountWarn -eq 1) { '' } else { 's' })) 'Yellow' -NoNewline
|
|
Write-Chunk ' ' $null -NoNewline
|
|
Write-Chunk ($script:CountFail.ToString() + ' failure' + $(if ($script:CountFail -eq 1) { '' } else { 's' })) 'Red'
|
|
|
|
if ($script:CountFail -gt 0) {
|
|
Write-Chunk ' Verdict: this install will not work as it stands.' 'Red'
|
|
Write-Host ''
|
|
Write-Chunk ' Do this:' 'White'
|
|
$i = 1
|
|
foreach ($a in $script:Actions) {
|
|
$first = $true
|
|
foreach ($line in ($a -split "`n")) {
|
|
if (-not $line.Trim()) { continue }
|
|
if ($first) { Write-Chunk (' ' + $i + '. ' + $line.Trim()) 'DarkYellow'; $first = $false }
|
|
else { Write-Chunk (' ' + $line.Trim()) 'DarkYellow' }
|
|
}
|
|
$i++
|
|
}
|
|
}
|
|
elseif ($script:CountWarn -gt 0) {
|
|
Write-Chunk ' Verdict: the install looks complete, but read the warnings above.' 'Yellow'
|
|
}
|
|
else {
|
|
Write-Chunk ' Verdict: everything checks out.' 'Green'
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
if ($script:CountFail -gt 0) { Exit-Verifier 1 }
|
|
Exit-Verifier 0
|