research: full acclient.exe decompilation — 22,225 functions, 688K lines
Complete decompilation of the retail Asheron's Call client using Ghidra 12.0.4 + pyghidra headless. 22,225 of 22,226 functions successfully decompiled in 75 seconds. Output: docs/research/decompiled/ (54 files, 688,567 lines of C) Key findings already identified: - CLandBlockStruct::ConstructPolygons at chunk_00530000.c:2270 (split direction formula with 0x0CCAC033 constants) - Motion command handlers at chunk_00510000.c (0x45000005 etc) - Motion interpreter at chunk_00520000.c - Portal space UI at chunk_004D0000.c and chunk_00560000.c Next: identify CPhysicsObj, CMotionInterp, collision, and movement functions by cross-referencing against ACE's C# port. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
370c6e3133
commit
4d36756b91
56 changed files with 688744 additions and 0 deletions
114
tools/decompile_full.py
Normal file
114
tools/decompile_full.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"""
|
||||
Decompile the ENTIRE acclient.exe binary using pyghidra.
|
||||
Outputs all functions organized by address range.
|
||||
"""
|
||||
import pyghidra
|
||||
import time
|
||||
|
||||
GHIDRA_PATH = "C:/tools/ghidra_12.0.4_PUBLIC"
|
||||
BINARY_PATH = "C:/Turbine/Asheron's Call/acclient.exe"
|
||||
OUTPUT_DIR = "C:/Users/erikn/source/repos/acdream/docs/research/decompiled"
|
||||
PROJECT_DIR = "C:/Users/erikn/source/repos/acdream/tools/ghidra_project"
|
||||
|
||||
def main():
|
||||
import os
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
pyghidra.start(install_dir=GHIDRA_PATH, verbose=False)
|
||||
|
||||
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_full2") as flat_api:
|
||||
program = flat_api.getCurrentProgram()
|
||||
print(f"Opened: {program.getName()}")
|
||||
|
||||
decomp = DecompInterface()
|
||||
decomp.openProgram(program)
|
||||
monitor = ConsoleTaskMonitor()
|
||||
|
||||
fm = program.getFunctionManager()
|
||||
|
||||
# Count total functions
|
||||
total = 0
|
||||
func_iter = fm.getFunctions(True)
|
||||
while func_iter.hasNext():
|
||||
func_iter.next()
|
||||
total += 1
|
||||
print(f"Total functions found: {total}")
|
||||
|
||||
# Decompile ALL functions, split into files by address range (64KB chunks)
|
||||
CHUNK_SIZE = 0x10000 # 64KB chunks
|
||||
current_chunk = -1
|
||||
current_file = None
|
||||
decompiled = 0
|
||||
failed = 0
|
||||
start_time = time.time()
|
||||
|
||||
func_iter = fm.getFunctions(True)
|
||||
while func_iter.hasNext():
|
||||
func = func_iter.next()
|
||||
faddr = func.getEntryPoint().getOffset()
|
||||
chunk = faddr // CHUNK_SIZE
|
||||
|
||||
# New chunk = new file
|
||||
if chunk != current_chunk:
|
||||
if current_file:
|
||||
current_file.close()
|
||||
current_chunk = chunk
|
||||
chunk_start = chunk * CHUNK_SIZE
|
||||
filename = f"chunk_{chunk_start:08X}.c"
|
||||
filepath = os.path.join(OUTPUT_DIR, filename)
|
||||
current_file = open(filepath, "w")
|
||||
current_file.write(f"// Decompiled from acclient.exe — chunk 0x{chunk_start:08X}\n")
|
||||
current_file.write(f"// Ghidra 12.0.4 + pyghidra headless\n\n")
|
||||
|
||||
try:
|
||||
results = decomp.decompileFunction(func, 30, monitor)
|
||||
if results and results.decompiledFunction:
|
||||
code = results.decompiledFunction.getC()
|
||||
current_file.write(f"// --- {func.getName()} at 0x{faddr:08X} (size: {func.getBody().getNumAddresses()}) ---\n")
|
||||
current_file.write(code)
|
||||
current_file.write("\n\n")
|
||||
decompiled += 1
|
||||
else:
|
||||
current_file.write(f"// --- {func.getName()} at 0x{faddr:08X} --- DECOMPILATION FAILED\n\n")
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
current_file.write(f"// --- {func.getName()} at 0x{faddr:08X} --- ERROR: {e}\n\n")
|
||||
failed += 1
|
||||
|
||||
if (decompiled + failed) % 500 == 0:
|
||||
elapsed = time.time() - start_time
|
||||
rate = (decompiled + failed) / elapsed if elapsed > 0 else 0
|
||||
print(f" Progress: {decompiled + failed}/{total} ({decompiled} ok, {failed} failed) "
|
||||
f"[{rate:.0f} funcs/sec, {elapsed:.0f}s elapsed]")
|
||||
|
||||
if current_file:
|
||||
current_file.close()
|
||||
|
||||
decomp.dispose()
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\nDone! Decompiled {decompiled}/{total} functions ({failed} failed)")
|
||||
print(f"Time: {elapsed:.0f}s ({decompiled/elapsed:.0f} funcs/sec)")
|
||||
print(f"Output: {OUTPUT_DIR}/")
|
||||
|
||||
# Write a summary index
|
||||
index_path = os.path.join(OUTPUT_DIR, "INDEX.md")
|
||||
with open(index_path, "w") as idx:
|
||||
idx.write("# Decompiled acclient.exe — Full Index\n\n")
|
||||
idx.write(f"- Total functions: {total}\n")
|
||||
idx.write(f"- Successfully decompiled: {decompiled}\n")
|
||||
idx.write(f"- Failed: {failed}\n")
|
||||
idx.write(f"- Time: {elapsed:.0f}s\n\n")
|
||||
idx.write("## Files by address chunk\n\n")
|
||||
for f in sorted(os.listdir(OUTPUT_DIR)):
|
||||
if f.endswith(".c"):
|
||||
fpath = os.path.join(OUTPUT_DIR, f)
|
||||
lines = sum(1 for _ in open(fpath))
|
||||
idx.write(f"- `{f}` — {lines} lines\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue