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>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user