# compat-inventory.ps1 - Dauntless 1.14.7 compatibility inventory # # WHY: the NetMode defect proved that a CORRECT RVA can still be wrong - the hook returned 'char' where the # native caller read all of EAX, so the live value was 0xEE47C001 instead of 1. An address alone is not # evidence. This enumerates every version-dependent assumption the runtime makes so each can be checked # against the 1.14.7 SDK and executable instead of trusted. # # Statuses: VERIFIED = address appears in the project's verified 1.14.7 RVA map; CANDIDATE = plausible but # unchecked for meaning and ABI; UNKNOWN = no evidence yet. This tool never promotes an entry itself. # Re-runnable: reads source and docs only, never touches a running process. param( # Repository root: two levels above this script (ParadoxRuntime\tools). [string]$Root = (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)), [string]$Out = 'Progress\37_DAUNTLESS_1_14_7_COMPATIBILITY_INVENTORY.md' ) $bt = [char]96 $srcDir = Join-Path $Root 'ParadoxRuntime' # The optional player-hosted module (p2p\*.cpp) and its anti-cheat module (anticheat\*.cpp, the Hunt Guard evidence # producer since 9 October) hold RVAs too when they are present. $files = @((Join-Path $srcDir 'dllmain.cpp'), (Join-Path $srcDir 'Networking.cpp')) + @(Get-ChildItem -LiteralPath (Join-Path $srcDir 'p2p') -Filter *.cpp -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }) + @(Get-ChildItem -LiteralPath (Join-Path $srcDir 'anticheat') -Filter *.cpp -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }) | Where-Object { Test-Path -LiteralPath $_ } $verified = @{} $mapPath = Join-Path $Root 'Progress\36_DAUNTLESS_1_14_7_ENDPOINT_AND_RVA_MAP.md' if (Test-Path -LiteralPath $mapPath) { foreach ($line in Get-Content -LiteralPath $mapPath) { foreach ($m in [regex]::Matches($line, '0x([0-9A-Fa-f]{6,8})')) { $verified[$m.Groups[1].Value.ToUpper()] = $true } } } $hooks = New-Object System.Collections.Generic.List[object] $rvas = New-Object System.Collections.Generic.List[object] $vt = New-Object System.Collections.Generic.List[object] $offs = New-Object System.Collections.Generic.List[object] foreach ($file in $files) { $name = Split-Path $file -Leaf $lines = Get-Content -LiteralPath $file for ($i = 0; $i -lt $lines.Count; $i++) { $text = $lines[$i]; $ln = $i + 1 if ($text -match 'MH_CreateHook') { $a = [regex]::Match($text, 'BaseAddress \+ 0x([0-9A-Fa-f]+)') if ($a.Success) { $rva = $a.Groups[1].Value.ToUpper() $fn = [regex]::Match($text, ',\s*&?([A-Za-z_][A-Za-z0-9_]*)') $hooks.Add([pscustomobject]@{ Rva=$rva; Hook=$fn.Groups[1].Value; File=$name; Line=$ln; Status=$(if ($verified[$rva]) {'VERIFIED'} else {'CANDIDATE'}) }) } } foreach ($a in [regex]::Matches($text, 'BaseAddress \+ 0x([0-9A-Fa-f]{4,8})')) { if ($text -notmatch 'MH_CreateHook') { $rva = $a.Groups[1].Value.ToUpper() $rvas.Add([pscustomobject]@{ Rva=$rva; File=$name; Line=$ln; Context=$text.Trim().Substring(0,[Math]::Min(110,$text.Trim().Length)); Status=$(if ($verified[$rva]) {'VERIFIED'} else {'CANDIDATE'}) }) } } foreach ($v in [regex]::Matches($text, '\(\*reinterpret_cast\(([^)]+)\)\)\[([^\]]+)\]')) { $vt.Add([pscustomobject]@{ On=$v.Groups[1].Value; Slot=$v.Groups[2].Value; File=$name; Line=$ln; Context=$text.Trim().Substring(0,[Math]::Min(110,$text.Trim().Length)) }) } foreach ($o in [regex]::Matches($text, '([A-Za-z_][A-Za-z0-9_]*)\s*\+\s*0x([0-9A-Fa-f]{1,3})\b')) { $offs.Add([pscustomobject]@{ Owner=$o.Groups[1].Value; Offset=('0x'+$o.Groups[2].Value.ToUpper()); File=$name; Line=$ln }) } } } $sb = New-Object System.Text.StringBuilder [void]$sb.AppendLine('# Dauntless 1.14.7 - runtime compatibility inventory') [void]$sb.AppendLine('') [void]$sb.AppendLine('Generated by the compat-inventory tool. Re-run after any runtime change; it reads source and docs') [void]$sb.AppendLine('only and never touches a running process.') [void]$sb.AppendLine('') [void]$sb.AppendLine('Statuses: **VERIFIED** = address appears in the verified 1.14.7 RVA map; **CANDIDATE** = plausible') [void]$sb.AppendLine('but unchecked for meaning and ABI; **UNKNOWN** = no evidence yet. A CANDIDATE is not a defect - it is') [void]$sb.AppendLine('simply not yet proven, and the NetMode bug is why that distinction matters: its RVA was right while') [void]$sb.AppendLine('its return type was wrong.') [void]$sb.AppendLine('') [void]$sb.AppendLine('## Summary') [void]$sb.AppendLine('') [void]$sb.AppendLine('| Category | Count | VERIFIED | CANDIDATE |') [void]$sb.AppendLine('|---|---|---|---|') $hV = @($hooks | Where-Object { $_.Status -eq 'VERIFIED' }).Count $rV = @($rvas | Where-Object { $_.Status -eq 'VERIFIED' }).Count [void]$sb.AppendLine('| Hook installations | ' + $hooks.Count + ' | ' + $hV + ' | ' + ($hooks.Count - $hV) + ' |') [void]$sb.AppendLine('| Direct RVA references | ' + $rvas.Count + ' | ' + $rV + ' | ' + ($rvas.Count - $rV) + ' |') [void]$sb.AppendLine('| Vtable calls | ' + $vt.Count + ' | - | - |') [void]$sb.AppendLine('| Raw field offsets | ' + $offs.Count + ' | - | - |') [void]$sb.AppendLine('') [void]$sb.AppendLine('## A. Hook installations') [void]$sb.AppendLine('') [void]$sb.AppendLine('Each row needs: address, ACTUAL entry point (PE unwind metadata, following chained entries),') [void]$sb.AppendLine('arguments and RETURN TYPE. The return type is what the NetMode defect failed.') [void]$sb.AppendLine('') [void]$sb.AppendLine('| RVA | Hook | Status | Location |') [void]$sb.AppendLine('|---|---|---|---|') foreach ($x in ($hooks | Sort-Object Rva -Unique)) { [void]$sb.AppendLine('| ' + $bt + '0x' + $x.Rva + $bt + ' | ' + $bt + $x.Hook + $bt + ' | ' + $x.Status + ' | ' + $x.File + ':' + $x.Line + ' |') } [void]$sb.AppendLine('') [void]$sb.AppendLine('## B. Direct RVA references (calls, reads, globals)') [void]$sb.AppendLine('') [void]$sb.AppendLine('| RVA | Status | Location | Context |') [void]$sb.AppendLine('|---|---|---|---|') foreach ($x in ($rvas | Sort-Object Rva -Unique)) { [void]$sb.AppendLine('| ' + $bt + '0x' + $x.Rva + $bt + ' | ' + $x.Status + ' | ' + $x.File + ':' + $x.Line + ' | ' + $bt + ($x.Context -replace '\|','\\|') + $bt + ' |') } [void]$sb.AppendLine('') [void]$sb.AppendLine('## C. Vtable calls') [void]$sb.AppendLine('') [void]$sb.AppendLine('| Object | Slot | Location | Context |') [void]$sb.AppendLine('|---|---|---|---|') foreach ($x in $vt) { [void]$sb.AppendLine('| ' + $bt + $x.On + $bt + ' | ' + $x.Slot + ' | ' + $x.File + ':' + $x.Line + ' | ' + $bt + $x.Context + $bt + ' |') } [void]$sb.AppendLine('') [void]$sb.AppendLine('## D. Raw field offsets (distinct owner+offset pairs)') [void]$sb.AppendLine('') [void]$sb.AppendLine('| Owner expression | Offset | First seen |') [void]$sb.AppendLine('|---|---|---|') foreach ($x in ($offs | Sort-Object Owner, Offset -Unique)) { [void]$sb.AppendLine('| ' + $bt + $x.Owner + $bt + ' | ' + $bt + $x.Offset + $bt + ' | ' + $x.File + ':' + $x.Line + ' |') } $outPath = Join-Path $Root $Out Set-Content -LiteralPath $outPath -Value $sb.ToString() -Encoding UTF8 Write-Output ('inventory written: ' + $outPath) Write-Output (' hooks=' + $hooks.Count + ' rvas=' + $rvas.Count + ' vtable=' + $vt.Count + ' offsets=' + $offs.Count)