docs(script): trace host callback
This commit is contained in:
@@ -240,6 +240,18 @@ defaults. Runtime exposes `resolve_loaded_handler30` for the exact opaque
|
|||||||
command without invoking a guessed game-side consumer: the tenth
|
command without invoking a guessed game-side consumer: the tenth
|
||||||
`CreateSuperAI` callback argument still needs its own recovery.
|
`CreateSuperAI` callback argument still needs its own recovery.
|
||||||
|
|
||||||
|
The live GOG AutoDemo closes that consumer boundary: the read-only callback
|
||||||
|
pointer at `ai.dll + 0x555e4` is `0x100611d0`, or `iron3d.dll + 0x611d0` at
|
||||||
|
the observed load base. Its recovered `__cdecl` ABI is `(mode, command,
|
||||||
|
payload)`, matching `Handler(30)` as `(0, first, second)`. In `mode == 0`,
|
||||||
|
`command == 0` and `payload == 0` selects `VOICE_MISSION_FAIL`, records the
|
||||||
|
failed status, and clears an IGame byte; `payload == 1` selects
|
||||||
|
`VOICE_MISSION_COMPLETE`, records completion, and sets that byte. Commands
|
||||||
|
1, 3, 4, and 5 have additional game-side paths; mode 2 is a separate IGame
|
||||||
|
call. Those branches are not yet assigned Rust gameplay meanings. Reproduce
|
||||||
|
the current evidence with `capture-ai-init.ps1` and
|
||||||
|
`ExportIron3dAiCallback.java`.
|
||||||
|
|
||||||
### Handler(8): problem-record state write
|
### Handler(8): problem-record state write
|
||||||
|
|
||||||
`Handler(8)` is the ninth VM-table entry at GOG `ai.dll` VA `0x10009b0d`.
|
`Handler(8)` is the ninth VM-table entry at GOG `ai.dll` VA `0x10009b0d`.
|
||||||
|
|||||||
@@ -52,12 +52,18 @@ $snapshot = [FparkanAiInitCapture]::CreateToolhelp32Snapshot(
|
|||||||
[FparkanAiInitCapture]::TH32CS_SNAPMODULE -bor [FparkanAiInitCapture]::TH32CS_SNAPMODULE32,
|
[FparkanAiInitCapture]::TH32CS_SNAPMODULE -bor [FparkanAiInitCapture]::TH32CS_SNAPMODULE32,
|
||||||
[uint32]$ProcessId)
|
[uint32]$ProcessId)
|
||||||
$aiBase = $null
|
$aiBase = $null
|
||||||
|
$modules = @()
|
||||||
try {
|
try {
|
||||||
$entry = [FparkanAiInitCapture+MODULEENTRY32]::new()
|
$entry = [FparkanAiInitCapture+MODULEENTRY32]::new()
|
||||||
$entry.dwSize = [Runtime.InteropServices.Marshal]::SizeOf([type][FparkanAiInitCapture+MODULEENTRY32])
|
$entry.dwSize = [Runtime.InteropServices.Marshal]::SizeOf([type][FparkanAiInitCapture+MODULEENTRY32])
|
||||||
if ([FparkanAiInitCapture]::Module32First($snapshot, [ref]$entry)) {
|
if ([FparkanAiInitCapture]::Module32First($snapshot, [ref]$entry)) {
|
||||||
do {
|
do {
|
||||||
if ($entry.szModule -ieq 'ai.dll') { $aiBase = $entry.modBaseAddr.ToInt64(); break }
|
$modules += [ordered]@{
|
||||||
|
name = $entry.szModule
|
||||||
|
base = $entry.modBaseAddr.ToInt64()
|
||||||
|
size = [int64]$entry.modBaseSize
|
||||||
|
}
|
||||||
|
if ($entry.szModule -ieq 'ai.dll') { $aiBase = $entry.modBaseAddr.ToInt64() }
|
||||||
$entry = [FparkanAiInitCapture+MODULEENTRY32]::new()
|
$entry = [FparkanAiInitCapture+MODULEENTRY32]::new()
|
||||||
$entry.dwSize = [Runtime.InteropServices.Marshal]::SizeOf([type][FparkanAiInitCapture+MODULEENTRY32])
|
$entry.dwSize = [Runtime.InteropServices.Marshal]::SizeOf([type][FparkanAiInitCapture+MODULEENTRY32])
|
||||||
} while ([FparkanAiInitCapture]::Module32Next($snapshot, [ref]$entry))
|
} while ([FparkanAiInitCapture]::Module32Next($snapshot, [ref]$entry))
|
||||||
@@ -70,6 +76,12 @@ $process = [FparkanAiInitCapture]::OpenProcess(
|
|||||||
$false, [uint32]$ProcessId)
|
$false, [uint32]$ProcessId)
|
||||||
if ($process -eq [IntPtr]::Zero) { throw "OpenProcess read-only failed" }
|
if ($process -eq [IntPtr]::Zero) { throw "OpenProcess read-only failed" }
|
||||||
try {
|
try {
|
||||||
|
# CreateSuperAI stores its tenth host-callback argument at DAT_100555e4.
|
||||||
|
$callbackBytes = Read-Bytes $process ($aiBase + 0x555e4) 4
|
||||||
|
$callback = [BitConverter]::ToUInt32($callbackBytes, 0)
|
||||||
|
$callbackModule = $modules | Where-Object {
|
||||||
|
$callback -ge $_.base -and [int64]$callback -lt ($_.base + $_.size)
|
||||||
|
} | Select-Object -First 1
|
||||||
# GetSuperAI(i) returns (&DAT_10055398)[i], with ai.dll preferred base 0x10000000.
|
# GetSuperAI(i) returns (&DAT_10055398)[i], with ai.dll preferred base 0x10000000.
|
||||||
$entries = Read-Bytes $process ($aiBase + 0x55398) (64 * 4)
|
$entries = Read-Bytes $process ($aiBase + 0x55398) (64 * 4)
|
||||||
$samples = for ($index = 0; $index -lt 64; $index++) {
|
$samples = for ($index = 0; $index -lt 64; $index++) {
|
||||||
@@ -86,6 +98,14 @@ try {
|
|||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
[ordered]@{ schema = 'fparkan-ai-init-v1'; process_id = $ProcessId; ai_module_base = ('0x{0:X8}' -f $aiBase); entries = @($samples) } |
|
[ordered]@{
|
||||||
|
schema = 'fparkan-ai-init-v1'
|
||||||
|
process_id = $ProcessId
|
||||||
|
ai_module_base = ('0x{0:X8}' -f $aiBase)
|
||||||
|
handler30_callback = ('0x{0:X8}' -f $callback)
|
||||||
|
handler30_callback_module = if ($null -eq $callbackModule) { $null } else { $callbackModule.name }
|
||||||
|
handler30_callback_rva = if ($null -eq $callbackModule) { $null } else { ('0x{0:X}' -f ([int64]$callback - $callbackModule.base)) }
|
||||||
|
entries = @($samples)
|
||||||
|
} |
|
||||||
ConvertTo-Json -Depth 4 -Compress
|
ConvertTo-Json -Depth 4 -Compress
|
||||||
} finally { [void][FparkanAiInitCapture]::CloseHandle($process) }
|
} finally { [void][FparkanAiInitCapture]::CloseHandle($process) }
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Emits the live CreateSuperAI host callback selected by the GOG AutoDemo.
|
||||||
|
// Run headless; the original PE remains read only.
|
||||||
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
|
||||||
|
public class ExportIron3dAiCallback extends GhidraScript {
|
||||||
|
private static final long ADDRESS = 0x100611d0L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
Address address = currentProgram.getAddressFactory().getDefaultAddressSpace()
|
||||||
|
.getAddress(ADDRESS);
|
||||||
|
Function function = currentProgram.getFunctionManager().getFunctionAt(address);
|
||||||
|
println("===== Iron3D CreateSuperAI callback =====");
|
||||||
|
if (function == null) { println("missing"); return; }
|
||||||
|
DecompInterface decompiler = new DecompInterface();
|
||||||
|
decompiler.openProgram(currentProgram);
|
||||||
|
println(decompiler.decompileFunction(function, 120, monitor).getDecompiledFunction().getC());
|
||||||
|
decompiler.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user