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>
89 lines
4.3 KiB
Java
89 lines
4.3 KiB
Java
// Headless script: attribute functions to their original .cpp via embedded assert source-paths
|
|
// (d:\tesla_bt\bt\<file>.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<Function, Set<String>> funcFiles = new HashMap<>();
|
|
Map<String, Set<Function>> 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<String, Set<Function>> 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());
|
|
}
|
|
}
|