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>
150 lines
6.3 KiB
Java
150 lines
6.3 KiB
Java
// 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<Long> 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);
|
|
}
|
|
}
|