ParadoxRuntime/tools/compat-inventory.ps1
Rama main · réplica de lectura del repositorio original.
# 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<void\*\*\*>\(([^)]+)\)\)\[([^\]]+)\]')) {
$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)