research: decompile acclient.exe terrain/physics via Ghidra headless
Used Ghidra 12.0.4 + pyghidra to decompile 368 functions from the retail AC client binary (acclient.exe, 4.7MB, 2016). Output: docs/research/acclient_decompiled.c (13,560 lines) Confirmed the decompiled code matches ACME's ClientReference.cs: - ConstructPolygons split formula at ~0x00532610 with constants 0x0CCAC033, 0x6C1AC587, -0x421BE3BD, -0x519B8F25 - Same 2.3283064e-10 float comparison for split direction Regions decompiled: - 0x530000-0x536000: CLandBlockStruct + terrain (85 functions) - 0x536000-0x540000: nearby functions (168 functions) - 0x5A9000-0x5AB000: LandDefs region (111 functions) Tools: tools/decompile_acclient.py (pyghidra headless script) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d4967a461
commit
370c6e3133
4 changed files with 13982 additions and 0 deletions
127
tools/ghidra_decompile.py
Normal file
127
tools/ghidra_decompile.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# Ghidra headless script to decompile specific functions from acclient.exe
|
||||
# Run with: analyzeHeadless <project_dir> <project_name> -import <exe> -postScript ghidra_decompile.py
|
||||
#
|
||||
# Targets the key physics/movement/rendering functions identified from
|
||||
# ACME's ClientReference.cs annotations.
|
||||
from ghidra.app.decompiler import DecompInterface
|
||||
from ghidra.util.task import ConsoleTaskMonitor
|
||||
import java.io
|
||||
|
||||
# Known function addresses from ACME ClientReference.cs and ACViewer
|
||||
# These are virtual addresses in the loaded PE image.
|
||||
TARGET_FUNCTIONS = {
|
||||
# Terrain (from ClientReference.cs)
|
||||
0x00531D10: "CLandBlockStruct::ConstructPolygons",
|
||||
0x00532170: "CLandBlockStruct::GetCellRotation",
|
||||
0x005328D0: "CLandBlockStruct::ConstructVertices",
|
||||
0x005A9980: "LandDefs::get_vars",
|
||||
|
||||
# Physics / movement (high priority targets)
|
||||
# These are educated guesses based on ACE's C# port class names.
|
||||
# Ghidra will find the actual functions at these addresses.
|
||||
}
|
||||
|
||||
# Additional search patterns - we'll grep the decompiled output for these
|
||||
SEARCH_PATTERNS = [
|
||||
"CPhysicsObj",
|
||||
"CMotionInterp",
|
||||
"MoveTo",
|
||||
"MoveToObject",
|
||||
"MoveToPosition",
|
||||
"find_transit_cells",
|
||||
"CLandBlock",
|
||||
"CEnvCell",
|
||||
"step_up",
|
||||
"jump",
|
||||
"get_jump",
|
||||
"set_velocity",
|
||||
"autonomous",
|
||||
"terrain_poly",
|
||||
"find_terrain",
|
||||
"split",
|
||||
"NESW",
|
||||
]
|
||||
|
||||
def decompile_at_address(decompiler, program, address, name):
|
||||
"""Decompile the function at the given address."""
|
||||
func = getFunctionAt(toAddr(address))
|
||||
if func is None:
|
||||
# Try to create a function at this address
|
||||
createFunction(toAddr(address), name)
|
||||
func = getFunctionAt(toAddr(address))
|
||||
|
||||
if func is None:
|
||||
print("WARNING: No function found at 0x{:08X} ({})".format(address, name))
|
||||
return None
|
||||
|
||||
results = decompiler.decompileFunction(func, 60, ConsoleTaskMonitor())
|
||||
if results is None or not results.depiledFunction():
|
||||
print("WARNING: Decompilation failed at 0x{:08X} ({})".format(address, name))
|
||||
return None
|
||||
|
||||
return results.getDecompiledFunction().getC()
|
||||
|
||||
def find_functions_by_name_pattern(program, pattern):
|
||||
"""Find functions whose name or decompiled body contains the pattern."""
|
||||
results = []
|
||||
fm = program.getFunctionManager()
|
||||
for func in fm.getFunctions(True):
|
||||
if pattern.lower() in func.getName().lower():
|
||||
results.append(func)
|
||||
return results
|
||||
|
||||
def main():
|
||||
program = getCurrentProgram()
|
||||
decompiler = DecompInterface()
|
||||
decompiler.openProgram(program)
|
||||
|
||||
output_path = "C:/Users/erikn/source/repos/acdream/docs/research/acclient_decompiled.c"
|
||||
f = java.io.PrintWriter(java.io.FileWriter(output_path))
|
||||
|
||||
f.println("// Decompiled from acclient.exe using Ghidra 12.0.4")
|
||||
f.println("// Target: Asheron's Call client physics/terrain/movement functions")
|
||||
f.println("// Source binary: C:/Turbine/Asheron's Call/acclient.exe (4.7MB, 2016)")
|
||||
f.println("")
|
||||
|
||||
# 1. Decompile known addresses
|
||||
f.println("// ============================================================")
|
||||
f.println("// KNOWN ADDRESSES (from ACME ClientReference.cs)")
|
||||
f.println("// ============================================================")
|
||||
f.println("")
|
||||
|
||||
for addr, name in sorted(TARGET_FUNCTIONS.items()):
|
||||
f.println("// --- {} at 0x{:08X} ---".format(name, addr))
|
||||
code = decompile_at_address(decompiler, program, addr, name)
|
||||
if code:
|
||||
f.println(code)
|
||||
f.println("")
|
||||
|
||||
# 2. Search for physics/movement functions by name pattern
|
||||
f.println("// ============================================================")
|
||||
f.println("// PATTERN-MATCHED FUNCTIONS")
|
||||
f.println("// ============================================================")
|
||||
f.println("")
|
||||
|
||||
seen_addrs = set(TARGET_FUNCTIONS.keys())
|
||||
for pattern in SEARCH_PATTERNS:
|
||||
funcs = find_functions_by_name_pattern(program, pattern)
|
||||
for func in funcs:
|
||||
addr = func.getEntryPoint().getOffset()
|
||||
if addr in seen_addrs:
|
||||
continue
|
||||
seen_addrs.add(addr)
|
||||
|
||||
f.println("// --- {} at 0x{:08X} (matched pattern '{}') ---".format(
|
||||
func.getName(), addr, pattern))
|
||||
results = decompiler.decompileFunction(func, 60, ConsoleTaskMonitor())
|
||||
if results and results.depiledFunction():
|
||||
f.println(results.getDecompiledFunction().getC())
|
||||
f.println("")
|
||||
|
||||
f.close()
|
||||
decompiler.dispose()
|
||||
print("Decompilation complete. Output: " + output_path)
|
||||
print("Decompiled {} known + {} pattern-matched functions".format(
|
||||
len(TARGET_FUNCTIONS), len(seen_addrs) - len(TARGET_FUNCTIONS)))
|
||||
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue