Files
tdkpin/original/tools/SeedMissingEntrypoints.java
ddidderr 8b99e9607c feat(reconstruction): complete binary-backed C recovery
Replace the partial mechanics transcriptions with a separate, readable C11
reconstruction of the complete Win16 image while preserving the original raw
Ghidra export as immutable evidence. Cover all ordinary and overlapping entry
points, Borland runtime behavior, Win16 imports, segmented data, callbacks,
resources, indirect control flow, physics, rendering, persistence, and
startup/shutdown lifecycles.

Add deterministic extraction and audit tooling plus address-linked ledgers for
functions, imports, DGROUP ranges and objects, relocations, resources, and
callbacks. The final gate records zero raw, partial, restored, unknown,
blocked, or unclassified required units. Keep the semantic-fidelity boundary
explicit: the portable C is not claimed to reproduce a byte-identical Borland
NE build.

Add strict focused harnesses for every reconstructed C unit, exact resource
round-trip checks, and a 16-bit Borland Real48 reference probe. No Rust source
or Cargo metadata is changed in this phase.

Test Plan:
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `bash original/tools/probe_real48_reference.sh` -- passed bit-for-bit
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
- `git diff HEAD -- '*.rs' Cargo.toml Cargo.lock` -- empty
2026-08-23 16:41:17 +02:00

120 lines
5.3 KiB
Java

// Seed NE entry points that Ghidra 12.1's automatic analysis leaves undefined.
// @category TDKPIN
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.ByteDataType;
import ghidra.program.model.data.WordDataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.SourceType;
public class SeedMissingEntrypoints extends GhidraScript {
@Override
public void run() throws Exception {
// Segment tags, compiler tables, and embedded Real48 constants must be
// defined before missed code is disassembled so linear sweep cannot
// consume them as instructions.
defineWords("1000:0000", 1, "code_segment_1_tag");
defineWords("1000:026e", 1, "code1_embedded_word_026e");
defineWords("1008:0000", 1, "code_segment_2_tag");
defineWords("1008:0f2c", 1, "code2_embedded_word_0f2c");
defineWords("1008:11bd", 4, "code2_embedded_words_11bd");
defineWords("1008:131e", 4, "code2_embedded_words_131e");
defineWords("1010:0000", 1, "code_segment_3_tag");
defineWords("1010:0470", 1, "code3_embedded_word_0470");
defineWords("1018:0000", 1, "code_segment_4_tag");
defineWords("1020:0000", 1, "code_segment_5_tag");
defineBytes("1020:0107", 38, "borland_runtime_copyright");
defineBytes("1020:1191", 42, "real48_table_1191");
defineBytes("1020:1240", 36, "real48_table_1240");
defineBytes("1020:12d7", 48, "real48_table_12d7");
defineBytes("1020:13e8", 78, "real48_table_13e8");
seed("1000:eaB1", "exported_ordinal_10");
seed("1000:fd85", "ne_program_entry");
// Ghidra's NE entry analysis misses a set of unreferenced Borland
// runtime routines in Code5. These starts are backed by complete x86
// prologue/control-flow/return sequences in the target bytes.
seed("1020:0195", "runtime_1020_0195");
seed("1020:03c5", "runtime_1020_03c5");
seed("1020:046c", "runtime_1020_046c");
seed("1020:048f", "runtime_1020_048f");
seed("1020:049f", "runtime_1020_049f");
seed("1020:04fc", "runtime_1020_04fc");
seed("1020:0710", "runtime_1020_0710");
seed("1020:0760", "runtime_1020_0760");
seed("1020:0796", "runtime_1020_0796");
seed("1020:0866", "runtime_1020_0866");
seed("1020:098c", "runtime_1020_098c");
seed("1020:09af", "runtime_1020_09af");
seed("1020:09d2", "runtime_1020_09d2");
seed("1020:0aee", "runtime_1020_0aee");
seed("1020:103b", "runtime_1020_103b");
seed("1020:1045", "runtime_1020_1045");
seed("1020:1059", "runtime_1020_1059");
seed("1020:10aa", "runtime_1020_10aa");
seed("1020:111d", "runtime_1020_111d");
seed("1020:11bb", "runtime_1020_11bb");
seed("1020:1264", "runtime_1020_1264");
seed("1020:1307", "runtime_1020_1307");
seed("1020:1436", "runtime_1020_1436");
seed("1020:14de", "runtime_1020_14de");
seed("1020:1539", "runtime_1020_1539");
seed("1020:16dd", "runtime_1020_16dd");
// These are proven code bytes that either complete an existing
// function or form an alternate entry sharing a tail with one. Ghidra
// does not permit all such Borland entry variants to be separate,
// overlapping function bodies, but the instruction bytes still belong
// in executable coverage and the reconstruction ledger.
disassembleOnly("1018:012f");
disassembleOnly("1020:0531");
disassembleOnly("1020:0582");
disassembleOnly("1020:0666");
disassembleOnly("1020:06b4");
disassembleOnly("1020:0767");
disassembleOnly("1020:079d");
disassembleOnly("1020:100f");
analyzeAll(currentProgram);
}
private void defineWords(String text, int count, String name) throws Exception {
Address address = toAddr(text);
if (getDataAt(address) == null) {
createData(address, new ArrayDataType(WordDataType.dataType, count, 2));
}
createLabel(address, name, true);
println("Defined " + count + " word(s) at " + address + " as " + name);
}
private void defineBytes(String text, int count, String name) throws Exception {
Address address = toAddr(text);
if (getDataAt(address) == null) {
createData(address, new ArrayDataType(ByteDataType.dataType, count, 1));
}
createLabel(address, name, true);
println("Defined " + count + " byte(s) at " + address + " as " + name);
}
private void disassembleOnly(String text) {
Address address = toAddr(text);
disassemble(address);
println("Disassembled shared/tail entry at " + address);
}
private void seed(String text, String name) throws Exception {
Address address = toAddr(text);
disassemble(address);
Function function = getFunctionAt(address);
if (function == null) {
function = createFunction(address, name);
}
if (function != null && !name.equals(function.getName())) {
function.setName(name, SourceType.USER_DEFINED);
}
println("Seeded " + name + " at " + address);
}
}