Files
BT412/reference/ghidra_scripts/ExportAll.java
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

110 lines
5.2 KiB
Java

// Full decompile: every function (sharded), vtable recovery, and assert-based file attribution.
// Outputs to decomp/recovered/: all/part_NNN.c, functions_index.tsv, vtables.tsv
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.*;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
import java.io.*;
import java.util.*;
public class ExportAll extends GhidraScript {
public void run() throws Exception {
File outDir = new File("C:\\git\\nick-games\\decomp\\recovered");
outDir.mkdirs();
File allDir = new File(outDir, "all");
allDir.mkdirs();
FunctionManager fm = currentProgram.getFunctionManager();
ReferenceManager rm = currentProgram.getReferenceManager();
Listing listing = currentProgram.getListing();
Memory mem = currentProgram.getMemory();
// 1) attribute functions to source files via embedded assert paths
Map<Long, String> funcFile = new HashMap<>();
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;
String file = s.substring(s.lastIndexOf('\\') + 1);
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) funcFile.put(f.getEntryPoint().getOffset(), tag);
}
}
println("attributed functions: " + funcFile.size());
// 2) vtable recovery: runs of >=3 consecutive pointers to function entries in data memory
PrintWriter vt = new PrintWriter(new FileWriter(new File(outDir, "vtables.tsv")));
int vtCount = 0;
for (MemoryBlock b : mem.getBlocks()) {
if (!b.isInitialized() || b.isExecute()) continue;
Address a = b.getStart(), end = b.getEnd();
List<Long> run = new ArrayList<>();
Address runStart = null;
while (a.compareTo(end) < 0 && !monitor.isCancelled()) {
long val;
try { val = mem.getInt(a) & 0xffffffffL; } catch (Exception ex) { break; }
Function f = null;
try { f = fm.getFunctionAt(a.getNewAddress(val)); } catch (Exception ex) {}
if (f != null) { if (run.isEmpty()) runStart = a; run.add(val); }
else { if (run.size() >= 3) { writeVt(vt, runStart, run); vtCount++; } run.clear(); }
a = a.add(4);
}
if (run.size() >= 3) { writeVt(vt, runStart, run); vtCount++; }
}
vt.close();
println("vtables recovered: " + vtCount);
// 3) decompile ALL functions, sharded 400/file
DecompInterface dec = new DecompInterface();
dec.openProgram(currentProgram);
PrintWriter idx = new PrintWriter(new FileWriter(new File(outDir, "functions_index.tsv")));
FunctionIterator it = fm.getFunctions(true);
int count = 0, shard = 0;
PrintWriter out = null;
while (it.hasNext() && !monitor.isCancelled()) {
Function f = it.next();
if (count % 400 == 0) {
if (out != null) out.close();
out = new PrintWriter(new FileWriter(new File(allDir, String.format("part_%03d.c", shard++))));
}
count++;
long off = f.getEntryPoint().getOffset();
String tag = funcFile.getOrDefault(off, "?");
idx.println(Long.toHexString(off) + "\t" + f.getBody().getNumAddresses() + "\t" + tag + "\t" + f.getName());
try {
DecompileResults r = dec.decompileFunction(f, 45, monitor);
out.println("/* @" + f.getEntryPoint() + " file=" + tag + " name=" + f.getName() + " */");
if (r != null && r.decompileCompleted() && r.getDecompiledFunction() != null)
out.println(r.getDecompiledFunction().getC());
else
out.println("// <decompile failed>");
out.println();
} catch (Exception ex) {
out.println("// <exception: " + ex.getMessage() + ">\n");
}
if (count % 500 == 0) println("decompiled " + count + " ...");
}
if (out != null) out.close();
idx.close();
dec.dispose();
println("DONE. total functions: " + count + " (shards: " + shard + ")");
}
void writeVt(PrintWriter vt, Address start, List<Long> run) {
StringBuilder sb = new StringBuilder(start.toString());
for (Long v : run) sb.append("\t").append(Long.toHexString(v));
vt.println(sb.toString());
}
}