// Ghidra headless Java script to decompile specific functions from acclient.exe // @category Analysis // @author acdream import ghidra.app.script.GhidraScript; import ghidra.app.decompiler.DecompInterface; import ghidra.app.decompiler.DecompileResults; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.FunctionIterator; import ghidra.program.model.address.Address; import ghidra.util.task.ConsoleTaskMonitor; import java.io.FileWriter; import java.io.PrintWriter; public class DecompileTargets extends GhidraScript { // Known function addresses from ACME ClientReference.cs private static final long[] KNOWN_ADDRS = { 0x00531D10L, // CLandBlockStruct::ConstructPolygons 0x00532170L, // CLandBlockStruct::GetCellRotation 0x005328D0L, // CLandBlockStruct::ConstructVertices 0x005A9980L, // LandDefs::get_vars }; private static final String[] KNOWN_NAMES = { "CLandBlockStruct::ConstructPolygons", "CLandBlockStruct::GetCellRotation", "CLandBlockStruct::ConstructVertices", "LandDefs::get_vars", }; // Name patterns to search for in the function list private static final String[] PATTERNS = { "physics", "motion", "moveto", "move_to", "jump", "transit", "portal", "envcell", "env_cell", "landblock", "terrain", "split", "step_up", "velocity", "autonomous", "collision", "cylinder", "sphere", "find_terrain", "interp", }; @Override public void run() throws Exception { String outPath = "C:/Users/erikn/source/repos/acdream/docs/research/acclient_decompiled.c"; PrintWriter out = new PrintWriter(new FileWriter(outPath)); DecompInterface decomp = new DecompInterface(); decomp.openProgram(currentProgram); out.println("// Decompiled from acclient.exe using Ghidra 12.0.4 headless"); out.println("// Target: Asheron's Call client physics/terrain/movement functions"); out.println("// Binary: C:/Turbine/Asheron's Call/acclient.exe (4.7MB, 2016)"); out.println(""); // 1. Decompile at known addresses out.println("// ============================================================"); out.println("// KNOWN ADDRESSES (from ACME ClientReference.cs)"); out.println("// ============================================================"); out.println(""); for (int i = 0; i < KNOWN_ADDRS.length; i++) { Address addr = toAddr(KNOWN_ADDRS[i]); Function func = getFunctionAt(addr); if (func == null) { createFunction(addr, KNOWN_NAMES[i]); func = getFunctionAt(addr); } out.printf("// --- %s at 0x%08X ---\n", KNOWN_NAMES[i], KNOWN_ADDRS[i]); if (func != null) { DecompileResults results = decomp.decompileFunction(func, 60, monitor); if (results != null && results.depiledFunction() != null) { out.println(results.getDecompiledFunction().getC()); } else { out.println("// DECOMPILATION FAILED"); } } else { out.println("// FUNCTION NOT FOUND"); } out.println(""); } // 2. Search for functions by name pattern out.println("// ============================================================"); out.println("// PATTERN-MATCHED FUNCTIONS"); out.println("// ============================================================"); out.println(""); java.util.Set seen = new java.util.HashSet<>(); for (long a : KNOWN_ADDRS) seen.add(a); FunctionManager fm = currentProgram.getFunctionManager(); int patternMatched = 0; for (String pattern : PATTERNS) { FunctionIterator iter = fm.getFunctions(true); while (iter.hasNext()) { Function func = iter.next(); String name = func.getName().toLowerCase(); if (!name.contains(pattern)) continue; long addr = func.getEntryPoint().getOffset(); if (seen.contains(addr)) continue; seen.add(addr); out.printf("// --- %s at 0x%08X (pattern: '%s') ---\n", func.getName(), addr, pattern); DecompileResults results = decomp.decompileFunction(func, 60, monitor); if (results != null && results.depiledFunction() != null) { out.println(results.getDecompiledFunction().getC()); } else { out.println("// DECOMPILATION FAILED"); } out.println(""); patternMatched++; } } // 3. Decompile ALL functions near the known CLandBlockStruct area // (0x00530000 - 0x00535000) to get the full terrain/physics module out.println("// ============================================================"); out.println("// CLandBlockStruct REGION (0x00530000 - 0x00535000)"); out.println("// ============================================================"); out.println(""); int regionFuncs = 0; FunctionIterator allFuncs = fm.getFunctions(true); while (allFuncs.hasNext()) { Function func = allFuncs.next(); long addr = func.getEntryPoint().getOffset(); if (addr >= 0x00530000L && addr < 0x00535000L && !seen.contains(addr)) { seen.add(addr); out.printf("// --- %s at 0x%08X ---\n", func.getName(), addr); DecompileResults results = decomp.decompileFunction(func, 60, monitor); if (results != null && results.depiledFunction() != null) { out.println(results.getDecompiledFunction().getC()); } out.println(""); regionFuncs++; } } out.close(); decomp.dispose(); printf("Decompilation complete. Output: %s\n", outPath); printf("Known: %d, Pattern-matched: %d, Region: %d, Total: %d\n", KNOWN_ADDRS.length, patternMatched, regionFuncs, KNOWN_ADDRS.length + patternMatched + regionFuncs); } }