# Ghidra headless script to decompile specific functions from acclient.exe # Run with: analyzeHeadless -import -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()