tools: function names for the Ghidra project, Hold-Key test helper
- tools/names.txt lists identified sacred.exe functions ("0xADDR
Class::Method"); rename.ps1 / Rename.java apply them to the persistent
Ghidra project, so decompiles read with real names.
- decompile.ps1: parameters other than the addresses are named-only (the
first address was taken as -Out).
- game-input.ps1: Hold-Key for polled keys such as movement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
09f7dcc0f4
commit
52db0f79f9
6 files changed
+182
-1
No files matched your search
+2
-1
@@ -10,6 +10,7 @@ passed explicitly.
|
|||||||
| `find-imm.mjs <exe> /regex/` | every instruction whose text matches (e.g. `/96AB0h\]/`) |
|
| `find-imm.mjs <exe> /regex/` | every instruction whose text matches (e.g. `/96AB0h\]/`) |
|
||||||
| `rtti.mjs <exe> <Class>` | vtables of an MSVC class via RTTI |
|
| `rtti.mjs <exe> <Class>` | vtables of an MSVC class via RTTI |
|
||||||
| `decompile.ps1 <addr...>` | decompile from the persistent Ghidra project `ghidra/` |
|
| `decompile.ps1 <addr...>` | decompile from the persistent Ghidra project `ghidra/` |
|
||||||
|
| `rename.ps1` | apply the function names in `names.txt` to the Ghidra project (add names there as functions are identified) |
|
||||||
|
|
||||||
## Persistent Ghidra project (`ghidra/`, not in git)
|
## Persistent Ghidra project (`ghidra/`, not in git)
|
||||||
|
|
||||||
@@ -36,5 +37,5 @@ settings to `build\x86-debug\sacred_re.ini` (restored afterwards), e.g.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Click positions assume the classic menu layout scaled onto a 3440x1440
|
Click positions assume the classic menu layout scaled onto a 3440x1440
|
||||||
monitor. `game-input.ps1` has the building blocks (start, click, key,
|
monitor. `game-input.ps1` has the building blocks (start, click, key, hold key,
|
||||||
screenshot). Don't touch mouse/keyboard while a test runs.
|
screenshot). Don't touch mouse/keyboard while a test runs.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# .\tools\decompile.ps1 0x6322b0 0x62ae70 [-Out file.c] [-ReadOnly]
|
# .\tools\decompile.ps1 0x6322b0 0x62ae70 [-Out file.c] [-ReadOnly]
|
||||||
# Prints the C to stdout (and keeps it in -Out, default ghidra\last.c).
|
# Prints the C to stdout (and keeps it in -Out, default ghidra\last.c).
|
||||||
# Without -ReadOnly, functions created for vtable-only methods are saved.
|
# Without -ReadOnly, functions created for vtable-only methods are saved.
|
||||||
|
[CmdletBinding(PositionalBinding = $false)]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory, ValueFromRemainingArguments)][string[]]$Addresses,
|
[Parameter(Mandatory, ValueFromRemainingArguments)][string[]]$Addresses,
|
||||||
[string]$Out = "",
|
[string]$Out = "",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# Start-Sacred # launcher, returns the sacred.exe process
|
# Start-Sacred # launcher, returns the sacred.exe process
|
||||||
# Click-At 1720 779 # screen pixels (left click)
|
# Click-At 1720 779 # screen pixels (left click)
|
||||||
# Press-Key 0x1B # virtual key (ESC)
|
# Press-Key 0x1B # virtual key (ESC)
|
||||||
|
# Hold-Key 0x27 -Ms 1500 # hold a key (right arrow)
|
||||||
# Save-Screen screenshots\x.png # primary monitor area of the game
|
# Save-Screen screenshots\x.png # primary monitor area of the game
|
||||||
# Stop-Sacred $game # only the given process
|
# Stop-Sacred $game # only the given process
|
||||||
#
|
#
|
||||||
@@ -85,3 +86,11 @@ function Move-To([int]$X, [int]$Y, [int]$AfterMs = 800) {
|
|||||||
}
|
}
|
||||||
Start-Sleep -Milliseconds $AfterMs
|
Start-Sleep -Milliseconds $AfterMs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Holds a key down for a while (polled keys such as movement need this).
|
||||||
|
function Hold-Key([byte]$VirtualKey, [int]$Ms = 1500, [int]$AfterMs = 500) {
|
||||||
|
[SacredRe.Input]::keybd_event($VirtualKey, 0, 0, [UIntPtr]::Zero)
|
||||||
|
Start-Sleep -Milliseconds $Ms
|
||||||
|
[SacredRe.Input]::keybd_event($VirtualKey, 0, 2, [UIntPtr]::Zero)
|
||||||
|
Start-Sleep -Milliseconds $AfterMs
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Apply function names from tools/names.txt to the persistent sacred.exe
|
||||||
|
// Ghidra project.
|
||||||
|
//
|
||||||
|
// Headless usage (see tools/rename.ps1):
|
||||||
|
// analyzeHeadless <projectDir> sacred -process sacred.exe -noanalysis
|
||||||
|
// -scriptPath tools/ghidra -postScript Rename.java <namesFile>
|
||||||
|
//
|
||||||
|
// names.txt: one "0xADDRESS Name" per line, "Class::Method" puts the function
|
||||||
|
// into a class namespace; "#" starts a comment. Functions are created where
|
||||||
|
// none starts at the address yet.
|
||||||
|
// @category sacred_re
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import ghidra.app.cmd.disassemble.DisassembleCommand;
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
|
import ghidra.program.model.symbol.SymbolTable;
|
||||||
|
|
||||||
|
public class Rename extends GhidraScript {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() throws Exception {
|
||||||
|
String[] args = getScriptArgs();
|
||||||
|
if (args.length != 1) {
|
||||||
|
printerr("usage: Rename.java <namesFile>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SymbolTable symbols = currentProgram.getSymbolTable();
|
||||||
|
int renamed = 0;
|
||||||
|
for (String raw : Files.readAllLines(Paths.get(args[0]), StandardCharsets.UTF_8)) {
|
||||||
|
String line = raw.replaceAll("#.*", "").trim();
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] parts = line.split("\\s+");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
printerr("bad line: " + raw);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Address address = toAddr(parts[0]);
|
||||||
|
Function function = getFunctionAt(address);
|
||||||
|
if (function == null) {
|
||||||
|
new DisassembleCommand(address, null, true).applyTo(currentProgram, monitor);
|
||||||
|
function = createFunction(address, null);
|
||||||
|
}
|
||||||
|
if (function == null) {
|
||||||
|
printerr("no function at " + parts[0]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = parts[1];
|
||||||
|
Namespace namespace = currentProgram.getGlobalNamespace();
|
||||||
|
int separator = name.lastIndexOf("::");
|
||||||
|
if (separator >= 0) {
|
||||||
|
String className = name.substring(0, separator);
|
||||||
|
name = name.substring(separator + 2);
|
||||||
|
namespace = symbols.getNamespace(className, currentProgram.getGlobalNamespace());
|
||||||
|
if (namespace == null) {
|
||||||
|
namespace = symbols.createClass(currentProgram.getGlobalNamespace(), className,
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function.setParentNamespace(namespace);
|
||||||
|
function.setName(name, SourceType.USER_DEFINED);
|
||||||
|
renamed++;
|
||||||
|
}
|
||||||
|
println("renamed " + renamed + " functions");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Function names for sacred.exe (Sacred Gold 2.28), applied to the Ghidra
|
||||||
|
# project by tools/rename.ps1. One "0xADDRESS Name" per line; "Class::Method"
|
||||||
|
# names go into a class namespace. Keep in sync with src/game/addresses.h.
|
||||||
|
|
||||||
|
# --- startup / engine core ---------------------------------------------------
|
||||||
|
0x0066f1c0 Log
|
||||||
|
0x00813140 CmdLineHasFlag
|
||||||
|
0x0066e500 GetMouseClientPos
|
||||||
|
0x008122a0 MainWindowProc
|
||||||
|
0x00849701 __RTDynamicCast
|
||||||
|
0x0084aa86 ToUpper
|
||||||
|
|
||||||
|
# --- renderer ----------------------------------------------------------------
|
||||||
|
0x00645390 DxDriver7::SetDisplayMode
|
||||||
|
0x00645f70 DxDriver7::Flip
|
||||||
|
0x00646f50 DxDriver7::BeginScene
|
||||||
|
0x00646fd0 DxDriver7::EndScene
|
||||||
|
0x006469d0 DxDriver7::LoadingScreen
|
||||||
|
|
||||||
|
# --- kernel event queue ------------------------------------------------------
|
||||||
|
0x00808490 Kernel_Instance
|
||||||
|
0x00808930 cKernel::PostEvent # (event, flags, delayMs); key events (category 4) are queued
|
||||||
|
0x008087b0 cKernel::DispatchQueued
|
||||||
|
|
||||||
|
# --- engine (cEngine, vtable 0x88ff34) ---------------------------------------
|
||||||
|
0x0060e3f0 cEngine::RenderThreadLoop
|
||||||
|
0x0060f1d0 cEngine::Tick # per game tick (10 Hz): queued events, then hold-to-move orders
|
||||||
|
0x00618130 cEngine::ReceiveEvent # vf00: offers events to the UI, then engine hotkeys (A, P, Q/W/E/R/B/Space, ^)
|
||||||
|
0x00617360 cEngine::HandleMouseButton # click: pick object / walk to ground point
|
||||||
|
0x006170d0 cEngine::SendCreatureOrder # (creature, cEvent_creature*): snaps ground targets, then creature->vf18
|
||||||
|
0x00611f10 cEngine::UpdateCursorPick # per frame: object under the cursor
|
||||||
|
0x00610650 cEngine::ScrollInput # arrow keys: walk object #2 or scroll the camera
|
||||||
|
0x00613040 cEngine::UseActionSlot # hotkeys Q/W/E/R/B/Space
|
||||||
|
0x00613710 cEngine::CaptureScreen # save-game thumbnail
|
||||||
|
|
||||||
|
# --- objects / creatures -----------------------------------------------------
|
||||||
|
0x005fe060 cObjectManager::GetObject # (id); manager at *0xad3bc0
|
||||||
|
0x00603e90 cObjectManager::GetPlayerHero
|
||||||
|
0x00546ed0 cCreature::IsWalking # state +0x150 == 1
|
||||||
|
0x0052e400 cCreature::ReceiveOrder # vf18
|
||||||
|
0x004fb620 cCreature::TraceToCursor # line trace from the creature to the cursor's world point (aiming)
|
||||||
|
|
||||||
|
# --- mouse cursor (single instance) ------------------------------------------
|
||||||
|
0x006550f0 MouseCursor_Instance
|
||||||
|
0x00654e10 cMouseCursor::cMouseCursor
|
||||||
|
0x00655670 cMouseCursor::SetPosition
|
||||||
|
0x006559e0 cMouseCursor::GetX
|
||||||
|
0x006559f0 cMouseCursor::GetY
|
||||||
|
0x006559a0 cMouseCursor::GetPreviousPosition
|
||||||
|
0x006555e0 cMouseCursor::DrawItem
|
||||||
|
0x00655ab0 cMouseCursor::IsButtonHeld
|
||||||
|
|
||||||
|
# --- world view (cWorldView0, vtable 0x8900f8) -------------------------------
|
||||||
|
0x00624ae0 cWorldView0::SetupView
|
||||||
|
0x00628210 cWorldView0::SetZoom
|
||||||
|
0x006322b0 cWorldView0::Render
|
||||||
|
0x0062a730 cWorldView0::UpdateCamera
|
||||||
|
0x0062b000 cWorldView0::RenderTileRow # tiles + collects objects into the draw lists
|
||||||
|
0x0062e580 cWorldView0::DrawObjects
|
||||||
|
0x006300d0 cWorldView0::DrawObjects2
|
||||||
|
0x00623c40 cWorldView0::WorldToScreen
|
||||||
|
0x00623a20 cWorldView0::ScreenToView
|
||||||
|
0x00626d30 cWorldView0::PickObjectAt # (screen x, y) -> object id
|
||||||
|
0x00627260 cWorldView0::DrawObjectLabels # Alt
|
||||||
|
0x006426a0 BuildOrthoMatrix
|
||||||
|
|
||||||
|
# --- UI ----------------------------------------------------------------------
|
||||||
|
0x00756820 UiManager_Instance # *0x17ecb3c
|
||||||
|
0x00756940 cUI_Manager::ThreadLoop
|
||||||
|
0x00757120 cUI_Manager::ReceiveEvent # vf00: UI hotkeys (C F H I L M N O S T)
|
||||||
|
0x007587b0 cUI_Manager::Render
|
||||||
|
0x0075a860 cUI_Manager::GetFocus
|
||||||
|
0x0075a370 cUI_Manager::IsUiAtPoint
|
||||||
|
0x0075bbf0 cUI_Manager::DrawStatusLine
|
||||||
|
0x0075c200 cUI_Manager::DrawMessage
|
||||||
|
0x00731d40 cUI_Window2::HitTest
|
||||||
|
0x006dfb10 cUI_Taskbar2::HitTest
|
||||||
|
0x006e1970 cUI_Taskbar2::HandleEvent # digit keys: belt / skill slots
|
||||||
|
0x006e3ac0 cUI_Taskbar2::Render
|
||||||
|
0x006e7300 cUI_Tooltip::Render
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Apply tools/names.txt to the persistent Ghidra project (ghidra/sacred.gpr).
|
||||||
|
# .\tools\rename.ps1
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$root = Split-Path $PSScriptRoot
|
||||||
|
$ghidra = if ($env:GHIDRA_INSTALL_DIR) { $env:GHIDRA_INSTALL_DIR } else { "E:\Coding\Ghidra\ghidra_12.1.4_PUBLIC" }
|
||||||
|
if (-not $env:JAVA_HOME) { $env:JAVA_HOME = "C:\Program Files\Java\jdk-21" }
|
||||||
|
|
||||||
|
$arguments = @(
|
||||||
|
(Join-Path $root "ghidra"), "sacred",
|
||||||
|
"-process", "sacred.exe", "-noanalysis",
|
||||||
|
"-scriptPath", (Join-Path $PSScriptRoot "ghidra"),
|
||||||
|
"-postScript", "Rename.java", (Join-Path $PSScriptRoot "names.txt")
|
||||||
|
)
|
||||||
|
& (Join-Path $ghidra "support\analyzeHeadless.bat") @arguments *> (Join-Path $root "ghidra\rename.out")
|
||||||
|
if ($LASTEXITCODE -ne 0) { Get-Content (Join-Path $root "ghidra\rename.out") -Tail 30; exit $LASTEXITCODE }
|
||||||
|
Select-String -Path (Join-Path $root "ghidra\rename.out") -Pattern "renamed|bad line|no function"
|
||||||
Reference in new issue
Block a user