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
This commit is contained in:
2026-08-23 16:41:17 +02:00
parent aef404c834
commit 8b99e9607c
253 changed files with 79031 additions and 51 deletions
@@ -0,0 +1,139 @@
// Apply Wine-11.15-backed Win16 import prototypes before decompilation.
// @category TDKPIN
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.data.ByteDataType;
import ghidra.program.model.data.CharDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.IntegerDataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.data.ShortDataType;
import ghidra.program.model.data.UnsignedIntegerDataType;
import ghidra.program.model.data.UnsignedShortDataType;
import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.listing.ParameterImpl;
import ghidra.program.model.listing.ReturnParameterImpl;
import ghidra.program.model.symbol.SourceType;
public class ApplyWin16ImportSignatures extends GhidraScript {
private static final Pattern PROTOTYPE = Pattern.compile(
"^pascal16(?<flags>(?: -\\S+)*) [A-Za-z0-9_@]+\\((?<arguments>.*)\\)$");
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args.length != 1) {
throw new IllegalArgumentException(
"usage: ApplyWin16ImportSignatures.java IMPORT_RECONSTRUCTION.tsv");
}
int applied = 0;
int dataExports = 0;
int registerExports = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(args[0]))) {
String header = reader.readLine();
if (header == null) {
throw new IllegalArgumentException("empty import ledger");
}
Map<String, Integer> columns = columns(header);
String line;
while ((line = reader.readLine()) != null && !monitor.isCancelled()) {
String[] fields = line.split("\\t", -1);
String status = field(fields, columns, "status");
if ("runtime-data".equals(status)) {
dataExports++;
continue;
}
String address = field(fields, columns, "address");
String prototype = field(fields, columns, "prototype");
Function function = getFunctionAt(toAddr(address));
if (function == null) {
throw new IllegalStateException("missing imported function at " + address);
}
if (prototype.startsWith("pascal16 -register ")) {
registerExports++;
continue;
}
apply(function, prototype);
applied++;
}
}
analyzeAll(currentProgram);
println("Applied " + applied + " Win16 import signatures; skipped " + dataExports +
" runtime-data and " + registerExports + " register-entry slots");
}
private void apply(Function function, String prototype) throws Exception {
Matcher matcher = PROTOTYPE.matcher(prototype);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"unsupported prototype for " + function.getEntryPoint() + ": " + prototype);
}
List<Parameter> parameters = new ArrayList<>();
String arguments = matcher.group("arguments").trim();
if (!arguments.isEmpty()) {
String[] tokens = arguments.contains(",") ? arguments.split(",") : arguments.split("\\s+");
for (int index = 0; index < tokens.length; index++) {
String token = tokens[index].trim();
String[] words = token.split("\\s+");
DataType type = type(words[0]);
String name = words.length > 1 ? words[words.length - 1] : "arg" + (index + 1);
parameters.add(new ParameterImpl(name, type, currentProgram));
}
}
boolean ret16 = matcher.group("flags").contains("-ret16");
DataType returnType = ret16 ? UnsignedShortDataType.dataType
: UnsignedIntegerDataType.dataType;
ReturnParameterImpl returnParameter = new ReturnParameterImpl(returnType, currentProgram);
function.updateFunction("__stdcall16far", returnParameter,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.USER_DEFINED,
parameters.toArray(Parameter[]::new));
}
private DataType type(String name) {
return switch (name) {
case "byte" -> ByteDataType.dataType;
case "s_byte" -> ByteDataType.dataType;
case "word", "HWND16" -> UnsignedShortDataType.dataType;
case "s_word" -> ShortDataType.dataType;
case "long" -> UnsignedIntegerDataType.dataType;
case "s_long" -> IntegerDataType.dataType;
case "ptr", "segptr" ->
new PointerDataType(VoidDataType.dataType, 4, currentProgram.getDataTypeManager());
case "str", "segstr" ->
new PointerDataType(CharDataType.dataType, 4, currentProgram.getDataTypeManager());
default -> throw new IllegalArgumentException("unsupported Win16 argument type: " + name);
};
}
private Map<String, Integer> columns(String header) {
Map<String, Integer> result = new HashMap<>();
String[] names = header.split("\\t", -1);
for (int index = 0; index < names.length; index++) {
result.put(names[index], index);
}
return result;
}
private String field(String[] fields, Map<String, Integer> columns, String name) {
Integer index = columns.get(name);
if (index == null || index >= fields.length) {
throw new IllegalArgumentException("missing import-ledger column: " + name);
}
return fields[index];
}
}
+4 -4
View File
@@ -64,7 +64,7 @@ public class AuditCoverage extends GhidraScript {
AddressSet explained = instructions.union(definedData);
AddressSet undefined = executable.subtract(explained);
out.printf("summary\texecutable-bytes\t\t%d\t%n", executable.getNumAddresses());
out.printf("summary\texecutable-bytes\t\t%d\tn/a%n", executable.getNumAddresses());
out.printf("summary\tdisassembled-bytes\t\t%d\t%.2f%% of executable%n",
instructions.getNumAddresses(), percent(instructions, executable));
out.printf("summary\tdefined-data-bytes\t\t%d\t%.2f%% of executable%n",
@@ -73,10 +73,10 @@ public class AuditCoverage extends GhidraScript {
explained.getNumAddresses(), percent(explained, executable));
out.printf("summary\tfunction-body-bytes\t\t%d\t%.2f%% of executable%n",
functionBodies.intersect(executable).getNumAddresses(), percent(functionBodies, executable));
out.printf("summary\tinternal-functions\t\t%d\t%n", internalFunctions);
out.printf("summary\tundefined-executable-bytes\t\t%d\t%n", undefined.getNumAddresses());
out.printf("summary\tinternal-functions\t\t%d\tn/a%n", internalFunctions);
out.printf("summary\tundefined-executable-bytes\t\t%d\tn/a%n", undefined.getNumAddresses());
for (var range : undefined) {
out.printf("undefined-range\t%s\t%s\t%d\t%n", range.getMinAddress(),
out.printf("undefined-range\t%s\t%s\t%d\tnot-in-listing%n", range.getMinAddress(),
range.getMaxAddress(), range.getLength());
}
}
+4 -2
View File
@@ -59,9 +59,11 @@ public class ExportDecompilation extends GhidraScript {
DecompileResults result = decompiler.decompileFunction(function, 120, monitor);
c.printf("/* ===== %s %s ===== */%n", address, name);
if (result.decompileCompleted() && result.getDecompiledFunction() != null) {
c.println(result.getDecompiledFunction().getC());
for (String line : result.getDecompiledFunction().getC().split("\\R", -1)) {
c.println(line.stripTrailing());
}
succeeded++;
ledger.printf("%s\t%s\tinternal\tok\t%n", address, name);
ledger.printf("%s\t%s\tinternal\tok\tdecompiled%n", address, name);
} else {
String message = result.getErrorMessage();
c.printf("/* DECOMPILATION FAILED: %s */%n%n", message);
+210
View File
@@ -0,0 +1,210 @@
// Export function metadata, references, defined data, symbols, and unexplained
// executable bytes for an auditable TDKPIN reconstruction ledger.
// @category TDKPIN
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.PrintWriter;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
public class ExportProgramEvidence extends GhidraScript {
@Override
public void run() throws Exception {
String[] args = getScriptArgs();
if (args.length != 5) {
throw new IllegalArgumentException(
"usage: ExportProgramEvidence.java FUNCTIONS REFERENCES DATA SYMBOLS UNEXPLAINED");
}
exportFunctions(args[0]);
exportReferences(args[1]);
exportDefinedData(args[2]);
exportSymbols(args[3]);
exportUnexplainedExecutableBytes(args[4]);
}
private void exportFunctions(String path) throws Exception {
try (PrintWriter out = writer(path)) {
out.println("address\tname\tnamespace\tblock\tbody_bytes\tentry_kind\tcalling_convention"
+ "\tstack_purge_bytes\treturn_type\tparameter_count\tvarargs\tthunk");
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
while (functions.hasNext() && !monitor.isCancelled()) {
Function function = functions.next();
Address address = function.getEntryPoint();
MemoryBlock block = currentProgram.getMemory().getBlock(address);
boolean imported = function.isExternal() || block == null ||
!block.isInitialized() || "EXTERNAL".equals(block.getName());
String kind = imported ? "external" : "internal";
DataType returnType = function.getReturnType();
out.printf("%s\t%s\t%s\t%s\t%d\t%s\t%s\t%d\t%s\t%d\t%s\t%s%n",
address,
clean(function.getName()),
clean(function.getParentNamespace().getName()),
block == null ? "" : clean(block.getName()),
function.getBody().getNumAddresses(),
kind,
clean(function.getCallingConventionName()),
function.getStackPurgeSize(),
returnType == null ? "" : clean(returnType.getDisplayName()),
function.getParameterCount(),
function.hasVarArgs(),
function.isThunk());
}
}
}
private void exportReferences(String path) throws Exception {
try (PrintWriter out = writer(path)) {
out.println("from_address\tfrom_function\treference_type\toperand_index\tprimary"
+ "\tto_address\tto_block\tto_function\tto_symbol");
ReferenceIterator references = currentProgram.getReferenceManager()
.getReferenceIterator(currentProgram.getMinAddress());
while (references.hasNext() && !monitor.isCancelled()) {
Reference reference = references.next();
Address from = reference.getFromAddress();
Address to = reference.getToAddress();
Function fromFunction = currentProgram.getFunctionManager().getFunctionContaining(from);
Function toFunction = currentProgram.getFunctionManager().getFunctionAt(to);
MemoryBlock toBlock = currentProgram.getMemory().getBlock(to);
Symbol toSymbol = currentProgram.getSymbolTable().getPrimarySymbol(to);
out.printf("%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s%n",
from,
fromFunction == null ? "" : clean(fromFunction.getName()),
clean(reference.getReferenceType().toString()),
reference.getOperandIndex(),
reference.isPrimary(),
to,
toBlock == null ? "" : clean(toBlock.getName()),
toFunction == null ? "" : clean(toFunction.getName()),
toSymbol == null ? "-" : clean(toSymbol.getName()));
}
}
}
private void exportDefinedData(String path) throws Exception {
try (PrintWriter out = writer(path)) {
out.println("address\tblock\tlength\tdata_type\tlabel\tvalue");
var dataIterator = currentProgram.getListing().getDefinedData(true);
while (dataIterator.hasNext() && !monitor.isCancelled()) {
Data data = dataIterator.next();
MemoryBlock block = currentProgram.getMemory().getBlock(data.getAddress());
Symbol symbol = currentProgram.getSymbolTable().getPrimarySymbol(data.getAddress());
out.printf("%s\t%s\t%d\t%s\t%s\t%s%n",
data.getAddress(),
block == null ? "" : clean(block.getName()),
data.getLength(),
clean(data.getDataType().getDisplayName()),
symbol == null ? "" : clean(symbol.getName()),
data.getDefaultValueRepresentation().isEmpty()
? "-" : clean(data.getDefaultValueRepresentation()));
}
}
}
private void exportSymbols(String path) throws Exception {
try (PrintWriter out = writer(path)) {
out.println("address\tblock\tname\tsymbol_type\tsource\tprimary\tdynamic");
SymbolIterator symbols = currentProgram.getSymbolTable().getAllSymbols(true);
while (symbols.hasNext() && !monitor.isCancelled()) {
Symbol symbol = symbols.next();
Address address = symbol.getAddress();
MemoryBlock block = currentProgram.getMemory().getBlock(address);
out.printf("%s\t%s\t%s\t%s\t%s\t%s\t%s%n",
address,
block == null ? "" : clean(block.getName()),
clean(symbol.getName()),
clean(symbol.getSymbolType().toString()),
clean(symbol.getSource().toString()),
symbol.isPrimary(),
symbol.isDynamic());
}
}
}
private void exportUnexplainedExecutableBytes(String path) throws Exception {
AddressSet executable = new AddressSet();
AddressSet explained = new AddressSet();
for (MemoryBlock block : currentProgram.getMemory().getBlocks()) {
if (block.isExecute()) {
executable.add(block.getStart(), block.getEnd());
}
}
InstructionIterator instructions = currentProgram.getListing().getInstructions(executable, true);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
explained.add(instruction.getMinAddress(), instruction.getMaxAddress());
}
var dataIterator = currentProgram.getListing().getDefinedData(executable, true);
while (dataIterator.hasNext()) {
Data data = dataIterator.next();
explained.add(data.getMinAddress(), data.getMaxAddress());
}
AddressSet unexplained = executable.subtract(explained);
try (PrintWriter out = writer(path)) {
out.println("start\tend\tbytes\thex\tclassification\tevidence");
for (AddressRange range : unexplained) {
int length = Math.toIntExact(range.getLength());
byte[] bytes = new byte[length];
currentProgram.getMemory().getBytes(range.getMinAddress(), bytes);
String[] classification = classifyUnexplained(range);
out.printf("%s\t%s\t%d\t%s\t%s\t%s%n",
range.getMinAddress(), range.getMaxAddress(), length, hex(bytes),
classification[0], classification[1]);
}
}
}
private String[] classifyUnexplained(AddressRange range) {
String start = range.getMinAddress().toString();
String end = range.getMaxAddress().toString();
if ("1020:0666".equals(start) && start.equals(end)) {
return new String[] {
"overlapping-entry-opcode",
"BA 33 D2 is mov dx,0xd233 at 0666; entry 0667 intentionally decodes 33 D2 as xor dx,dx"
};
}
if ("1020:0dea".equals(start) && start.equals(end)) {
return new String[] {
"alignment",
"single NOP between the 0d97 and 0e93 Borland runtime routines"
};
}
return new String[] {"unclassified", ""};
}
private PrintWriter writer(String path) throws Exception {
return new PrintWriter(new BufferedWriter(new FileWriter(path)));
}
private String clean(String value) {
if (value == null) {
return "";
}
return value.replace('\t', ' ').replace('\r', ' ').replace('\n', ' ');
}
private String hex(byte[] bytes) {
StringBuilder result = new StringBuilder(bytes.length * 2);
for (byte value : bytes) {
result.append(String.format("%02x", value & 0xff));
}
return result.toString();
}
}
@@ -3,17 +3,107 @@
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);
+836
View File
@@ -0,0 +1,836 @@
#!/usr/bin/env python3
"""Audit the exact binary-to-readable-C reconstruction boundary for TDKPIN."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import re
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TARGET_SHA256 = "a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c"
PRESERVED_RAW_SHA256 = (
"987a47580ba5c92fa071c989bdaeddf138e285d2b07106c5909a4acbc3e27088"
)
LEDGER_COLUMNS = (
"address",
"raw_name",
"segment",
"body_bytes",
"status",
"reconstructed_name",
"module",
"evidence",
"notes",
)
STATUS_ORDER = ("raw", "partial", "restored", "verified", "unknown", "blocked")
ALLOWED_STATUSES = set(STATUS_ORDER)
RAW_MARKER = re.compile(r"^/\* ===== ([0-9a-f]{4}:[0-9a-f]{4}) (\S+) ===== \*/$")
IMPORT_COLUMNS = (
"address",
"library",
"import_name",
"stack_purge_bytes",
"call_sites",
"status",
"stage",
"prototype",
"evidence",
"notes",
)
SHARED_ENTRY_COLUMNS = (
"address",
"containing_function",
"status",
"reconstructed_name",
"evidence",
"notes",
)
CODE_DATA_COLUMNS = (
"start",
"end",
"bytes",
"status",
"object_id",
"object_name",
"c_type",
"initialization",
"readers",
"evidence",
"notes",
)
# These routines have readable companion fragments, but none yet has a complete,
# standalone, binary-faithful implementation. Keep them partial until the whole
# raw function has been reviewed and all callees/types are resolved.
PARTIAL_FUNCTIONS = {}
CALLBACK_APIS = {
"REGISTERCLASS",
"SYSTEMTIMERMAKE",
"DIALOGBOXPARAM",
"CREATEDIALOGPARAM",
"MAKEPROCINSTANCE",
}
def read_tsv(path: Path) -> list[dict[str, str]]:
with path.open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def internal_functions() -> list[dict[str, str]]:
rows = read_tsv(ROOT / "BINARY_FUNCTIONS.tsv")
return [row for row in rows if row["entry_kind"] == "internal"]
def initialize_ledger(path: Path) -> None:
if path.exists():
raise SystemExit(f"refusing to overwrite existing ledger: {path}")
rows = []
for function in internal_functions():
partial = PARTIAL_FUNCTIONS.get(function["address"])
rows.append(
{
"address": function["address"],
"raw_name": function["name"],
"segment": function["block"],
"body_bytes": function["body_bytes"],
"status": "partial" if partial else "raw",
"reconstructed_name": partial[0] if partial else "",
"module": partial[1] if partial else "",
"evidence": partial[2] if partial else "decompiler-only",
"notes": (
"Readable fragment exists; full function and dependencies remain pending."
if partial
else "No reviewed readable-C implementation yet."
),
}
)
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=LEDGER_COLUMNS, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
def sync_ledger(path: Path) -> None:
"""Add newly recovered functions while preserving manual semantic work."""
if not path.exists():
raise SystemExit(f"cannot sync missing ledger: {path}")
existing = {row["address"]: row for row in read_tsv(path)}
functions = internal_functions()
current_addresses = {function["address"] for function in functions}
removed = set(existing) - current_addresses
if removed:
raise SystemExit(
"refusing to discard ledger rows: " + ", ".join(sorted(removed))
)
rows = []
for function in functions:
row = existing.get(function["address"])
if row is None:
row = {
"address": function["address"],
"status": "raw",
"reconstructed_name": "",
"module": "",
"evidence": "decompiler-only",
"notes": "Newly recovered function; no reviewed readable-C implementation yet.",
}
row["raw_name"] = function["name"]
row["segment"] = function["block"]
row["body_bytes"] = function["body_bytes"]
rows.append(row)
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=LEDGER_COLUMNS, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(path)
def write_tsv(
path: Path, columns: tuple[str, ...], rows: list[dict[str, object]]
) -> None:
if path.exists():
raise SystemExit(f"refusing to overwrite existing ledger: {path}")
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
def initialize_support_ledgers() -> None:
functions = read_tsv(ROOT / "BINARY_FUNCTIONS.tsv")
references = read_tsv(ROOT / "REFERENCES.tsv")
calls_by_target = Counter(
row["to_address"] for row in references if "CALL" in row["reference_type"]
)
imports = [row for row in functions if row["entry_kind"] == "external"]
write_tsv(
ROOT / "IMPORT_RECONSTRUCTION.tsv",
IMPORT_COLUMNS,
[
{
"address": row["address"],
"library": row["namespace"],
"import_name": row["name"],
"stack_purge_bytes": row["stack_purge_bytes"],
"call_sites": calls_by_target[row["address"]],
"status": "unknown",
"stage": "untyped-import",
"prototype": "",
"evidence": "NE import+Ghidra reference export",
"notes": "Win16 prototype and every call site remain to be reviewed.",
}
for row in imports
],
)
data_references: dict[str, list[dict[str, str]]] = {}
for row in references:
if row["to_block"] == "Data6":
data_references.setdefault(row["to_address"], []).append(row)
write_tsv(
ROOT / "DATA_RECONSTRUCTION.tsv",
(
"address",
"status",
"object_name",
"type",
"extent_start",
"extent_end",
"reads",
"writes",
"referring_functions",
"initialization",
"evidence",
"notes",
),
[
{
"address": address,
"status": "unknown",
"stage": "candidate-address",
"object_name": next(
(row["to_symbol"] for row in rows if row["to_symbol"]), ""
),
"type": "",
"extent_start": address,
"extent_end": address,
"reads": sum("READ" in row["reference_type"] for row in rows),
"writes": sum("WRITE" in row["reference_type"] for row in rows),
"referring_functions": ",".join(
sorted(
{row["from_function"] for row in rows if row["from_function"]}
)
),
"initialization": "",
"evidence": "Ghidra DGROUP references",
"notes": "Candidate address; semantic object boundary and type remain unresolved.",
}
for address, rows in sorted(data_references.items())
],
)
manifest = json.loads((ROOT / "assets/manifest.json").read_text(encoding="utf-8"))
write_tsv(
ROOT / "RESOURCE_RECONSTRUCTION.tsv",
(
"type",
"id",
"file_offset",
"allocated_size",
"raw_sha256",
"raw_path",
"decoded_paths",
"status",
"users",
"behavior",
"notes",
),
[
{
"type": resource["type"],
"id": resource["id"],
"file_offset": resource["file_offset"],
"allocated_size": resource["allocated_size"],
"raw_sha256": resource["raw_sha256"],
"raw_path": resource["raw_path"],
"decoded_paths": ",".join(resource.get("decoded_paths", [])),
"status": "partial",
"stage": "extracted-unmapped",
"users": "",
"behavior": "",
"notes": "Lossless extraction is verified; code users and behavior remain to be mapped.",
}
for resource in manifest["resources"]
],
)
callback_rows = []
for row in references:
if row["to_symbol"] in CALLBACK_APIS and "CALL" in row["reference_type"]:
callback_rows.append(
{
"registration_site": row["from_address"],
"registering_function": row["from_function"],
"api": row["to_symbol"],
"status": "unknown",
"stage": "registration-site",
"callback_address": "",
"callback_name": "",
"lifetime": "",
"evidence": "Ghidra call reference",
"notes": "Recover callback argument through Win16 ABI and stored state.",
}
)
write_tsv(
ROOT / "CALLBACK_RECONSTRUCTION.tsv",
(
"registration_site",
"registering_function",
"api",
"status",
"stage",
"callback_address",
"callback_name",
"lifetime",
"evidence",
"notes",
),
callback_rows,
)
def sync_support_ledgers() -> None:
path = ROOT / "IMPORT_RECONSTRUCTION.tsv"
if not path.exists():
raise SystemExit(f"cannot sync missing ledger: {path}")
existing = {row["address"]: row for row in read_tsv(path)}
import_statuses = {
"unreviewed": ("unknown", "untyped-import"),
"prototype-sourced": ("partial", "prototype-sourced"),
"binary-reviewed": ("verified", "binary-reviewed"),
"runtime-data": ("verified", "runtime-data"),
}
for row in existing.values():
if not row.get("stage"):
row["status"], row["stage"] = import_statuses.get(
row["status"], (row["status"], row["status"])
)
functions = read_tsv(ROOT / "BINARY_FUNCTIONS.tsv")
imports = [row for row in functions if row["entry_kind"] == "external"]
references = read_tsv(ROOT / "REFERENCES.tsv")
calls_by_target = Counter(
row["to_address"] for row in references if "CALL" in row["reference_type"]
)
current_addresses = {row["address"] for row in imports}
removed = set(existing) - current_addresses
if removed:
raise SystemExit(
"refusing to discard import-ledger rows: " + ", ".join(sorted(removed))
)
rows = []
for function in imports:
row = existing.get(function["address"])
if row is None:
row = {
"address": function["address"],
"status": "unknown",
"stage": "untyped-import",
"prototype": "",
"evidence": "NE import+Ghidra reference export",
"notes": "Win16 prototype and every call site remain to be reviewed.",
}
row["library"] = function["namespace"]
row["import_name"] = function["name"]
row["stack_purge_bytes"] = function["stack_purge_bytes"]
row["call_sites"] = calls_by_target[function["address"]]
rows.append(row)
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=IMPORT_COLUMNS, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(path)
normalize_support_statuses(
ROOT / "DATA_RECONSTRUCTION.tsv",
{"unresolved": ("unknown", "candidate-address")},
)
normalize_support_statuses(
ROOT / "RESOURCE_RECONSTRUCTION.tsv",
{"extracted-unmapped": ("partial", "extracted-unmapped")},
)
normalize_support_statuses(
ROOT / "CALLBACK_RECONSTRUCTION.tsv",
{"unresolved": ("unknown", "registration-site")},
)
def normalize_support_statuses(path: Path, mapping: dict[str, tuple[str, str]]) -> None:
with path.open(newline="", encoding="utf-8") as handle:
reader = csv.DictReader(handle, delimiter="\t")
columns = list(reader.fieldnames or ())
rows = list(reader)
if "stage" not in columns:
columns.insert(columns.index("status") + 1, "stage")
for row in rows:
if not row.get("stage"):
row["status"], row["stage"] = mapping.get(
row["status"], (row["status"], row["status"])
)
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(path)
def require(condition: bool, message: str, failures: list[str]) -> None:
if not condition:
failures.append(message)
def status_summary(rows: list[dict[str, str]]) -> str:
counts = Counter(row["status"] for row in rows)
return ",".join(f"{status}:{counts[status]}" for status in STATUS_ORDER)
def dgroup_offset(address: str) -> int:
selector, value = address.split(":", 1)
if selector != "1028":
raise ValueError(f"not a DGROUP address: {address}")
return int(value, 16)
def audit(require_complete: bool = False) -> int:
failures: list[str] = []
digest = hashlib.sha256((ROOT / "TDKPIN.EXE").read_bytes()).hexdigest()
require(digest == TARGET_SHA256, f"target SHA-256 changed: {digest}", failures)
preserved_raw_digest = hashlib.sha256(
(ROOT / "TDKPIN_GHIDRA_RAW.c").read_bytes()
).hexdigest()
require(
preserved_raw_digest == PRESERVED_RAW_SHA256,
f"preserved raw decompiler evidence changed: {preserved_raw_digest}",
failures,
)
functions = internal_functions()
function_by_address = {row["address"]: row for row in functions}
require(
len(function_by_address) == len(functions),
"duplicate binary function addresses",
failures,
)
raw_markers: dict[str, str] = {}
for line in (
(ROOT / "TDKPIN_GHIDRA_COMPLETE_RAW.c").read_text(encoding="utf-8").splitlines()
):
match = RAW_MARKER.match(line)
if match:
raw_markers[match.group(1)] = match.group(2)
require(
set(raw_markers) == set(function_by_address),
"raw decompilation/function inventory mismatch",
failures,
)
ledger_path = ROOT / "FUNCTION_RECONSTRUCTION.tsv"
require(ledger_path.exists(), "FUNCTION_RECONSTRUCTION.tsv is missing", failures)
ledger = read_tsv(ledger_path) if ledger_path.exists() else []
ledger_by_address = {row["address"]: row for row in ledger}
require(
len(ledger_by_address) == len(ledger),
"duplicate reconstruction-ledger addresses",
failures,
)
require(
set(ledger_by_address) == set(function_by_address),
"reconstruction ledger/function inventory mismatch",
failures,
)
for address, row in ledger_by_address.items():
function = function_by_address[address]
require(
row["status"] in ALLOWED_STATUSES,
f"{address}: invalid status {row['status']!r}",
failures,
)
require(
row["raw_name"] == function["name"], f"{address}: raw name drift", failures
)
require(
row["segment"] == function["block"], f"{address}: segment drift", failures
)
require(
row["body_bytes"] == function["body_bytes"],
f"{address}: body-size drift",
failures,
)
if row["status"] in {"restored", "verified"}:
require(
bool(row["reconstructed_name"]),
f"{address}: restored row lacks a name",
failures,
)
require(
bool(row["module"]), f"{address}: restored row lacks a module", failures
)
require(
(ROOT / row["module"]).is_file(),
f"{address}: module does not exist",
failures,
)
coverage = read_tsv(ROOT / "COMPLETE_COVERAGE.tsv")
summaries = {
row["name_or_start"]: row["bytes"]
for row in coverage
if row["record"] == "summary"
}
executable_exceptions = read_tsv(ROOT / "UNEXPLAINED.tsv")
executable_exception_bytes = sum(
int(row["bytes"]) for row in executable_exceptions
)
unclassified = [
row
for row in executable_exceptions
if row["classification"] == "unclassified"
]
require(
executable_exception_bytes ==
int(summaries["undefined-executable-bytes"]),
"exception-byte evidence does not match coverage summary",
failures,
)
references = read_tsv(ROOT / "REFERENCES.tsv")
data_targets = {
row["to_address"] for row in references if row["to_block"] == "Data6"
}
objects = read_tsv(ROOT / "OBJECTS.tsv")
require(
len(objects) == 175,
f"expected 175 collision records, found {len(objects)}",
failures,
)
support_ledgers = (
"IMPORT_RECONSTRUCTION.tsv",
"DATA_RECONSTRUCTION.tsv",
"DATA_COVERAGE.tsv",
"DATA_OBJECTS.tsv",
"CODE_DATA_OBJECTS.tsv",
"RESOURCE_RECONSTRUCTION.tsv",
"CALLBACK_RECONSTRUCTION.tsv",
"NE_SEGMENTS.tsv",
"ENTRY_POINTS.tsv",
"MODULE_REFERENCES.tsv",
"RELOCATIONS.tsv",
"SHARED_ENTRY_POINTS.tsv",
)
for name in support_ledgers:
require((ROOT / name).exists(), f"{name} is missing", failures)
shared_entries = read_tsv(ROOT / "SHARED_ENTRY_POINTS.tsv")
for row in shared_entries:
require(
tuple(row) == SHARED_ENTRY_COLUMNS,
f"shared entry {row.get('address', '?')}: schema drift",
failures,
)
require(
row["status"] in ALLOWED_STATUSES,
f"shared entry {row['address']}: invalid status {row['status']!r}",
failures,
)
require(
row["containing_function"] in function_by_address,
f"shared entry {row['address']}: unknown containing function",
failures,
)
require(
bool(row["reconstructed_name"]),
f"shared entry {row['address']}: missing readable name",
failures,
)
import_ledger: list[dict[str, str]] = []
data_ledger: list[dict[str, str]] = []
data_coverage: list[dict[str, str]] = []
data_objects: list[dict[str, str]] = []
code_data_objects: list[dict[str, str]] = []
resource_ledger: list[dict[str, str]] = []
if ROOT.joinpath("IMPORT_RECONSTRUCTION.tsv").exists():
import_ledger = read_tsv(ROOT / "IMPORT_RECONSTRUCTION.tsv")
external_addresses = {
row["address"]
for row in read_tsv(ROOT / "BINARY_FUNCTIONS.tsv")
if row["entry_kind"] == "external"
}
require(
{row["address"] for row in import_ledger} == external_addresses,
"import ledger/inventory mismatch",
failures,
)
for row in import_ledger:
require(
row["status"] in ALLOWED_STATUSES,
f"import {row['address']}: invalid status {row['status']!r}",
failures,
)
if ROOT.joinpath("DATA_RECONSTRUCTION.tsv").exists():
data_ledger = read_tsv(ROOT / "DATA_RECONSTRUCTION.tsv")
require(
{row["address"] for row in data_ledger} == data_targets,
"data ledger/reference-target mismatch",
failures,
)
for row in data_ledger:
require(
row["status"] in ALLOWED_STATUSES,
f"data {row['address']}: invalid status {row['status']!r}",
failures,
)
if ROOT.joinpath("DATA_COVERAGE.tsv").exists():
data_coverage = read_tsv(ROOT / "DATA_COVERAGE.tsv")
next_offset = 0
for row in data_coverage:
start = dgroup_offset(row["start"])
end = dgroup_offset(row["end"])
require(
start == next_offset,
f"DGROUP coverage gap/overlap at {row['start']}",
failures,
)
require(
end >= start,
f"invalid DGROUP coverage range at {row['start']}",
failures,
)
require(
int(row["bytes"]) == end - start + 1,
f"DGROUP range length mismatch at {row['start']}",
failures,
)
require(
row["status"] in ALLOWED_STATUSES,
f"DGROUP range {row['start']}: invalid status {row['status']!r}",
failures,
)
next_offset = end + 1
require(
next_offset == 0x4D3A,
f"DGROUP coverage ends at {next_offset:04x}",
failures,
)
if ROOT.joinpath("DATA_OBJECTS.tsv").exists():
data_objects = read_tsv(ROOT / "DATA_OBJECTS.tsv")
previous_end = -1
for row in sorted(data_objects, key=lambda item: dgroup_offset(item["start"])):
start = dgroup_offset(row["start"])
end = dgroup_offset(row["end"])
require(
start > previous_end,
f"overlapping data object at {row['start']}",
failures,
)
require(
row["status"] in ALLOWED_STATUSES,
f"data object {row['object_id']}: invalid status {row['status']!r}",
failures,
)
previous_end = end
if ROOT.joinpath("CODE_DATA_OBJECTS.tsv").exists():
code_data_objects = read_tsv(ROOT / "CODE_DATA_OBJECTS.tsv")
code_data_inventory = {
row["address"]: int(row["length"])
for row in read_tsv(ROOT / "DEFINED_DATA.tsv")
if row["block"].startswith("Code")
}
require(
{row["start"]: int(row["bytes"]) for row in code_data_objects}
== code_data_inventory,
"code-data ledger/defined-data inventory mismatch",
failures,
)
for row in code_data_objects:
require(
tuple(row) == CODE_DATA_COLUMNS,
f"code-data object {row.get('start', '?')}: schema drift",
failures,
)
start_selector, start_text = row["start"].split(":", 1)
end_selector, end_text = row["end"].split(":", 1)
require(
start_selector == end_selector,
f"code-data object {row['start']}: crosses segments",
failures,
)
require(
int(row["bytes"]) == int(end_text, 16) - int(start_text, 16) + 1,
f"code-data object {row['start']}: extent mismatch",
failures,
)
require(
row["status"] in ALLOWED_STATUSES,
f"code-data object {row['object_id']}: invalid status {row['status']!r}",
failures,
)
if ROOT.joinpath("RESOURCE_RECONSTRUCTION.tsv").exists():
resource_ledger = read_tsv(ROOT / "RESOURCE_RECONSTRUCTION.tsv")
manifest = json.loads(
(ROOT / "assets/manifest.json").read_text(encoding="utf-8")
)
resource_keys = {
(str(row["type"]), str(row["id"])) for row in manifest["resources"]
}
require(
{(row["type"], row["id"]) for row in resource_ledger} == resource_keys,
"resource ledger/manifest mismatch",
failures,
)
for row in resource_ledger:
require(
row["status"] in ALLOWED_STATUSES,
f"resource {row['type']}:{row['id']}: invalid status {row['status']!r}",
failures,
)
callback_ledger = (
read_tsv(ROOT / "CALLBACK_RECONSTRUCTION.tsv")
if ROOT.joinpath("CALLBACK_RECONSTRUCTION.tsv").exists()
else []
)
entry_points = (
read_tsv(ROOT / "ENTRY_POINTS.tsv")
if ROOT.joinpath("ENTRY_POINTS.tsv").exists()
else []
)
relocations = (
read_tsv(ROOT / "RELOCATIONS.tsv")
if ROOT.joinpath("RELOCATIONS.tsv").exists()
else []
)
require(
len(entry_points) == 301,
f"expected 301 NE entries, found {len(entry_points)}",
failures,
)
for row in callback_ledger:
require(
row["status"] in ALLOWED_STATUSES,
f"callback {row['registration_site']}: invalid status {row['status']!r}",
failures,
)
if require_complete:
require(
not unclassified, "completion requires zero unclassified ranges", failures
)
for label, rows in (
("function", ledger),
("import", import_ledger),
("data", data_ledger),
("DGROUP coverage", data_coverage),
("data object", data_objects),
("code-data object", code_data_objects),
("resource", resource_ledger),
("callback", callback_ledger),
("shared entry", shared_entries),
):
incomplete = [row for row in rows if row["status"] != "verified"]
require(
not incomplete,
f"completion requires every {label} row verified; {len(incomplete)} remain",
failures,
)
print(f"target_sha256={digest}")
print(f"preserved_raw_sha256={preserved_raw_digest}")
print(f"internal_functions={len(functions)}")
print(f"function_statuses={status_summary(ledger)}")
print(f"import_statuses={status_summary(import_ledger)}")
print(f"data_statuses={status_summary(data_ledger)}")
print(f"data_range_statuses={status_summary(data_coverage)}")
print(f"data_object_statuses={status_summary(data_objects)}")
print(f"code_data_object_statuses={status_summary(code_data_objects)}")
print(f"resource_statuses={status_summary(resource_ledger)}")
print(f"callback_statuses={status_summary(callback_ledger)}")
print(f"shared_entry_statuses={status_summary(shared_entries)}")
print(f"executable_bytes={summaries['executable-bytes']}")
print(f"classified_executable_exception_ranges={len(executable_exceptions)}")
print(f"classified_executable_exception_bytes={executable_exception_bytes}")
print(f"unclassified_executable_ranges={len(unclassified)}")
print(f"defined_data_units={len(read_tsv(ROOT / 'DEFINED_DATA.tsv'))}")
print(f"referenced_data_addresses={len(data_targets)}")
print(f"collision_records={len(objects)}")
print(f"ne_entry_points={len(entry_points)}")
print(f"relocation_sites={len(relocations)}")
if failures:
for failure in failures:
print(f"FAIL: {failure}")
return 1
print("ledger_consistency=pass")
return 0
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--initialize", action="store_true", help="create the initial function ledger"
)
parser.add_argument(
"--sync",
action="store_true",
help="add newly recovered functions to the ledger",
)
parser.add_argument(
"--initialize-support-ledgers",
action="store_true",
help="create import, data, resource, and callback ledgers",
)
parser.add_argument(
"--sync-support-ledgers",
action="store_true",
help="refresh mechanically derived support-ledger fields",
)
parser.add_argument(
"--require-complete",
action="store_true",
help="fail unless every required ledger row is verified",
)
args = parser.parse_args()
if args.initialize:
initialize_ledger(ROOT / "FUNCTION_RECONSTRUCTION.tsv")
if args.sync:
sync_ledger(ROOT / "FUNCTION_RECONSTRUCTION.tsv")
if args.initialize_support_ledgers:
initialize_support_ledgers()
if args.sync_support_ledgers:
sync_support_ledgers()
return audit(args.require_complete)
if __name__ == "__main__":
raise SystemExit(main())
+314
View File
@@ -0,0 +1,314 @@
#!/usr/bin/env python3
"""Build a byte-complete, non-overlapping DGROUP reconstruction ledger."""
from __future__ import annotations
import csv
import hashlib
from itertools import pairwise
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / "DATA_COVERAGE.tsv"
COLUMNS = (
"start",
"end",
"bytes",
"storage",
"status",
"stage",
"object_id",
"object_name",
"c_type",
"initial_value",
"readers",
"writers",
"relocations",
"evidence",
"notes",
)
RELOCATION_WIDTHS = {
"low-byte": 1,
"selector-16": 2,
"far-pointer-32": 4,
"offset-16": 2,
"offset-32": 4,
"pointer-48": 6,
}
def read_tsv(path: Path) -> list[dict[str, str]]:
with path.open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def offset(address: str) -> int:
selector, value = address.split(":", 1)
if selector != "1028":
raise ValueError(f"not a DGROUP address: {address}")
return int(value, 16)
def address(value: int) -> str:
return f"1028:{value:04x}"
def initial_value(payload: bytes, start: int, end: int, file_length: int) -> str:
if start >= file_length:
return "zero-fill"
value = payload[start : min(end + 1, file_length)]
if end + 1 > file_length:
value += bytes(end + 1 - file_length)
if len(value) <= 32:
return value.hex()
return f"sha256:{hashlib.sha256(value).hexdigest()}"
def project_semantic_objects(
objects: list[dict[str, str]],
) -> None:
path = ROOT / "DATA_RECONSTRUCTION.tsv"
rows = read_tsv(path)
with path.open(newline="", encoding="utf-8") as handle:
columns = tuple(csv.DictReader(handle, delimiter="\t").fieldnames or ())
for row in rows:
anchor = offset(row["address"])
matches = [
item
for item in objects
if offset(item["start"]) <= anchor <= offset(item["end"])
]
if len(matches) > 1:
raise ValueError(f"overlapping semantic objects at {row['address']}")
if not matches:
continue
item = matches[0]
row["status"] = item["status"]
row["stage"] = "semantic-object-field"
row["object_name"] = item["object_name"]
row["type"] = item["c_type"]
row["extent_start"] = item["start"]
row["extent_end"] = item["end"]
row["initialization"] = item["initialization"]
row["evidence"] = item["evidence"]
row["notes"] = item["notes"]
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(path)
def main() -> int:
segments = read_tsv(ROOT / "NE_SEGMENTS.tsv")
data_segment = next(row for row in segments if row["segment"] == "6")
file_offset = int(data_segment["file_offset"])
file_length = int(data_segment["file_length"])
allocation_size = int(data_segment["allocation_size"])
binary = (ROOT / "TDKPIN.EXE").read_bytes()
payload = binary[file_offset : file_offset + file_length]
references = [
row for row in read_tsv(ROOT / "REFERENCES.tsv") if row["to_block"] == "Data6"
]
defined_data = [
row for row in read_tsv(ROOT / "DEFINED_DATA.tsv") if row["block"] == "Data6"
]
relocations = [
row for row in read_tsv(ROOT / "RELOCATIONS.tsv") if row["segment"] == "6"
]
objects = (
read_tsv(ROOT / "DATA_OBJECTS.tsv")
if (ROOT / "DATA_OBJECTS.tsv").exists()
else []
)
project_semantic_objects(objects)
boundaries = {0, file_length, allocation_size}
for row in references:
boundaries.add(offset(row["to_address"]))
for row in defined_data:
start = offset(row["address"])
boundaries.add(start)
boundaries.add(min(start + int(row["length"]), allocation_size))
for row in relocations:
start = offset(row["source_address"])
width = RELOCATION_WIDTHS.get(row["source_type"])
if width is None:
raise ValueError(f"unknown relocation width: {row['source_type']}")
boundaries.add(start)
boundaries.add(min(start + width, allocation_size))
for row in objects:
start = offset(row["start"])
end = offset(row["end"])
boundaries.add(start)
boundaries.add(min(end + 1, allocation_size))
ordered = sorted(
boundary for boundary in boundaries if 0 <= boundary <= allocation_size
)
old = {}
if OUTPUT.exists():
old = {(row["start"], row["end"]): row for row in read_tsv(OUTPUT)}
rows = []
current_keys = set()
for start, next_start in pairwise(ordered):
if start == next_start:
continue
end = next_start - 1
key = (address(start), address(end))
current_keys.add(key)
row = old.get(key, {})
range_refs = [
reference
for reference in references
if start <= offset(reference["to_address"]) <= end
]
range_data = [
data for data in defined_data if start <= offset(data["address"]) <= end
]
range_relocations = [
relocation
for relocation in relocations
if start <= offset(relocation["source_address"]) <= end
]
range_objects = [
item
for item in objects
if offset(item["start"]) <= start and end <= offset(item["end"])
]
if len(range_objects) > 1:
raise ValueError(f"overlapping semantic data objects at {key[0]}-{key[1]}")
semantic_object = range_objects[0] if range_objects else None
row.update(
{
"start": key[0],
"end": key[1],
"bytes": end - start + 1,
"storage": "initialized" if end < file_length else "zero-fill",
"status": (
semantic_object["status"]
if semantic_object
else row.get("status", "unknown")
),
"stage": (
"semantic-object"
if semantic_object
else row.get("stage", "candidate-range")
),
"object_id": (
semantic_object["object_id"]
if semantic_object
else row.get("object_id", "")
),
"object_name": (
semantic_object["object_name"]
if semantic_object
else row.get("object_name", "")
),
"c_type": (
semantic_object["c_type"]
if semantic_object
else row.get("c_type", "")
),
"initial_value": initial_value(payload, start, end, file_length),
"readers": ",".join(
sorted(
{
reference["from_function"]
for reference in range_refs
if "READ" in reference["reference_type"]
and reference["from_function"]
}
)
),
"writers": ",".join(
sorted(
{
reference["from_function"]
for reference in range_refs
if "WRITE" in reference["reference_type"]
and reference["from_function"]
}
)
),
"relocations": ",".join(
f"{item['source_address']}->{item['target']}"
for item in range_relocations
),
"evidence": ";".join(
[
*([semantic_object["evidence"]] if semantic_object else []),
*[
f"{item['address']}:{item['data_type']}[{item['length']}]"
for item in range_data
],
]
),
"notes": (
semantic_object["notes"]
if semantic_object
else row.get(
"notes",
"Candidate byte range; semantic object grouping and type remain unresolved.",
)
),
}
)
rows.append(row)
lost_review = [
row
for key, row in old.items()
if key not in current_keys and row["status"] != "unknown"
]
unsafe_lost_review = []
for reviewed in lost_review:
reviewed_start = offset(reviewed["start"])
reviewed_end = offset(reviewed["end"])
replacements = [
row
for row in rows
if offset(row["end"]) >= reviewed_start
and offset(row["start"]) <= reviewed_end
]
cursor = reviewed_start
safe = True
for replacement in replacements:
replacement_start = max(offset(replacement["start"]), reviewed_start)
replacement_end = min(offset(replacement["end"]), reviewed_end)
if replacement_start != cursor or replacement["status"] != "verified" or (
replacement["stage"] != "semantic-object"
):
safe = False
break
cursor = replacement_end + 1
if not safe or cursor != reviewed_end + 1:
unsafe_lost_review.append(reviewed)
if unsafe_lost_review:
raise RuntimeError(
"refusing to discard "
f"{len(unsafe_lost_review)} reviewed data ranges after boundary drift"
)
temporary = OUTPUT.with_suffix(OUTPUT.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=COLUMNS, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(OUTPUT)
print(f"data_ranges={len(rows)}")
print(f"covered_bytes={sum(int(row['bytes']) for row in rows)}")
print(f"initialized_bytes={file_length}")
print(f"zero_fill_bytes={allocation_size - file_length}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+15 -3
View File
@@ -22,10 +22,22 @@ fi
-noanalysis \
-scriptPath "$script_dir" \
-preScript SeedMissingEntrypoints.java \
-preScript ApplyWin16ImportSignatures.java \
"$workspace_dir/IMPORT_RECONSTRUCTION.tsv" \
-postScript ExportDecompilation.java \
"$workspace_dir/TDKPIN_GHIDRA_RAW.c" \
"$workspace_dir/FUNCTIONS.tsv" \
"$workspace_dir/TDKPIN_GHIDRA_COMPLETE_RAW.c" \
"$workspace_dir/COMPLETE_FUNCTIONS.tsv" \
-postScript AuditCoverage.java \
"$workspace_dir/COVERAGE.tsv"
"$workspace_dir/COMPLETE_COVERAGE.tsv" \
-postScript ExportProgramEvidence.java \
"$workspace_dir/BINARY_FUNCTIONS.tsv" \
"$workspace_dir/REFERENCES.tsv" \
"$workspace_dir/DEFINED_DATA.tsv" \
"$workspace_dir/SYMBOLS.tsv" \
"$workspace_dir/UNEXPLAINED.tsv"
python3 "$script_dir/extract_ne_metadata.py" "$workspace_dir/TDKPIN.EXE" \
--output-dir "$workspace_dir"
python3 "$script_dir/build_data_coverage.py"
echo "Ghidra project retained at: $project_dir"
+319
View File
@@ -0,0 +1,319 @@
#!/usr/bin/env python3
"""Extract exact NE segments, entry points, modules, and relocation sites."""
from __future__ import annotations
import argparse
import csv
import struct
from dataclasses import dataclass
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SOURCE_TYPE_NAMES = {
0: "low-byte",
2: "selector-16",
3: "far-pointer-32",
5: "offset-16",
11: "offset-32",
13: "pointer-48",
}
TARGET_TYPE_NAMES = {
0: "internal",
1: "import-ordinal",
2: "import-name",
3: "os-fixup",
}
@dataclass(frozen=True)
class Segment:
number: int
file_offset: int
file_length: int
flags: int
allocation_size: int
@property
def selector(self) -> int:
return 0x1000 + (self.number - 1) * 8
def address(self, offset: int) -> str:
return f"{self.selector:04x}:{offset:04x}"
def u16(data: bytes, offset: int) -> int:
return struct.unpack_from("<H", data, offset)[0]
def pascal_string(data: bytes, offset: int) -> str:
length = data[offset]
return data[offset + 1 : offset + 1 + length].decode("latin-1")
def parse_segments(data: bytes, ne_offset: int) -> list[Segment]:
count = u16(data, ne_offset + 0x1C)
table = ne_offset + u16(data, ne_offset + 0x22)
shift = u16(data, ne_offset + 0x32)
segments = []
for index in range(count):
sector, length, flags, allocation = struct.unpack_from(
"<HHHH", data, table + index * 8
)
segments.append(
Segment(
number=index + 1,
file_offset=sector << shift,
file_length=length or 0x10000,
flags=flags,
allocation_size=allocation or 0x10000,
)
)
return segments
def parse_entries(data: bytes, ne_offset: int) -> dict[int, tuple[int, int, int, str]]:
table = ne_offset + u16(data, ne_offset + 0x04)
end = table + u16(data, ne_offset + 0x06)
ordinal = 1
entries = {}
cursor = table
while cursor < end:
count = data[cursor]
segment_indicator = data[cursor + 1]
cursor += 2
if count == 0:
break
if segment_indicator == 0:
ordinal += count
continue
for _ in range(count):
flags = data[cursor]
if segment_indicator == 0xFF:
int3f = u16(data, cursor + 1)
segment = data[cursor + 3]
offset = u16(data, cursor + 4)
kind = f"movable-int3f-{int3f:04x}"
cursor += 6
else:
segment = segment_indicator
offset = u16(data, cursor + 1)
kind = "fixed"
cursor += 3
entries[ordinal] = (segment, offset, flags, kind)
ordinal += 1
return entries
def parse_modules(data: bytes, ne_offset: int) -> list[str]:
count = u16(data, ne_offset + 0x1E)
module_table = ne_offset + u16(data, ne_offset + 0x28)
import_table = ne_offset + u16(data, ne_offset + 0x2A)
return [
pascal_string(data, import_table + u16(data, module_table + index * 2))
for index in range(count)
]
def relocation_sites(
data: bytes, segment: Segment, first: int, additive: bool
) -> list[int]:
if additive:
return [first]
sites = []
current = first
seen = set()
while current != 0xFFFF:
if current in seen:
raise ValueError(
f"segment {segment.number}: cyclic relocation chain at {current:04x}"
)
if current + 2 > segment.file_length:
raise ValueError(
f"segment {segment.number}: relocation source outside file image: {current:04x}"
)
seen.add(current)
sites.append(current)
current = u16(data, segment.file_offset + current)
return sites
def write_tsv(
path: Path, columns: tuple[str, ...], rows: list[dict[str, object]]
) -> None:
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
def extract(source: Path, output_dir: Path) -> None:
data = source.read_bytes()
ne_offset = struct.unpack_from("<I", data, 0x3C)[0]
if data[ne_offset : ne_offset + 2] != b"NE":
raise ValueError(f"not an NE executable: {source}")
segments = parse_segments(data, ne_offset)
entries = parse_entries(data, ne_offset)
modules = parse_modules(data, ne_offset)
import_table = ne_offset + u16(data, ne_offset + 0x2A)
segment_rows = []
for segment in segments:
segment_rows.append(
{
"segment": segment.number,
"selector": f"{segment.selector:04x}",
"file_offset": segment.file_offset,
"file_length": segment.file_length,
"allocation_size": segment.allocation_size,
"zero_fill_bytes": segment.allocation_size - segment.file_length,
"flags": f"0x{segment.flags:04x}",
"has_relocations": bool(segment.flags & 0x0100),
}
)
entry_rows = []
for ordinal, (segment, offset, flags, kind) in sorted(entries.items()):
selector = 0x1000 + (segment - 1) * 8
entry_rows.append(
{
"ordinal": ordinal,
"segment": segment,
"offset": f"0x{offset:04x}",
"address": f"{selector:04x}:{offset:04x}",
"flags": f"0x{flags:02x}",
"kind": kind,
}
)
module_rows = [
{"module_index": index, "module_name": name}
for index, name in enumerate(modules, 1)
]
relocation_rows = []
for segment in segments:
if not (segment.flags & 0x0100):
continue
cursor = segment.file_offset + segment.file_length
count = u16(data, cursor)
cursor += 2
for record_index in range(1, count + 1):
source_type, flags, first_source, target1, target2 = struct.unpack_from(
"<BBHHH", data, cursor
)
cursor += 8
target_type = flags & 0x03
additive = bool(flags & 0x04)
target_kind = TARGET_TYPE_NAMES.get(target_type, f"target-{target_type}")
target = ""
target_address = ""
module = ""
import_ordinal = ""
import_name = ""
if target_type == 0:
if target1 == 0x00FF:
entry = entries.get(target2)
target = f"entry-ordinal:{target2}"
if entry:
target_address = segments[entry[0] - 1].address(entry[1])
else:
target = f"segment:{target1}:offset:{target2:04x}"
target_address = segments[target1 - 1].address(target2)
elif target_type == 1:
module = modules[target1 - 1]
import_ordinal = target2
target = f"{module}.ordinal:{target2}"
elif target_type == 2:
module = modules[target1 - 1]
import_name = pascal_string(data, import_table + target2)
target = f"{module}.{import_name}"
else:
target = f"os-fixup:{target1}:{target2}"
sites = relocation_sites(data, segment, first_source, additive)
for chain_index, site in enumerate(sites):
relocation_rows.append(
{
"segment": segment.number,
"record": record_index,
"chain_index": chain_index,
"source_offset": f"0x{site:04x}",
"source_address": segment.address(site),
"source_type": SOURCE_TYPE_NAMES.get(
source_type & 0x0F, f"source-{source_type & 0x0F}"
),
"additive": additive,
"target_kind": target_kind,
"target": target,
"target_address": target_address,
"module": module,
"import_ordinal": import_ordinal,
"import_name": import_name,
"raw_flags": f"0x{flags:02x}",
}
)
write_tsv(
output_dir / "NE_SEGMENTS.tsv",
(
"segment",
"selector",
"file_offset",
"file_length",
"allocation_size",
"zero_fill_bytes",
"flags",
"has_relocations",
),
segment_rows,
)
write_tsv(
output_dir / "ENTRY_POINTS.tsv",
("ordinal", "segment", "offset", "address", "flags", "kind"),
entry_rows,
)
write_tsv(
output_dir / "MODULE_REFERENCES.tsv",
("module_index", "module_name"),
module_rows,
)
write_tsv(
output_dir / "RELOCATIONS.tsv",
(
"segment",
"record",
"chain_index",
"source_offset",
"source_address",
"source_type",
"additive",
"target_kind",
"target",
"target_address",
"module",
"import_ordinal",
"import_name",
"raw_flags",
),
relocation_rows,
)
print(f"segments={len(segment_rows)}")
print(f"entry_points={len(entry_rows)}")
print(f"module_references={len(module_rows)}")
print(f"relocation_sites={len(relocation_rows)}")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("source", nargs="?", type=Path, default=ROOT / "TDKPIN.EXE")
parser.add_argument("--output-dir", type=Path, default=ROOT)
args = parser.parse_args()
extract(args.source.resolve(), args.output_dir.resolve())
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Generate typed 1000:0270 collision-call data from preserved raw evidence."""
from __future__ import annotations
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RAW = ROOT / "TDKPIN_GHIDRA_COMPLETE_RAW.c"
OUTPUT = ROOT / "reconstructed" / "tdkpin_game_setup_collision_data.inc"
def calls_from_raw() -> list[list[int]]:
text = RAW.read_text(encoding="utf-8")
start = text.index("/* ===== 1000:0270 ")
end = text.index("/* ===== 1000:51f5 ", start)
text = text[start:end]
needle = "FUN_1008_0138("
calls: list[list[int]] = []
position = 0
while True:
begin = text.find(needle, position)
if begin < 0:
break
cursor = begin + len(needle)
depth = 1
while depth:
character = text[cursor]
if character == "(":
depth += 1
elif character == ")":
depth -= 1
cursor += 1
arguments = text[begin + len(needle) : cursor - 1]
words = [
int(item.strip(), 0) & 0xFFFF
for item in arguments.replace("\n", "").split(",")
]
if len(words) != 40:
raise RuntimeError(f"expected 40 words, found {len(words)}")
calls.append(words)
position = cursor
if len(calls) != 177:
raise RuntimeError(f"expected 177 calls, found {len(calls)}")
return calls
def u16(value: int) -> str:
return f"0x{value:04x}u"
def i32(low: int, high: int) -> str:
value = low | high << 16
return f"(int32_t)UINT32_C(0x{value:08x})"
def real48(words: list[int]) -> str:
bytes_ = []
for word in words:
bytes_.extend((word & 0xFF, word >> 8))
return "{{" + ", ".join(f"0x{byte:02x}" for byte in bytes_) + "}}"
def initializer(words: list[int], ordinal: int) -> str:
fields = [
f".render_bottom={u16(words[0])}",
f".render_right={u16(words[1])}",
f".render_top={u16(words[2])}",
f".render_left={u16(words[3])}",
f".fallback_y_adjust={i32(words[4], words[5])}",
f".fallback_x_adjust={i32(words[6], words[7])}",
f".layer_mask={u16(words[8])}",
f".score_low={u16(words[9])}",
f".score_high={u16(words[10])}",
f".contact_state={u16(words[11])}",
f".flags={u16(words[12])}",
f".response_kick={real48(words[13:16])}",
f".response_auxiliary={real48(words[16:19])}",
f".response_tangent={real48(words[19:22])}",
f".response_normal={real48(words[22:25])}",
f".radius={i32(words[25], words[26])}",
f".point2_y={i32(words[27], words[28])}",
f".point2_x={i32(words[29], words[30])}",
f".point1_y={i32(words[31], words[32])}",
f".point1_x={i32(words[33], words[34])}",
f".coordinate_mode={u16(words[35])}",
f".subtype={u16(words[36])}",
f".type={u16(words[37])}",
f".active={u16(words[38])}",
f".index={u16(words[39])}",
]
return (
f" /* call {ordinal:03d}, record {words[39]:3d} */ "
"{" + ", ".join(fields) + "},"
)
def generated_text() -> str:
calls = calls_from_raw()
lines = [
"/* Generated by tools/generate_game_setup_collision_data.py.",
" * Source: TDKPIN_GHIDRA_COMPLETE_RAW.c, 1000:0270 call order.",
" * Do not hand-edit; run the generator and its --check mode.",
" */",
"static const TdkpinCollisionInitArgs g_game_setup_collision_calls[177] = {",
]
lines.extend(initializer(words, index + 1) for index, words in enumerate(calls))
lines.append("};")
lines.append("")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
arguments = parser.parse_args()
generated = generated_text()
if arguments.check:
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != generated:
raise SystemExit(f"generated collision data is stale: {OUTPUT}")
return 0
OUTPUT.write_text(generated, encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
workspace_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
probe_dir=$(mktemp -d /tmp/tdkpin-real48-probe.XXXXXX)
trap 'rm -f -- "$probe_dir/R48.COM" "$probe_dir/R48.OUT"; rmdir -- "$probe_dir"' EXIT
cd -- "$workspace_dir"
nasm -f bin tools/probes/real48_reference.asm -o "$probe_dir/R48.COM"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy dosbox \
-c "mount c $probe_dir" \
-c 'c:' \
-c 'R48.COM' \
-c 'exit' >/dev/null 2>&1
test -f "$probe_dir/R48.OUT"
actual=$(od -An -v -tx1 "$probe_dir/R48.OUT" | tr -d ' \n')
expected=0000000000008100000000008100000000808200000000009f00feffff7fa000000000809b00a0a2796b9b00a0a279eb0100000000ffffffff0000000000000200000000010000000000000080010000008000ffffff7f01820000000000000000000000000081000000000000810000000000008200000000000081000000000000800000000040008200000000400081000000000000820000000000008000000000c0008200000000c0008000000000000080000000008000000000000000000200000000000000ffffffffff01000000000000007efeffffff7e0181000000000000820000000060008000000000800082000000004000800000000040000000000000008000000000008100000000008000000000408100000000808000000000c0000000000000010000000000a855aa55aa5500000000000000000000000081000000000081fa33f304358200000000008149a070c41c0000000000007eb6b6afdb7a7f0b0c35626d7f49e2c20f490000000000007d60d5d4ad7e7f0a2b38636d8021a2da0f49815f970cb70d8021a2da0fc980d0f71772b100000000000080d5f71772317fff651f994f82acdd8d5d137efc1d55950a7f67b1b15a3c807be397451b81000000000081f2704c095382a35854f82d83a7c625736c8fa87cee142c0000000000007ffeffffff7f80faffffff7f00000000000000000000000080f9ffffffff804878a46a5780faffffff7f80c242d7b35d00000000000080f9ffffffff80f9ffffff7f00000000000080a87d40510a
printf '%s' "$actual"
printf '\n'
test "$actual" = "$expected"
+399
View File
@@ -0,0 +1,399 @@
; Differential probe for the unmodified Borland Real48 core in TDKPIN.EXE.
; Assemble from original/ so INCBIN addresses the authoritative target bytes.
bits 16
org 0x100
jmp main
integer_inputs:
dd 0, 1, -1, 2, 0x7fffffff, 0x80000000, 123456789, -123456789
integer_input_count equ ($ - integer_inputs) / 4
round_inputs:
db 0x80,0,0,0,0,0 ; +0.5
db 0x80,0,0,0,0,0x80 ; -0.5
db 0x7f,0xff,0xff,0xff,0xff,0x7f ; largest value below +0.5
db 0x81,0,0,0,0,0x40 ; +1.5
db 0x81,0xff,0xff,0xff,0xff,0x3f ; largest value below +1.5
db 0xa0,0,0,0,0,0 ; +2^31, overflow
db 0xa0,0,0,0,0,0x80 ; -2^31
db 0xa0,0,1,0,0,0x80 ; below -2^31, overflow
round_input_count equ ($ - round_inputs) / 6
arithmetic_pairs:
db 0x81,0,0,0,0,0, 0x81,0,0,0,0,0 ; 1, 1
db 0x81,0,0,0,0,0x40, 0x80,0,0,0,0,0 ; 1.5, 0.5
db 0x81,0,0,0,0,0x40, 0x80,0,0,0,0,0x80 ; 1.5, -0.5
db 0x01,0,0,0,0,0, 0x80,0,0,0,0,0 ; minimum, 0.5
db 0xff,0xff,0xff,0xff,0xff,0x7f, 0xff,0xff,0xff,0xff,0xff,0x7f
db 0x81,0,0,0,0,0x40, 0x82,0,0,0,0,0 ; 1.5, 2
arithmetic_pair_count equ ($ - arithmetic_pairs) / 12
part_inputs:
db 0x80,0,0,0,0,0 ; +0.5
db 0x81,0,0,0,0,0x60 ; +1.75
db 0x81,0,0,0,0,0xe0 ; -1.75
db 0x01,0,0,0,0,0 ; minimum positive
db 0xa8,0x55,0xaa,0x55,0xaa,0x55 ; already wholly integral
part_input_count equ ($ - part_inputs) / 6
sqrt_inputs:
db 0,0,0,0,0,0
db 0x81,0,0,0,0,0 ; 1
db 0x82,0,0,0,0,0 ; 2
db 0x83,0,0,0,0,0 ; 4
db 0x81,0,0,0,0,0x40 ; 1.5
sqrt_input_count equ ($ - sqrt_inputs) / 6
atan_series_inputs:
db 0,0,0,0,0,0
db 0x7f,0,0,0,0,0 ; 0.25
db 0x80,0,0,0,0,0 ; 0.5
db 0x7f,0xe7,0xcf,0xcc,0x13,0x54 ; sqrt(2)-1
atan_series_input_count equ ($ - atan_series_inputs) / 6
arctan_inputs:
db 0,0,0,0,0,0
db 0x7e,0,0,0,0,0 ; 0.125
db 0x80,0,0,0,0,0 ; 0.5
db 0x81,0,0,0,0,0 ; 1
db 0x82,0,0,0,0,0 ; 2
db 0x81,0,0,0,0,0x80 ; -1
arctan_input_count equ ($ - arctan_inputs) / 6
ln_inputs:
db 0x80,0,0,0,0,0 ; 0.5
db 0x81,0,0,0,0,0 ; 1
db 0x82,0,0,0,0,0 ; 2
db 0x81,0,0,0,0,0x40 ; 1.5
db 0x84,0,0,0,0,0x20 ; 10
ln_input_count equ ($ - ln_inputs) / 6
exp_inputs:
db 0x82,0,0,0,0,0x80 ; -2
db 0x81,0,0,0,0,0x80 ; -1
db 0x80,0,0,0,0,0x80 ; -0.5
db 0,0,0,0,0,0
db 0x80,0,0,0,0,0 ; 0.5
db 0x81,0,0,0,0,0 ; 1
db 0x82,0,0,0,0,0 ; 2
db 0x84,0,0,0,0,0x20 ; 10
exp_input_count equ ($ - exp_inputs) / 6
trig_inputs:
db 0,0,0,0,0,0
db 0x80,0x6a,0xc1,0x91,0x0a,0x06 ; pi/6
db 0x81,0x21,0xa2,0xda,0x0f,0x49 ; pi/2
db 0x82,0x21,0xa2,0xda,0x0f,0x49 ; pi
db 0x83,0x21,0xa2,0xda,0x0f,0x49 ; 2*pi
db 0x81,0x21,0xa2,0xda,0x0f,0xc9 ; -pi/2
db 0x81,0,0,0,0,0 ; 1
trig_input_count equ ($ - trig_inputs) / 6
probe_output:
times integer_input_count * 6 db 0
round_output equ $
times round_input_count * 5 db 0
arithmetic_output equ $
times arithmetic_pair_count * 28 db 0
part_output equ $
times part_input_count * 12 db 0
sqrt_output equ $
times sqrt_input_count * 6 db 0
atan_series_output equ $
times atan_series_input_count * 6 db 0
arctan_output equ $
times arctan_input_count * 6 db 0
ln_output equ $
times ln_input_count * 6 db 0
exp_output equ $
times exp_input_count * 6 db 0
sin_output equ $
times trig_input_count * 6 db 0
cos_output equ $
times trig_input_count * 6 db 0
probe_output_end:
output_name db 'R48.OUT',0
arithmetic_input_cursor dw arithmetic_pairs
arithmetic_output_cursor dw arithmetic_output
arithmetic_remaining db arithmetic_pair_count
part_input_cursor dw part_inputs
part_output_cursor dw part_output
part_remaining db part_input_count
sqrt_input_cursor dw sqrt_inputs
sqrt_output_cursor dw sqrt_output
sqrt_remaining db sqrt_input_count
atan_series_input_cursor dw atan_series_inputs
atan_series_output_cursor dw atan_series_output
atan_series_remaining db atan_series_input_count
arctan_input_cursor dw arctan_inputs
arctan_output_cursor dw arctan_output
arctan_remaining db arctan_input_count
ln_input_cursor dw ln_inputs
ln_output_cursor dw ln_output
ln_remaining db ln_input_count
exp_input_cursor dw exp_inputs
exp_output_cursor dw exp_output
exp_remaining db exp_input_count
trig_input_cursor dw trig_inputs
sin_output_cursor dw sin_output
cos_output_cursor dw cos_output
trig_remaining db trig_input_count
; Code5 file offset 108800 + 0x0cd0. Keeping the slice contiguous preserves
; every near displacement among the conversion/compare core routines. It is
; also placed at its original CS offset so absolute table references 1191..
; 1435 used by the transcendental helpers continue to address original bytes.
times (0x0bd0 - ($ - $$)) db 0
real48_core:
incbin "TDKPIN.EXE", 112080, 0x7d4
real48_i32_to_registers equ real48_core + (0x0f3b - 0x0cd0)
real48_registers_to_i32 equ real48_core + (0x0f77 - 0x0cd0)
real48_add_registers equ real48_core + (0x0cd4 - 0x0cd0)
real48_subtract_registers equ real48_core
real48_multiply_registers equ real48_core + (0x0d97 - 0x0cd0)
real48_divide_registers equ real48_core + (0x0e9a - 0x0cd0)
real48_integer_part equ real48_core + (0x1059 - 0x0cd0)
real48_fractional_part equ real48_core + (0x10aa - 0x0cd0)
real48_sqrt equ real48_core + (0x10be - 0x0cd0)
real48_arctan_series equ real48_core + (0x1436 - 0x0cd0)
real48_arctan equ real48_core + (0x1307 - 0x0cd0)
real48_ln equ real48_core + (0x11bb - 0x0cd0)
real48_exp equ real48_core + (0x1264 - 0x0cd0)
real48_sin equ real48_core + (0x1130 - 0x0cd0)
real48_cos equ real48_core + (0x111d - 0x0cd0)
%macro probe_arithmetic 1
mov bp, [arithmetic_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
mov cx, [bp + 6]
mov si, [bp + 8]
mov di, [bp + 10]
call %1
pushf
mov di, [arithmetic_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
pop ax
and al, 1
mov [di + 6], al
add word [arithmetic_output_cursor], 7
%endmacro
%macro probe_trig 2
mov bp, [trig_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call %1
mov di, [%2]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [%2], 6
%endmacro
%macro probe_exp 0
mov bp, [exp_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call real48_exp
mov di, [exp_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [exp_output_cursor], 6
%endmacro
%macro probe_ln 0
mov bp, [ln_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call real48_ln
mov di, [ln_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [ln_output_cursor], 6
%endmacro
%macro probe_arctan 0
mov bp, [arctan_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call real48_arctan
mov di, [arctan_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [arctan_output_cursor], 6
%endmacro
%macro probe_atan_series 0
mov bp, [atan_series_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
call real48_arctan_series
mov di, [atan_series_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [atan_series_output_cursor], 6
%endmacro
%macro probe_sqrt 0
mov bp, [sqrt_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call real48_sqrt
mov di, [sqrt_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [sqrt_output_cursor], 6
%endmacro
%macro probe_part 1
mov bp, [part_input_cursor]
mov ax, [bp]
mov bx, [bp + 2]
mov dx, [bp + 4]
push cs
call %1
mov di, [part_output_cursor]
mov [di], ax
mov [di + 2], bx
mov [di + 4], dx
add word [part_output_cursor], 6
%endmacro
main:
push cs
pop ds
push cs
pop es
mov si, integer_inputs
mov di, probe_output
mov cx, integer_input_count
.integer_loop:
mov ax, [si]
mov dx, [si + 2]
push cx
call real48_i32_to_registers
pop cx
stosw ; exponent/low fraction
mov ax, bx
stosw ; middle fraction
mov ax, dx
stosw ; high fraction/sign
add si, 4
loop .integer_loop
mov si, round_inputs
mov di, round_output
mov bp, round_input_count
.round_loop:
mov ax, [si]
mov bx, [si + 2]
mov dx, [si + 4]
mov ch, 1 ; Borland Round entry mode
call real48_registers_to_i32
stosw
mov ax, dx
stosw
mov byte [di], 0
jnc .round_no_overflow
inc byte [di]
.round_no_overflow:
inc di
add si, 6
dec bp
jnz .round_loop
.arithmetic_loop:
probe_arithmetic real48_add_registers
probe_arithmetic real48_subtract_registers
probe_arithmetic real48_multiply_registers
probe_arithmetic real48_divide_registers
add word [arithmetic_input_cursor], 12
dec byte [arithmetic_remaining]
jnz .arithmetic_loop
.part_loop:
probe_part real48_integer_part
probe_part real48_fractional_part
add word [part_input_cursor], 6
dec byte [part_remaining]
jnz .part_loop
.sqrt_loop:
probe_sqrt
add word [sqrt_input_cursor], 6
dec byte [sqrt_remaining]
jnz .sqrt_loop
.atan_series_loop:
probe_atan_series
add word [atan_series_input_cursor], 6
dec byte [atan_series_remaining]
jnz .atan_series_loop
.arctan_loop:
probe_arctan
add word [arctan_input_cursor], 6
dec byte [arctan_remaining]
jnz .arctan_loop
.ln_loop:
probe_ln
add word [ln_input_cursor], 6
dec byte [ln_remaining]
jnz .ln_loop
.exp_loop:
probe_exp
add word [exp_input_cursor], 6
dec byte [exp_remaining]
jnz .exp_loop
.trig_loop:
probe_trig real48_sin, sin_output_cursor
probe_trig real48_cos, cos_output_cursor
add word [trig_input_cursor], 6
dec byte [trig_remaining]
jnz .trig_loop
mov dx, output_name
xor cx, cx
mov ah, 0x3c ; create/truncate
int 0x21
jc .failure
mov bx, ax
mov dx, probe_output
mov cx, probe_output_end - probe_output
mov ah, 0x40
int 0x21
mov ah, 0x3e
int 0x21
mov ax, 0x4c00
int 0x21
.failure:
mov ax, 0x4c01
int 0x21
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Promote semantic DGROUP rows after every referring function is verified."""
from __future__ import annotations
import argparse
import csv
import re
from io import StringIO
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def closed_evidence(text: str) -> str:
"""Remove obsolete pre-closure clauses without discarding useful evidence."""
stale = re.compile(
r"\b(pending|remain(?:s|ing)? raw|raw/partial|remain(?:s|ing)? partial|"
r"unresolved)\b",
re.IGNORECASE,
)
clauses = [
clause.strip()
for clause in text.split(";")
if clause.strip() and not stale.search(clause)
]
closure = "complete verified referring-function closure"
if not any(closure in clause for clause in clauses):
clauses.append(closure)
return "; ".join(clauses)
def read_tsv(name: str) -> list[dict[str, str]]:
with (ROOT / name).open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def serialize(columns: list[str], rows: list[dict[str, str]]) -> str:
output = StringIO()
writer = csv.DictWriter(
output, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
return output.getvalue()
def promote() -> dict[str, str]:
functions = read_tsv("FUNCTION_RECONSTRUCTION.tsv")
verified_names = {
row["raw_name"] for row in functions if row["status"] == "verified"
}
if len(verified_names) != len(functions):
raise SystemExit("refusing data promotion before all functions are verified")
reconstruction = read_tsv("DATA_RECONSTRUCTION.tsv")
if any(row["status"] == "unknown" for row in reconstruction):
raise SystemExit("refusing data promotion while referenced addresses are unknown")
for row in reconstruction:
referring = {
name for name in row["referring_functions"].split(",") if name
}
unresolved = referring - verified_names
if unresolved:
raise SystemExit(
f"unverified data callers at {row['address']}: "
+ ", ".join(sorted(unresolved))
)
row["evidence"] = closed_evidence(row["evidence"])
if row["status"] == "restored":
row["status"] = "verified"
row["notes"] = (
"Exact semantic object field, extent, and complete read/write "
"inventory are closed through verified address-linked functions."
)
objects = read_tsv("DATA_OBJECTS.tsv")
for row in objects:
row["evidence"] = closed_evidence(row["evidence"])
if row["status"] == "restored":
row["status"] = "verified"
row["notes"] = (
"Exact object boundary, type, initialization, and full in-image "
"reader/writer lifecycle are verified."
)
coverage = read_tsv("DATA_COVERAGE.tsv")
for row in coverage:
row["evidence"] = closed_evidence(row["evidence"])
if row["status"] == "restored":
row["status"] = "verified"
row["notes"] = (
"Range is fully covered by a verified semantic DGROUP object."
)
return {
"DATA_RECONSTRUCTION.tsv": serialize(
list(reconstruction[0]), reconstruction
),
"DATA_OBJECTS.tsv": serialize(list(objects[0]), objects),
"DATA_COVERAGE.tsv": serialize(list(coverage[0]), coverage),
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
arguments = parser.parse_args()
expected = promote()
for name, text in expected.items():
path = ROOT / name
if arguments.check:
if path.read_text(encoding="utf-8") != text:
raise SystemExit(f"{name} needs deterministic semantic promotion")
else:
path.write_text(text, encoding="utf-8")
if __name__ == "__main__":
main()
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Promote Win16 imports only after the complete caller ledger is verified."""
from __future__ import annotations
import argparse
import csv
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
IMPORT_COLUMNS = (
"address",
"library",
"import_name",
"stack_purge_bytes",
"call_sites",
"status",
"stage",
"prototype",
"evidence",
"notes",
)
def read_tsv(name: str) -> list[dict[str, str]]:
with (ROOT / name).open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def expected_rows() -> list[dict[str, str]]:
rows = read_tsv("IMPORT_RECONSTRUCTION.tsv")
functions = read_tsv("FUNCTION_RECONSTRUCTION.tsv")
verified_names = {
row["raw_name"] for row in functions if row["status"] == "verified"
}
if len(verified_names) != len(functions):
raise SystemExit("refusing import promotion before every function is verified")
calls: Counter[str] = Counter()
callers: dict[str, set[str]] = defaultdict(set)
for reference in read_tsv("REFERENCES.tsv"):
if "CALL" not in reference["reference_type"]:
continue
target = reference["to_address"]
if not target.startswith("1228:"):
continue
calls[target] += 1
callers[target].add(reference["from_function"])
for row in rows:
if not row["prototype"]:
raise SystemExit(f"missing prototype for {row['address']}")
expected_count = int(row["call_sites"])
if calls[row["address"]] != expected_count:
raise SystemExit(
f"call-count drift for {row['address']}: "
f"ledger={expected_count}, references={calls[row['address']]}"
)
unresolved = callers[row["address"]] - verified_names
if unresolved:
raise SystemExit(
f"unverified callers for {row['address']}: "
+ ", ".join(sorted(unresolved))
)
if row["status"] == "verified":
continue
row["status"] = "verified"
if expected_count == 0:
row["stage"] = "unused-slot-verified"
row["evidence"] += (
"; complete Ghidra CALL inventory and NE relocation inventory: "
"zero call sites"
)
row["notes"] = (
"Explicit imported NE slot with no CALL reference anywhere in the "
"complete image; no TDKPIN call-site behavior exists to reconstruct."
)
else:
row["stage"] = "call-sites-verified"
row["evidence"] += (
f"; all {expected_count} direct call sites map to verified "
"address-linked function rows and readable C"
)
row["notes"] = (
f"All {expected_count} relocation-backed direct call sites are in "
"verified functions whose evidence records argument order, segmented "
"pointer or handle use, return handling, side effects, and branches."
)
return rows
def serialize(rows: list[dict[str, str]]) -> str:
from io import StringIO
output = StringIO()
writer = csv.DictWriter(
output, fieldnames=IMPORT_COLUMNS, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
return output.getvalue()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
arguments = parser.parse_args()
path = ROOT / "IMPORT_RECONSTRUCTION.tsv"
expected = serialize(expected_rows())
current = path.read_text(encoding="utf-8")
if arguments.check:
if current != expected:
raise SystemExit("IMPORT_RECONSTRUCTION.tsv needs deterministic promotion")
return
path.write_text(expected, encoding="utf-8")
if __name__ == "__main__":
main()
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Build the verified resource ledger from immutable extraction evidence."""
from __future__ import annotations
import argparse
import csv
import hashlib
import io
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
TARGET_SHA256 = "a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c"
FIELDS = (
"type", "id", "file_offset", "allocated_size", "raw_sha256",
"raw_path", "decoded_paths", "status", "stage", "users",
"behavior", "notes",
)
def read_tsv(path: Path) -> list[dict[str, str]]:
with path.open(newline="", encoding="utf-8") as stream:
return list(csv.DictReader(stream, delimiter="\t"))
def verified_function_addresses() -> set[str]:
rows = read_tsv(ROOT / "FUNCTION_RECONSTRUCTION.tsv")
assert len(rows) == 391
assert all(row["status"] == "verified" for row in rows)
return {row["address"] for row in rows}
def require_closed_ledger(name: str) -> None:
rows = read_tsv(ROOT / name)
assert rows
assert all(row["status"] == "verified" for row in rows), name
def role(resource_type: str, resource_id: int) -> str:
if resource_type == "DAT":
if 400 <= resource_id <= 407:
return f"42x123 player-emblem sprite {resource_id - 400}"
if resource_id == 600:
return "544x180 rotating-target and panel-animation atlas"
if 701 <= resource_id <= 704:
return f"146x142 score-status level {resource_id - 700}"
if 801 <= resource_id <= 809:
return f"68x61 collected-item display count {resource_id - 800}"
return {
900: "900x296 18-frame claw atlas",
901: "230x17 launcher and velocity-row decoration atlas",
993: "360x300 high-score background",
994: "237x13 asset-loading progress strip",
995: "640x460 one-shot board splash",
997: "640x460 immutable restore overlay A",
998: "640x460 restore overlay B and mutable background",
1001: "640x460 board background selected by index 1",
1002: "640x460 board background selected by index 2",
1003: "640x460 board background selected by index 3",
1004: "640x460 board background selected by index 4",
1005: "640x460 board background selected by index 5",
}[resource_id]
if resource_type == "PAL":
return "256 RGB triples shared by all custom DAT DIBs"
if resource_type == "WAV":
return {
2001: "add-player cue",
2002: "launcher-release cue",
2004: "trigger-activation cue",
2006: "timed target 51..53 hit cue",
2007: "score-marker or full-item-bank award cue",
2008: "normal ball-end cue",
2011: "rotating-target mechanism cue",
2012: "target and panel transition cue",
2013: "panel-mechanism motion cue",
2015: "ball-capture cue",
2016: "claw ball-release cue",
2017: "completed target-bank/item award cue",
2019: "table-nudge or kick-response cue",
2020: "tilt cue",
2021: "flipper-motion cue",
2022: "loaded and freed sound slot 22 with no in-image playback selector",
}[resource_id]
if resource_type == "BITMAP":
return {
101: "16x16 SRCPAINT object-composition image",
102: "16x16 SRCAND object-composition mask",
500: "944x28 score-digit and timer-indicator atlas",
}[resource_id]
return {
("ICON", 1): "32x32 image selected by GROUP_ICON 996",
("GROUP_ICON", 996): "main-window icon group loaded at 1000:016c",
("VERSION", 1): "Windows version metadata with no image-code loader",
("DIALOG", 32512): "Borland 2D File Open template",
("DIALOG", 32513): "Borland 2D File Save As template",
("DIALOG", 32514): "2D text-entry template selected at 1008:1607",
("DIALOG", 32515): "Borland CTL3D File Open template",
("DIALOG", 32516): "Borland CTL3D File Save As template",
("DIALOG", 32517): "CTL3D text-entry template selected at 1008:1607",
}[(resource_type, resource_id)]
def mapping(resource_type: str, resource_id: int) -> tuple[str, str]:
if resource_type == "DAT":
if 400 <= resource_id <= 407:
return "fully-mapped", "1000:5390,1008:149d,1000:0002,1000:51f5"
if resource_id == 600:
return "fully-mapped", "1000:5390,1008:149d,1000:6f27,1000:7440,1000:51f5"
if 701 <= resource_id <= 704:
return "fully-mapped", "1000:5390,1008:149d,1008:0b9e,1000:51f5"
if 801 <= resource_id <= 809:
return "fully-mapped", "1000:5390,1008:149d,1008:0dbd,1000:51f5"
return "fully-mapped", {
900: "1000:5390,1008:149d,1008:0e08,1000:51f5",
901: "1000:5390,1008:149d,1000:638e,1000:6b37,1000:51f5",
993: "1000:5be5,1008:149d,1000:5c13,1000:5ba6",
994: "1000:5390,1000:531e,1008:149d",
995: "1000:5390,1008:149d",
997: "1000:5390,1008:149d,1008:0837,1000:7440,1000:eab1,1000:51f5",
998: "1000:5390,1008:149d,1008:07b6,1008:08bb,1008:093f,1008:0996,1008:0e08,1000:51f5",
1001: "1000:6129,1008:149d,1000:616e,1000:60e7",
1002: "1000:6129,1008:149d,1000:616e,1000:60e7",
1003: "1000:6129,1008:149d,1000:616e,1000:60e7",
1004: "1000:6129,1008:149d,1000:616e,1000:60e7",
1005: "1000:6129,1008:149d,1000:616e,1000:60e7",
}[resource_id]
if resource_type == "PAL":
return "fully-mapped", "1008:1326,1008:11c5,1008:13a8,1008:149d"
if resource_type == "WAV":
events = {
2001: "1000:638e", 2002: "1000:638e", 2004: "1000:c79c",
2006: "1000:b476", 2007: "1000:b476,1000:bc36",
2008: "1000:ae6e", 2011: "1000:6f27", 2012: "1000:b476,1000:7440",
2013: "1000:7440", 2015: "1000:c79c", 2016: "1000:eab1",
2017: "1000:b476", 2019: "1000:638e,1000:c79c",
2020: "1000:638e", 2021: "1000:7ed9", 2022: "",
}[resource_id]
users = "1008:0f2e,1008:1131,1008:10e3"
if events:
users += "," + events
return "fully-mapped", users
if resource_type == "BITMAP":
return "fully-mapped", {
101: "1000:5390,1000:9bca,1000:9f73,1000:a38e,1000:51f5",
102: "1000:5390,1000:9bca,1000:9f73,1000:a38e",
500: "1000:5390,1008:0996,1000:eab1,1000:51f5",
}[resource_id]
if (resource_type, resource_id) == ("GROUP_ICON", 996):
return "system-resolved", "1000:016c"
if (resource_type, resource_id) == ("ICON", 1):
return "system-resolved", "GROUP_ICON:996 -> USER:LoadIcon"
if resource_type == "VERSION":
return "system-resolved", "Windows resource/version consumers; no in-image call"
if resource_id in {32514, 32517}:
return "fully-mapped", "1008:1607,1008:2996,1008:2a45,1008:2ae3"
return "linked-unused", "none (complete verified template-selector inventory)"
def build_rows() -> list[dict[str, str]]:
target = (ROOT / "TDKPIN.EXE").read_bytes()
assert hashlib.sha256(target).hexdigest() == TARGET_SHA256
manifest = json.loads((ROOT / "assets/manifest.json").read_text(encoding="utf-8"))
assert manifest["source_sha256"] == TARGET_SHA256
assert manifest["resource_count"] == len(manifest["resources"]) == 63
functions = verified_function_addresses()
require_closed_ledger("IMPORT_RECONSTRUCTION.tsv")
require_closed_ledger("DATA_RECONSTRUCTION.tsv")
require_closed_ledger("DATA_COVERAGE.tsv")
rows = []
for entry in manifest["resources"]:
resource_type = entry["type"]
resource_id = int(entry["id"])
raw = (ROOT / entry["raw_path"]).read_bytes()
start = entry["file_offset"]
end = start + entry["allocated_size"]
assert raw == target[start:end]
assert hashlib.sha256(raw).hexdigest() == entry["raw_sha256"]
stage, users = mapping(resource_type, resource_id)
for token in users.split(","):
token = token.strip()
if len(token) == 9 and token[4] == ":":
assert token in functions, (resource_type, resource_id, token)
if stage == "linked-unused":
notes = (
"Lossless extraction and decoded template structure are verified; "
"the complete selector inventory across 391 verified functions has no user, "
"so this Borland library template is explicitly retained as unused."
)
elif stage == "system-resolved":
notes = (
"Lossless extraction is verified; the Windows resource-system relationship "
"and the absence of any additional in-image loader are fully accounted."
)
else:
notes = (
"Lossless extraction, decoded derivative, exact load/use/release lifecycle, "
"and complete verified function closure are represented in reconstructed/"
"tdkpin_resources.c and the cited reconstruction units."
)
rows.append({
"type": resource_type,
"id": str(resource_id),
"file_offset": str(entry["file_offset"]),
"allocated_size": str(entry["allocated_size"]),
"raw_sha256": entry["raw_sha256"],
"raw_path": entry["raw_path"],
"decoded_paths": ",".join(entry.get("decoded_paths", [])),
"status": "verified",
"stage": stage,
"users": users,
"behavior": role(resource_type, resource_id) + ".",
"notes": notes,
})
assert len({(row["type"], row["id"]) for row in rows}) == 63
return rows
def render(rows: list[dict[str, str]]) -> str:
stream = io.StringIO(newline="")
writer = csv.DictWriter(stream, fieldnames=FIELDS, delimiter="\t", lineterminator="\n")
writer.writeheader()
writer.writerows(rows)
return stream.getvalue()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
output = render(build_rows())
destination = ROOT / "RESOURCE_RECONSTRUCTION.tsv"
if args.check:
assert destination.read_text(encoding="utf-8") == output
else:
destination.write_text(output, encoding="utf-8")
if __name__ == "__main__":
main()
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Populate the import ledger from Wine 11.15 Win16 specs and target evidence."""
from __future__ import annotations
import csv
import hashlib
import re
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
LEDGER = ROOT / "IMPORT_RECONSTRUCTION.tsv"
WINE_TAG = "wine-11.15"
WINE_BASE = f"https://gitlab.winehq.org/wine/wine/-/raw/{WINE_TAG}"
SPEC_SOURCES = {
"USER": (
"dlls/user.exe16/user.exe16.spec",
"d873fa88079c4f922cbba2fb3a75f40e2ff23b4d56434b0f4326169397539e32",
),
"GDI": (
"dlls/gdi.exe16/gdi.exe16.spec",
"3bd84b26a9d4b7aa91a9114d0a46dc13e4d6fcd71290120afdd9b7cfaffe76ab",
),
"KERNEL": (
"dlls/krnl386.exe16/krnl386.exe16.spec",
"8d799b701d1cf16d4e882b5e354dc6e357056d4adcd83e629571dc4b33b02075",
),
"MMSYSTEM": (
"dlls/mmsystem.dll16/mmsystem.dll16.spec",
"31e54179db61fa95a1d90a9a230d2ae5afb6013ba37ae54e78990d1795694a73",
),
"KEYBOARD": (
"dlls/keyboard.drv16/keyboard.drv16.spec",
"ca9a9e697e9afe0baf5cff7099ebb442169f36fa05293e78a0bb50fbefaa096d",
),
}
SPEC_FUNCTION = re.compile(
r"^\s*(?P<ordinal>\d+)\s+"
r"(?P<kind>pascal|cdecl|varargs|stub|register)"
r"(?P<flags>(?:\s+-\S+)*)\s+"
r"(?P<name>[A-Za-z0-9_@]+)\((?P<arguments>[^)]*)\)"
)
SPEC_EQUATE = re.compile(
r"^\s*(?P<ordinal>\d+)\s+equate\s+(?P<name>[A-Za-z0-9_@]+)\s+(?P<value>\S+)"
)
def fetch_specs() -> dict[str, tuple[str, list[str]]]:
specs = {}
for library, (path, expected_digest) in SPEC_SOURCES.items():
url = f"{WINE_BASE}/{path}"
with urllib.request.urlopen(url, timeout=30) as response:
payload = response.read()
digest = hashlib.sha256(payload).hexdigest()
if digest != expected_digest:
raise RuntimeError(
f"Wine source drift for {path}: expected {expected_digest}, got {digest}"
)
specs[library] = (url, payload.decode("utf-8").splitlines())
return specs
def parse_specs(
specs: dict[str, tuple[str, list[str]]],
) -> dict[tuple[str, str], dict[str, str]]:
declarations = {}
for library, (url, lines) in specs.items():
for line_number, line in enumerate(lines, 1):
match = SPEC_FUNCTION.match(line)
if match:
name = match.group("name")
flags = match.group("flags").strip()
kind = match.group("kind")
prefix = f"{kind}16"
if flags:
prefix += f" {flags}"
declaration = f"{prefix} {name}({match.group('arguments').strip()})"
declarations[(library, name.upper())] = {
"prototype": declaration,
"evidence": f"Wine {WINE_TAG} {url}#L{line_number}",
"notes": (
f"Wine Win16 ordinal {match.group('ordinal')}; "
"every TDKPIN call site still requires argument-level review."
),
"status": "partial",
"stage": "prototype-sourced",
}
continue
match = SPEC_EQUATE.match(line)
if match:
name = match.group("name")
declarations[(library, name.upper())] = {
"prototype": f"equate16 {name} = {match.group('value')}",
"evidence": f"Wine {WINE_TAG} {url}#L{line_number}",
"notes": (
f"Wine Win16 ordinal {match.group('ordinal')}; this is runtime data, "
"not a callable function."
),
"status": "verified",
"stage": "runtime-data",
}
return declarations
def recover_special_declarations(
declarations: dict[tuple[str, str], dict[str, str]],
) -> None:
keyboard_url = f"{WINE_BASE}/{SPEC_SOURCES['KEYBOARD'][0]}"
declarations[("KEYBOARD", "ORDINAL_5")] = {
"prototype": "pascal16 -ret16 AnsiToOem(str ptr)",
"evidence": f"Wine {WINE_TAG} {keyboard_url}#L5",
"notes": "Imported by ordinal; Wine identifies ordinal 5 as AnsiToOem.",
"status": "partial",
"stage": "prototype-sourced",
}
declarations[("KEYBOARD", "ORDINAL_6")] = {
"prototype": "pascal16 -ret16 OemToAnsi(str ptr)",
"evidence": f"Wine {WINE_TAG} {keyboard_url}#L6",
"notes": "Imported by ordinal; Wine identifies ordinal 6 as OemToAnsi.",
"status": "partial",
"stage": "prototype-sourced",
}
declarations[("MMTIMER", "SYSTEMTIMERMAKE")] = {
"prototype": (
"pascal16 -ret16 SystemTimerMake("
"HWND16 recipient, word delay_ms, word resolution_ms, word one_shot)"
),
"evidence": (
"MMTIMER.DLL 1000:0039 disassembly; export ordinal 1; "
"f1d9ac980c7bfba5dc53eaa9e7cb2c3cd9b82f879ee8962ad40bf863d641bb49"
),
"notes": (
"Binary-reviewed wrapper around timeSetEvent16: it installs MMTIMER 1000:0002, "
"posts message 0x0580 to recipient, and returns the multimedia timer id."
),
"status": "verified",
"stage": "binary-reviewed",
}
def main() -> int:
with LEDGER.open(newline="", encoding="utf-8") as handle:
reader = csv.DictReader(handle, delimiter="\t")
columns = list(reader.fieldnames or ())
rows = list(reader)
if "stage" not in columns:
columns.insert(columns.index("status") + 1, "stage")
declarations = parse_specs(fetch_specs())
recover_special_declarations(declarations)
import_keys = {(row["library"], row["import_name"].upper()) for row in rows}
missing = sorted(import_keys - set(declarations))
if missing:
raise RuntimeError(f"no Win16 declaration for: {missing}")
for row in rows:
recovered = declarations[(row["library"], row["import_name"].upper())]
preserve_review = row.get("stage") not in {
"",
"untyped-import",
"prototype-sourced",
"runtime-data",
"binary-reviewed",
}
preserved = (
{key: row.get(key, "") for key in ("status", "stage", "evidence", "notes")}
if preserve_review
else None
)
row.update(recovered)
if preserved:
row.update(preserved)
temporary = LEDGER.with_suffix(LEDGER.suffix + ".tmp")
with temporary.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(
handle, fieldnames=columns, delimiter="\t", lineterminator="\n"
)
writer.writeheader()
writer.writerows(rows)
temporary.replace(LEDGER)
statuses = {}
for row in rows:
statuses[row["status"]] = statuses.get(row["status"], 0) + 1
print(f"updated_import_slots={len(rows)}")
print(
f"unique_declarations={len({(row['library'], row['import_name']) for row in rows})}"
)
print("statuses=" + ",".join(f"{key}:{statuses[key]}" for key in sorted(statuses)))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+901
View File
@@ -0,0 +1,901 @@
#!/usr/bin/env bash
set -euo pipefail
workspace_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
test_build_dir=$(mktemp -d /tmp/tdkpin-c-tests.XXXXXX)
trap 'rm -f -- "$test_build_dir/resource-test" "$test_build_dir/runtime-ui-test" "$test_build_dir/string-test" "$test_build_dir/shortstring-test" "$test_build_dir/random-test" "$test_build_dir/object-windows-test" "$test_build_dir/object-windows-handlers-test" "$test_build_dir/window-advanced-test" "$test_build_dir/window-creation-test" "$test_build_dir/game-windows-test" "$test_build_dir/highscores-test" "$test_build_dir/gameplay-helpers-test" "$test_build_dir/ball-render-test" "$test_build_dir/ball-restore-test" "$test_build_dir/ball-sweep-test" "$test_build_dir/player-sprite-test" "$test_build_dir/scoring-test" "$test_build_dir/player-state-test" "$test_build_dir/turn-state-test" "$test_build_dir/mechanism-render-test" "$test_build_dir/collision-events-test" "$test_build_dir/panel-animation-test" "$test_build_dir/game-setup-test" "$test_build_dir/flippers-test" "$test_build_dir/flipper-bitmap-geometry-test" "$test_build_dir/flipper-collision-test" "$test_build_dir/physics-test" "$test_build_dir/timer-tick-test" "$test_build_dir/key-input-test" "$test_build_dir/key-release-test" "$test_build_dir/arithmetic-test" "$test_build_dir/command-line-test" "$test_build_dir/heap-reserve-test" "$test_build_dir/numeric-test" "$test_build_dir/multimedia-test" "$test_build_dir/multimedia-startup-test" "$test_build_dir/multimedia-paths-test" "$test_build_dir/program-entry-test" "$test_build_dir/collision-records-test" "$test_build_dir/heap-core-test" "$test_build_dir/heap-paths-test" "$test_build_dir/global-memory-test" "$test_build_dir/borland-objects-test" "$test_build_dir/stack-test" "$test_build_dir/system-timer-test" "$test_build_dir/dispatch-test" "$test_build_dir/bound-thunks-test" "$test_build_dir/application-test" "$test_build_dir/message-dispatch-test" "$test_build_dir/application-loop-test" "$test_build_dir/application-windows-test" "$test_build_dir/window-messages-test" "$test_build_dir/window-command-test" "$test_build_dir/object-list-test" "$test_build_dir/window-destroy-test" "$test_build_dir/class-registration-test" "$test_build_dir/object-lifecycle-test" "$test_build_dir/child-validation-test" "$test_build_dir/message-pump-test" "$test_build_dir/dialog-test" "$test_build_dir/runtime-input-test" "$test_build_dir/runtime-lifecycle-test" "$test_build_dir/runtime-minmax-test" "$test_build_dir/borland-records-test" "$test_build_dir/borland-files-test" "$test_build_dir/borland-startup-test" "$test_build_dir/tpwincrt-text-test" "$test_build_dir/tpwincrt-lifecycle-test" "$test_build_dir/tables-test" "$test_build_dir/sound-test" "$test_build_dir/real48-test" "$test_build_dir/palette-test" "$test_build_dir/palette-gdi-test" "$test_build_dir/bitmap-loader-test" "$test_build_dir/game-assets-test" "$test_build_dir/overlay-setup-test" "$test_build_dir/board-overlay-test" "$test_build_dir/asset-initializer-test" "$test_build_dir/asset-cleanup-test" "$test_build_dir/render-test" "$test_build_dir/score-render-test" "$test_build_dir/status-render-test" "$test_build_dir/claw-render-test" "$test_build_dir/palette-messages-test" "$test_build_dir/palette-window-messages-test" "$test_build_dir/window-lifecycle-test" "$test_build_dir/window-placement-test" "$test_build_dir/game-application-test"; rmdir -- "$test_build_dir"' EXIT
python3 "$workspace_dir/tools/generate_game_setup_collision_data.py" --check
python3 "$workspace_dir/tools/promote_verified_imports.py" --check
python3 "$workspace_dir/tools/promote_verified_data.py" --check
python3 "$workspace_dir/tools/promote_verified_resources.py" --check
python3 "$workspace_dir/tools/verify_extracted_assets.py"
while IFS= read -r source_unit; do
source_name=$(basename -- "$source_unit")
rg -Fq -- "$source_name" "$workspace_dir/tools/test_reconstructed_c.sh"
done < <(find "$workspace_dir/reconstructed" -maxdepth 1 -name '*.c' -print)
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_initialization.c" \
"$workspace_dir/reconstructed/tests/test_initialization.c" \
-o "$test_build_dir/resource-test"
"$test_build_dir/resource-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_resources.c" \
"$workspace_dir/reconstructed/tests/test_resources.c" \
-o "$test_build_dir/resource-test"
"$test_build_dir/resource-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_runtime_ui.c" \
"$workspace_dir/reconstructed/tdkpin_data.c" \
"$workspace_dir/reconstructed/tests/test_runtime_ui.c" \
-Wl,--gc-sections \
-o "$test_build_dir/runtime-ui-test"
"$test_build_dir/runtime-ui-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_strings.c" \
"$workspace_dir/reconstructed/tests/test_strings.c" \
-Wl,--gc-sections \
-o "$test_build_dir/string-test"
"$test_build_dir/string-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_shortstrings.c" \
"$workspace_dir/reconstructed/tests/test_shortstrings.c" \
-Wl,--gc-sections \
-o "$test_build_dir/shortstring-test"
"$test_build_dir/shortstring-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_random.c" \
"$workspace_dir/reconstructed/tests/test_random.c" \
-Wl,--gc-sections \
-o "$test_build_dir/random-test"
"$test_build_dir/random-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_windows.c" \
"$workspace_dir/reconstructed/tests/test_object_windows.c" \
-Wl,--gc-sections \
-o "$test_build_dir/object-windows-test"
"$test_build_dir/object-windows-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tdkpin_dialog.c" \
"$workspace_dir/reconstructed/tests/test_object_windows_handlers.c" \
-Wl,--gc-sections \
-o "$test_build_dir/object-windows-handlers-test"
"$test_build_dir/object-windows-handlers-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tests/test_window_advanced_handlers.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-advanced-test"
"$test_build_dir/window-advanced-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_placement.c" \
"$workspace_dir/reconstructed/tests/test_window_creation.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-creation-test"
"$test_build_dir/window-creation-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_windows.c" \
"$workspace_dir/reconstructed/tests/test_game_windows.c" \
-Wl,--gc-sections \
-o "$test_build_dir/game-windows-test"
"$test_build_dir/game-windows-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_borland_runtime.c" \
"$workspace_dir/reconstructed/tdkpin_shortstrings.c" \
"$workspace_dir/reconstructed/tdkpin_strings.c" \
"$workspace_dir/reconstructed/tdkpin_numeric.c" \
"$workspace_dir/reconstructed/tdkpin_highscores.c" \
"$workspace_dir/reconstructed/tests/test_highscores.c" \
-Wl,--gc-sections \
-o "$test_build_dir/highscores-test"
"$test_build_dir/highscores-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_gameplay_helpers.c" \
-Wl,--gc-sections \
-o "$test_build_dir/gameplay-helpers-test"
"$test_build_dir/gameplay-helpers-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_ball_render.c" \
-Wl,--gc-sections \
-o "$test_build_dir/ball-render-test"
"$test_build_dir/ball-render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_ball_restore.c" \
-Wl,--gc-sections \
-o "$test_build_dir/ball-restore-test"
"$test_build_dir/ball-restore-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_ball_sweep.c" \
-Wl,--gc-sections \
-o "$test_build_dir/ball-sweep-test"
"$test_build_dir/ball-sweep-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_player_sprite.c" \
-Wl,--gc-sections \
-o "$test_build_dir/player-sprite-test"
"$test_build_dir/player-sprite-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_gameplay_helpers.c" \
"$workspace_dir/reconstructed/tests/test_scoring.c" \
-Wl,--gc-sections \
-o "$test_build_dir/scoring-test"
"$test_build_dir/scoring-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_player_state.c" \
"$workspace_dir/reconstructed/tests/test_player_state.c" \
-Wl,--gc-sections \
-o "$test_build_dir/player-state-test"
"$test_build_dir/player-state-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_turn_state.c" \
"$workspace_dir/reconstructed/tests/test_turn_state.c" \
-Wl,--gc-sections \
-o "$test_build_dir/turn-state-test"
"$test_build_dir/turn-state-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_mechanism_render.c" \
"$workspace_dir/reconstructed/tests/test_mechanism_render.c" \
-Wl,--gc-sections \
-o "$test_build_dir/mechanism-render-test"
"$test_build_dir/mechanism-render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_collision_events.c" \
"$workspace_dir/reconstructed/tests/test_collision_events.c" \
-Wl,--gc-sections \
-o "$test_build_dir/collision-events-test"
"$test_build_dir/collision-events-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_panel_animation.c" \
"$workspace_dir/reconstructed/tests/test_panel_animation.c" \
-Wl,--gc-sections \
-o "$test_build_dir/panel-animation-test"
"$test_build_dir/panel-animation-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_setup.c" \
"$workspace_dir/reconstructed/tests/test_game_setup.c" \
-Wl,--gc-sections \
-o "$test_build_dir/game-setup-test"
"$test_build_dir/game-setup-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_flippers.c" \
"$workspace_dir/reconstructed/tests/test_flippers.c" \
-Wl,--gc-sections \
-o "$test_build_dir/flippers-test"
"$test_build_dir/flippers-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_flipper_bitmap_geometry.c" \
"$workspace_dir/reconstructed/tests/test_flipper_bitmap_geometry.c" \
-Wl,--gc-sections \
-o "$test_build_dir/flipper-bitmap-geometry-test"
"$test_build_dir/flipper-bitmap-geometry-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_real48.c" \
"$workspace_dir/reconstructed/tdkpin_flipper_collision.c" \
"$workspace_dir/reconstructed/tests/test_flipper_collision.c" \
-Wl,--gc-sections \
-o "$test_build_dir/flipper-collision-test"
"$test_build_dir/flipper-collision-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_real48.c" \
"$workspace_dir/reconstructed/tdkpin_physics.c" \
"$workspace_dir/reconstructed/tests/test_physics.c" \
-Wl,--gc-sections \
-o "$test_build_dir/physics-test"
"$test_build_dir/physics-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_real48.c" \
"$workspace_dir/reconstructed/tdkpin_timer_tick.c" \
"$workspace_dir/reconstructed/tests/test_timer_tick.c" \
-Wl,--gc-sections \
-o "$test_build_dir/timer-tick-test"
"$test_build_dir/timer-tick-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_key_input.c" \
"$workspace_dir/reconstructed/tests/test_key_input.c" \
-Wl,--gc-sections \
-o "$test_build_dir/key-input-test"
"$test_build_dir/key-input-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_key_input.c" \
"$workspace_dir/reconstructed/tests/test_key_release.c" \
-Wl,--gc-sections \
-o "$test_build_dir/key-release-test"
"$test_build_dir/key-release-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tests/test_arithmetic.c" \
-o "$test_build_dir/arithmetic-test"
"$test_build_dir/arithmetic-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_arithmetic.c" \
"$workspace_dir/reconstructed/tdkpin_real48.c" \
"$workspace_dir/reconstructed/tdkpin_collision_records.c" \
"$workspace_dir/reconstructed/tests/test_collision_records.c" \
-Wl,--gc-sections \
-o "$test_build_dir/collision-records-test"
"$test_build_dir/collision-records-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_command_line.c" \
"$workspace_dir/reconstructed/tests/test_command_line.c" \
-o "$test_build_dir/command-line-test"
"$test_build_dir/command-line-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_runtime.c" \
"$workspace_dir/reconstructed/tests/test_heap_reserve.c" \
-Wl,--gc-sections \
-o "$test_build_dir/heap-reserve-test"
"$test_build_dir/heap-reserve-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_numeric.c" \
"$workspace_dir/reconstructed/tests/test_numeric.c" \
-o "$test_build_dir/numeric-test"
"$test_build_dir/numeric-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_multimedia.c" \
"$workspace_dir/reconstructed/tests/test_multimedia.c" \
-Wl,--gc-sections \
-o "$test_build_dir/multimedia-test"
"$test_build_dir/multimedia-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_strings.c" \
"$workspace_dir/reconstructed/tdkpin_shortstrings.c" \
"$workspace_dir/reconstructed/tdkpin_multimedia_startup.c" \
"$workspace_dir/reconstructed/tests/test_multimedia_startup.c" \
-Wl,--gc-sections \
-o "$test_build_dir/multimedia-startup-test"
"$test_build_dir/multimedia-startup-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_strings.c" \
"$workspace_dir/reconstructed/tdkpin_multimedia_paths.c" \
"$workspace_dir/reconstructed/tests/test_multimedia_paths.c" \
-Wl,--gc-sections \
-o "$test_build_dir/multimedia-paths-test"
"$test_build_dir/multimedia-paths-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_program_entry.c" \
"$workspace_dir/reconstructed/tests/test_program_entry.c" \
-Wl,--gc-sections \
-o "$test_build_dir/program-entry-test"
"$test_build_dir/program-entry-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_heap.c" \
"$workspace_dir/reconstructed/tests/test_heap_core.c" \
-Wl,--gc-sections \
-o "$test_build_dir/heap-core-test"
"$test_build_dir/heap-core-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_heap.c" \
"$workspace_dir/reconstructed/tests/test_heap_paths.c" \
-Wl,--gc-sections \
-o "$test_build_dir/heap-paths-test"
"$test_build_dir/heap-paths-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_global_memory.c" \
"$workspace_dir/reconstructed/tests/test_global_memory.c" \
-o "$test_build_dir/global-memory-test"
"$test_build_dir/global-memory-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_objects.c" \
"$workspace_dir/reconstructed/tests/test_borland_objects.c" \
-Wl,--gc-sections \
-o "$test_build_dir/borland-objects-test"
"$test_build_dir/borland-objects-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_stack.c" \
"$workspace_dir/reconstructed/tests/test_stack.c" \
-Wl,--gc-sections \
-o "$test_build_dir/stack-test"
"$test_build_dir/stack-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_stack.c" \
"$workspace_dir/reconstructed/tdkpin_borland_objects.c" \
"$workspace_dir/reconstructed/tdkpin_system_timer.c" \
"$workspace_dir/reconstructed/tests/test_system_timer.c" \
-Wl,--gc-sections \
-o "$test_build_dir/system-timer-test"
"$test_build_dir/system-timer-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_dispatch.c" \
"$workspace_dir/reconstructed/tests/test_dispatch.c" \
-Wl,--gc-sections \
-o "$test_build_dir/dispatch-test"
"$test_build_dir/dispatch-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_runtime.c" \
"$workspace_dir/reconstructed/tdkpin_object_windows.c" \
"$workspace_dir/reconstructed/tests/test_bound_thunks.c" \
-Wl,--gc-sections \
-o "$test_build_dir/bound-thunks-test"
"$test_build_dir/bound-thunks-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_objects.c" \
"$workspace_dir/reconstructed/tdkpin_application.c" \
"$workspace_dir/reconstructed/tests/test_application.c" \
-Wl,--gc-sections \
-o "$test_build_dir/application-test"
"$test_build_dir/application-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_windows.c" \
"$workspace_dir/reconstructed/tests/test_message_dispatch.c" \
-Wl,--gc-sections \
-o "$test_build_dir/message-dispatch-test"
"$test_build_dir/message-dispatch-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_application.c" \
"$workspace_dir/reconstructed/tests/test_application_loop.c" \
-Wl,--gc-sections \
-o "$test_build_dir/application-loop-test"
"$test_build_dir/application-loop-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_application.c" \
"$workspace_dir/reconstructed/tests/test_application_windows.c" \
-Wl,--gc-sections \
-o "$test_build_dir/application-windows-test"
"$test_build_dir/application-windows-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_windows.c" \
"$workspace_dir/reconstructed/tdkpin_application.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tests/test_window_messages.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-messages-test"
"$test_build_dir/window-messages-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_windows.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tests/test_window_command.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-command-test"
"$test_build_dir/window-command-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_list.c" \
"$workspace_dir/reconstructed/tests/test_object_list.c" \
-o "$test_build_dir/object-list-test"
"$test_build_dir/object-list-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_list.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tests/test_window_destroy.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-destroy-test"
"$test_build_dir/window-destroy-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_messages.c" \
"$workspace_dir/reconstructed/tests/test_class_registration.c" \
-Wl,--gc-sections \
-o "$test_build_dir/class-registration-test"
"$test_build_dir/class-registration-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_lifecycle.c" \
"$workspace_dir/reconstructed/tests/test_object_lifecycle.c" \
-Wl,--gc-sections \
-o "$test_build_dir/object-lifecycle-test"
"$test_build_dir/object-lifecycle-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_object_list.c" \
"$workspace_dir/reconstructed/tests/test_child_validation.c" \
-o "$test_build_dir/child-validation-test"
"$test_build_dir/child-validation-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_message_pump.c" \
"$workspace_dir/reconstructed/tests/test_message_pump.c" \
-Wl,--gc-sections \
-o "$test_build_dir/message-pump-test"
"$test_build_dir/message-pump-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_dialog.c" \
"$workspace_dir/reconstructed/tests/test_dialog.c" \
-Wl,--gc-sections \
-o "$test_build_dir/dialog-test"
"$test_build_dir/dialog-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_runtime_ui.c" \
"$workspace_dir/reconstructed/tdkpin_data.c" \
"$workspace_dir/reconstructed/tests/test_runtime_input.c" \
-Wl,--gc-sections \
-o "$test_build_dir/runtime-input-test"
"$test_build_dir/runtime-input-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_runtime_ui.c" \
"$workspace_dir/reconstructed/tests/test_runtime_lifecycle.c" \
-Wl,--gc-sections \
-o "$test_build_dir/runtime-lifecycle-test"
"$test_build_dir/runtime-lifecycle-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_runtime_ui.c" \
"$workspace_dir/reconstructed/tests/test_runtime_minmax.c" \
-Wl,--gc-sections \
-o "$test_build_dir/runtime-minmax-test"
"$test_build_dir/runtime-minmax-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_records.c" \
"$workspace_dir/reconstructed/tests/test_borland_records.c" \
-o "$test_build_dir/borland-records-test"
"$test_build_dir/borland-records-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_borland_files.c" \
"$workspace_dir/reconstructed/tests/test_borland_files.c" \
-o "$test_build_dir/borland-files-test"
"$test_build_dir/borland-files-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_borland_startup.c" \
"$workspace_dir/reconstructed/tests/test_borland_startup.c" \
-o "$test_build_dir/borland-startup-test"
"$test_build_dir/borland-startup-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_tpwincrt_text.c" \
"$workspace_dir/reconstructed/tests/test_tpwincrt_text.c" \
-Wl,--gc-sections \
-o "$test_build_dir/tpwincrt-text-test"
"$test_build_dir/tpwincrt-text-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_tpwincrt_lifecycle.c" \
"$workspace_dir/reconstructed/tests/test_tpwincrt_lifecycle.c" \
-Wl,--gc-sections \
-o "$test_build_dir/tpwincrt-lifecycle-test"
"$test_build_dir/tpwincrt-lifecycle-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_tables.c" \
"$workspace_dir/reconstructed/tests/test_tables.c" \
-o "$test_build_dir/tables-test"
"$test_build_dir/tables-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_data.c" \
"$workspace_dir/reconstructed/tdkpin_command_line.c" \
"$workspace_dir/reconstructed/tdkpin_strings.c" \
"$workspace_dir/reconstructed/tdkpin_shortstrings.c" \
"$workspace_dir/reconstructed/tdkpin_sound.c" \
"$workspace_dir/reconstructed/tests/test_sound.c" \
-Wl,--gc-sections \
-o "$test_build_dir/sound-test"
"$test_build_dir/sound-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-fsanitize=address,undefined \
"$workspace_dir/reconstructed/tdkpin_real48.c" \
"$workspace_dir/reconstructed/tests/test_real48.c" \
-o "$test_build_dir/real48-test"
"$test_build_dir/real48-test"
"$workspace_dir/tools/probe_real48_reference.sh" >/dev/null
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_palette.c" \
"$workspace_dir/reconstructed/tests/test_palette.c" \
-Wl,--gc-sections \
-o "$test_build_dir/palette-test"
"$test_build_dir/palette-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_palette.c" \
"$workspace_dir/reconstructed/tests/test_palette_gdi.c" \
-Wl,--gc-sections \
-o "$test_build_dir/palette-gdi-test"
"$test_build_dir/palette-gdi-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_palette.c" \
"$workspace_dir/reconstructed/tests/test_bitmap_loader.c" \
-Wl,--gc-sections \
-o "$test_build_dir/bitmap-loader-test"
"$test_build_dir/bitmap-loader-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_assets.c" \
"$workspace_dir/reconstructed/tests/test_game_assets.c" \
-Wl,--gc-sections \
-o "$test_build_dir/game-assets-test"
"$test_build_dir/game-assets-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_assets.c" \
"$workspace_dir/reconstructed/tests/test_overlay_setup.c" \
-Wl,--gc-sections \
-o "$test_build_dir/overlay-setup-test"
"$test_build_dir/overlay-setup-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_assets.c" \
"$workspace_dir/reconstructed/tests/test_board_overlay.c" \
-Wl,--gc-sections \
-o "$test_build_dir/board-overlay-test"
"$test_build_dir/board-overlay-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_asset_initializer.c" \
"$workspace_dir/reconstructed/tests/test_asset_initializer.c" \
-o "$test_build_dir/asset-initializer-test"
"$test_build_dir/asset-initializer-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_asset_cleanup.c" \
"$workspace_dir/reconstructed/tests/test_asset_cleanup.c" \
-o "$test_build_dir/asset-cleanup-test"
"$test_build_dir/asset-cleanup-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_render.c" \
"$workspace_dir/reconstructed/tests/test_render.c" \
-Wl,--gc-sections \
-o "$test_build_dir/render-test"
"$test_build_dir/render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_render.c" \
"$workspace_dir/reconstructed/tests/test_score_render.c" \
-Wl,--gc-sections \
-o "$test_build_dir/score-render-test"
"$test_build_dir/score-render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_status_render.c" \
"$workspace_dir/reconstructed/tests/test_status_render.c" \
-o "$test_build_dir/status-render-test"
"$test_build_dir/status-render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_claw_render.c" \
"$workspace_dir/reconstructed/tests/test_claw_render.c" \
-o "$test_build_dir/claw-render-test"
"$test_build_dir/claw-render-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_assets.c" \
"$workspace_dir/reconstructed/tests/test_palette_messages.c" \
-Wl,--gc-sections \
-o "$test_build_dir/palette-messages-test"
"$test_build_dir/palette-messages-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_assets.c" \
"$workspace_dir/reconstructed/tests/test_palette_window_messages.c" \
-Wl,--gc-sections \
-o "$test_build_dir/palette-window-messages-test"
"$test_build_dir/palette-window-messages-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_lifecycle.c" \
"$workspace_dir/reconstructed/tests/test_window_lifecycle.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-lifecycle-test"
"$test_build_dir/window-lifecycle-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-ffunction-sections -fdata-sections \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_window_placement.c" \
"$workspace_dir/reconstructed/tests/test_window_placement.c" \
-Wl,--gc-sections \
-o "$test_build_dir/window-placement-test"
"$test_build_dir/window-placement-test"
clang -std=c11 -Wall -Wextra -Werror -pedantic \
"$workspace_dir/reconstructed/tdkpin_segmented.c" \
"$workspace_dir/reconstructed/tdkpin_game_application.c" \
"$workspace_dir/reconstructed/tests/test_game_application.c" \
-o "$test_build_dir/game-application-test"
"$test_build_dir/game-application-test"
echo "Reconstructed C tests passed"