""" Decompile targeted functions from acclient.exe using pyghidra. Run with: python decompile_acclient.py """ import pyghidra GHIDRA_PATH = "C:/tools/ghidra_12.0.4_PUBLIC" BINARY_PATH = "C:/Turbine/Asheron's Call/acclient.exe" OUTPUT_PATH = "C:/Users/erikn/source/repos/acdream/docs/research/acclient_decompiled.c" PROJECT_DIR = "C:/Users/erikn/source/repos/acdream/tools/ghidra_project" # Known function addresses from ACME ClientReference.cs KNOWN_FUNCTIONS = { 0x00531D10: "CLandBlockStruct_ConstructPolygons", 0x00532170: "CLandBlockStruct_GetCellRotation", 0x005328D0: "CLandBlockStruct_ConstructVertices", 0x005A9980: "LandDefs_get_vars", } # Name patterns to search for PATTERNS = [ "physics", "motion", "moveto", "move_to", "jump", "transit", "portal", "envcell", "env_cell", "landblock", "terrain", "split", "step_up", "velocity", "autonomous", "collision", "cylinder", "sphere", "interp", "heading", "position", ] def main(): pyghidra.start(install_dir=GHIDRA_PATH, verbose=True) from ghidra.app.decompiler import DecompInterface from ghidra.util.task import ConsoleTaskMonitor with pyghidra.open_program(BINARY_PATH, project_location=PROJECT_DIR, project_name="acclient_pyghidra") as flat_api: program = flat_api.getCurrentProgram() print(f"Opened: {program.getName()}, {program.getLanguage()}") decomp = DecompInterface() decomp.openProgram(program) monitor = ConsoleTaskMonitor() with open(OUTPUT_PATH, "w") as out: out.write("// Decompiled from acclient.exe using Ghidra 12.0.4 + pyghidra\n") out.write("// Target: AC client physics/terrain/movement functions\n") out.write(f"// Binary: {BINARY_PATH}\n\n") # 1. Known addresses out.write("// " + "=" * 60 + "\n") out.write("// KNOWN ADDRESSES (from ACME ClientReference.cs)\n") out.write("// " + "=" * 60 + "\n\n") for addr_int, name in sorted(KNOWN_FUNCTIONS.items()): addr = flat_api.toAddr(addr_int) func = flat_api.getFunctionAt(addr) if func is None: # Try to disassemble at this address first try: flat_api.disassemble(addr) except: pass try: flat_api.createFunction(addr, name) except: pass func = flat_api.getFunctionAt(addr) if func is None: # Maybe it's inside another function — get the containing function func = flat_api.getFunctionContaining(addr) if func: out.write(f"// NOTE: 0x{addr_int:08X} is inside {func.getName()} at 0x{func.getEntryPoint().getOffset():08X}\n") out.write(f"// --- {name} at 0x{addr_int:08X} ---\n") if func: results = decomp.decompileFunction(func, 60, monitor) if results and results.decompiledFunction: out.write(results.getDecompiledFunction().getC()) else: out.write("// DECOMPILATION FAILED\n") else: out.write("// FUNCTION NOT FOUND\n") out.write("\n") # 2. Pattern search out.write("// " + "=" * 60 + "\n") out.write("// PATTERN-MATCHED FUNCTIONS\n") out.write("// " + "=" * 60 + "\n\n") seen = set(KNOWN_FUNCTIONS.keys()) fm = program.getFunctionManager() pattern_count = 0 for pattern in PATTERNS: func_iter = fm.getFunctions(True) while func_iter.hasNext(): func = func_iter.next() fname = func.getName().lower() if pattern not in fname: continue faddr = func.getEntryPoint().getOffset() if faddr in seen: continue seen.add(faddr) out.write(f"// --- {func.getName()} at 0x{faddr:08X} (pattern: '{pattern}') ---\n") results = decomp.decompileFunction(func, 60, monitor) if results and results.decompiledFunction: out.write(results.getDecompiledFunction().getC()) else: out.write("// DECOMPILATION FAILED\n") out.write("\n") pattern_count += 1 # 3. Expanded region scan REGIONS = [ (0x00530000, 0x00536000, "CLandBlockStruct + terrain"), (0x00536000, 0x00540000, "Nearby functions"), (0x005A9000, 0x005AB000, "LandDefs region"), ] region_count = 0 for rstart, rend, rname in REGIONS: out.write("// " + "=" * 60 + "\n") out.write(f"// REGION: {rname} (0x{rstart:08X} - 0x{rend:08X})\n") out.write("// " + "=" * 60 + "\n\n") func_iter = fm.getFunctions(True) while func_iter.hasNext(): func = func_iter.next() faddr = func.getEntryPoint().getOffset() if rstart <= faddr < rend and faddr not in seen: seen.add(faddr) out.write(f"// --- {func.getName()} at 0x{faddr:08X} ---\n") results = decomp.decompileFunction(func, 60, monitor) if results and results.decompiledFunction: out.write(results.decompiledFunction.getC()) out.write("\n") region_count += 1 decomp.dispose() print(f"Done! Known: {len(KNOWN_FUNCTIONS)}, Pattern: {pattern_count}, Region: {region_count}") print(f"Output: {OUTPUT_PATH}") if __name__ == "__main__": main()