""" 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()