// Headless script: attribute functions to their original .cpp via embedded assert source-paths // (d:\tesla_bt\bt\.cpp), then decompile the BattleTech game-logic functions and write them // grouped per source file into decomp/recovered/. Engine (munga\) is summarized, not exported. import ghidra.app.script.GhidraScript; import ghidra.app.decompiler.*; import ghidra.program.model.address.Address; import ghidra.program.model.data.StringDataInstance; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import java.io.*; import java.util.*; public class ExportBTSource extends GhidraScript { public void run() throws Exception { File outDir = new File("C:\\git\\nick-games\\decomp\\recovered"); outDir.mkdirs(); // 1) map function -> set of source files it references (via assert path strings) Map> funcFiles = new HashMap<>(); Map> fileFuncs = new TreeMap<>(); ReferenceManager rm = currentProgram.getReferenceManager(); FunctionManager fm = currentProgram.getFunctionManager(); Listing listing = currentProgram.getListing(); int strCount = 0; DataIterator di = listing.getDefinedData(true); while (di.hasNext() && !monitor.isCancelled()) { Data d = di.next(); if (d == null || !d.hasStringValue()) continue; Object v = d.getValue(); if (v == null) continue; String s = v.toString().toLowerCase().replace('/', '\\'); if (!s.contains("\\tesla_bt\\")) continue; if (!(s.endsWith(".cpp") || s.endsWith(".hpp"))) continue; strCount++; String file = s.substring(s.lastIndexOf('\\') + 1); // classify subdir String dir = s.contains("\\bt_l4\\") ? "bt_l4" : s.contains("\\bt\\") ? "bt" : s.contains("\\munga_l4\\") ? "munga_l4" : "munga"; String tag = dir + "/" + file; for (Reference ref : rm.getReferencesTo(d.getAddress())) { Function f = fm.getFunctionContaining(ref.getFromAddress()); if (f == null) continue; funcFiles.computeIfAbsent(f, k -> new HashSet<>()).add(tag); fileFuncs.computeIfAbsent(tag, k -> new LinkedHashSet<>()).add(f); } } println("assert source-path strings: " + strCount); println("source files attributed: " + fileFuncs.size()); // 2) decompile + export functions for BT game modules (bt/ and bt_l4/) DecompInterface dec = new DecompInterface(); dec.openProgram(currentProgram); PrintWriter index = new PrintWriter(new FileWriter(new File(outDir, "_index.txt"))); for (Map.Entry> e : fileFuncs.entrySet()) { String tag = e.getKey(); int n = e.getValue().size(); index.println(tag + " -> " + n + " functions"); boolean isGame = tag.startsWith("bt/") || tag.startsWith("bt_l4/"); if (!isGame) continue; // engine/HAL we already have as source — skip export String safe = tag.replace('/', '_'); PrintWriter out = new PrintWriter(new FileWriter(new File(outDir, safe + ".c"))); out.println("// Recovered decompilation of functions attributed to " + tag); out.println("// (from BTL4OPT.EXE via embedded assert paths). Pseudocode — reconstruct against the header.\n"); int ok = 0; for (Function f : e.getValue()) { if (monitor.isCancelled()) break; DecompileResults r = dec.decompileFunction(f, 60, monitor); if (r != null && r.decompileCompleted()) { DecompiledFunction df = r.getDecompiledFunction(); if (df != null) { out.println("/* ---- @ " + f.getEntryPoint() + " ---- */"); out.println(df.getC()); out.println(); ok++; } } } out.close(); println("exported " + tag + ": " + ok + "/" + n + " functions"); } index.close(); dec.dispose(); println("DONE. Output in " + outDir.getAbsolutePath()); } }