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

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

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

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

299 lines
9.8 KiB
C

/*
* Address-linked Borland Win16 runtime termination support from TDKPIN.EXE.
* Functions: 1020:0061, 1020:00d2, and 1020:00f0.
*/
#include "tdkpin_borland_runtime.h"
enum {
DGROUP_RUNTIME_ERROR_TEXT = 0x0728,
RUNTIME_ERROR_DECIMAL_END = 0x0739,
RUNTIME_ERROR_SEGMENT_END = 0x0741,
RUNTIME_ERROR_OFFSET_END = 0x0746,
RUNTIME_ERROR_MESSAGEBOX_FLAGS = 0x1010,
};
extern Win16FarPtr g_borland_exit_proc; /* 1028:0712/0714 */
extern uint16_t g_borland_exit_code_word; /* 1028:0716/0717 */
extern uint16_t g_borland_error_offset; /* 1028:0718 */
extern uint16_t g_borland_error_segment; /* 1028:071a */
extern uint16_t g_borland_windows_mode; /* 1028:071c */
extern char g_borland_runtime_error_text[32]; /* 1028:0728..0747 */
/* 1020:012d — checked GetMem wrapper; allocation failure is Runtime Error 203. */
Win16FarPtr borland_get_mem(uint16_t bytes)
{
BorlandAllocationAttempt attempt = borland_try_get_mem(bytes);
if (attempt.failed) {
borland_runtime_error(203);
}
return attempt.block;
}
/* 1020:0147 — checked FreeMem wrapper; invalid release is Runtime Error 204. */
void borland_free_mem(Win16FarPtr block, uint16_t bytes)
{
if (borland_try_free_mem(block, bytes)) {
borland_runtime_error(204);
}
}
/* 1020:0222 — GlobalAlloc16 followed by GlobalLock16 on nonzero handle. */
Win16FarPtr borland_global_allocate(uint16_t bytes)
{
HGLOBAL16 allocation = GlobalAlloc16(g_borland_global_alloc_flags, bytes);
return allocation == 0 ? 0 : GlobalLock16(allocation);
}
uint16_t borland_take_io_result(void)
{
return atomic_exchange_explicit(
&g_borland_io_result, 0, memory_order_seq_cst);
}
void borland_require_io_success(void)
{
uint16_t result = atomic_load_explicit(
&g_borland_io_result, memory_order_seq_cst);
if (result != 0) {
borland_runtime_error(result);
}
}
void borland_check_int32_range(
int32_t value, const BorlandInt32Range *range)
{
if (value < range->minimum || value > range->maximum) {
borland_runtime_error(201);
}
}
_Noreturn void borland_runtime_error_215(void)
{
borland_runtime_error(215);
}
/* 1008:2de0 — AL=1 exactly when the emergency reserve far pointer is null. */
bool borland_heap_reserve_is_unallocated(void)
{
return g_borland_heap_reserve == 0;
}
/* 1008:2dff — allocate the configured reserve only when it is absent. */
void borland_ensure_heap_reserve(void)
{
if (borland_heap_reserve_is_unallocated()) {
g_borland_heap_reserve = borland_get_mem(g_borland_heap_reserve_bytes);
}
}
/*
* 1008:2e22 — Borland heap-error callback.
* 0 means no reserve was available, 1 suppresses release, and 2 tells the
* allocator to retry after the reserve has been freed. The zero-size entry is
* not used by the allocator and returns the same indeterminate word as the
* binary's uninitialized local.
*/
uint16_t borland_release_heap_reserve_on_oom(uint16_t requested_bytes)
{
if (requested_bytes == 0) {
return win16_indeterminate_u16();
}
if (g_borland_heap_reserve_disabled) {
return 1;
}
if (borland_heap_reserve_is_unallocated()) {
return 0;
}
borland_free_mem(g_borland_heap_reserve, g_borland_heap_reserve_bytes);
g_borland_heap_reserve = 0;
return 2;
}
/* 1008:2e76 — provision the reserve and publish the retry callback. */
void borland_initialize_heap_reserve(void)
{
borland_ensure_heap_reserve();
g_borland_heap_error_handler = win16_relocated_code_pointer(2, 0x2e22);
}
/* 1020:16c9 — CLD plus REP STOSB over one far destination. */
void borland_fill_bytes(Win16FarPtr destination, uint16_t count, uint8_t value)
{
win16_fill_bytes(destination, count, value);
}
/*
* 1020:16a5 — Borland System.Move over two far pointers.
*
* The original compares only SI and DI, not their selectors, before choosing
* CLD or STD. Copying backwards across distinct segments is harmless but is a
* real observable detail of the routine. All address arithmetic wraps at the
* 16-bit offset boundary exactly as the 8086 registers do.
*/
void borland_move_bytes(
Win16FarPtr destination, Win16FarPtr source, uint16_t count)
{
uint16_t source_selector = win16_far_selector(source);
uint16_t destination_selector = win16_far_selector(destination);
uint16_t source_offset = win16_far_offset(source);
uint16_t destination_offset = win16_far_offset(destination);
if (source_offset >= destination_offset) {
while (count != 0) {
uint8_t byte = win16_read_u8(
win16_make_far_pointer(source_selector, source_offset));
win16_write_u8(
win16_make_far_pointer(
destination_selector, destination_offset),
byte);
source_offset++;
destination_offset++;
count--;
}
return;
}
source_offset = (uint16_t)(source_offset + count - 1u);
destination_offset = (uint16_t)(destination_offset + count - 1u);
while (count != 0) {
uint8_t byte = win16_read_u8(
win16_make_far_pointer(source_selector, source_offset));
win16_write_u8(
win16_make_far_pointer(destination_selector, destination_offset),
byte);
source_offset--;
destination_offset--;
count--;
}
}
/*
* 1020:046c — copy one compiler-described record.
*
* A near descriptor pointer lives at destination + descriptor_field_offset;
* its first DGROUP word is the record byte length. The binary saves that near
* pointer, performs a forward far copy onto the start of the destination, and
* writes the descriptor pointer back because the copied bytes may overlap it.
*/
void borland_copy_typed_record(
uint16_t descriptor_field_offset,
Win16FarPtr destination,
Win16FarPtr source)
{
uint16_t destination_selector = win16_far_selector(destination);
uint16_t destination_offset = win16_far_offset(destination);
uint16_t source_selector = win16_far_selector(source);
uint16_t source_offset = win16_far_offset(source);
uint16_t descriptor_address =
(uint16_t)(destination_offset + descriptor_field_offset);
Win16FarPtr descriptor_field = win16_make_far_pointer(
destination_selector, descriptor_address);
uint16_t descriptor_offset = win16_read_u16(descriptor_field);
uint16_t count = win16_read_u16(win16_dgroup_pointer(descriptor_offset));
while (count != 0) {
uint8_t byte = win16_read_u8(
win16_make_far_pointer(source_selector, source_offset));
win16_write_u8(
win16_make_far_pointer(
destination_selector, destination_offset),
byte);
source_offset++;
destination_offset++;
count--;
}
win16_write_u16(descriptor_field, 0, descriptor_offset);
}
/* 1020:00f0 — prepend AX as unsigned digits in base CX before BX. */
char *borland_prepend_unsigned(char *end, uint16_t value, uint16_t base)
{
do {
uint16_t quotient = value / base;
uint8_t digit = (uint8_t)(value % base);
digit = (uint8_t)(digit + (digit < 10 ? '0' : 'A' - 10));
*--end = (char)digit;
value = quotient;
} while (value != 0);
return end;
}
/*
* 1020:00d2 — execute the mutable Borland ExitProc chain.
* The binary clears ExitProc and IOResult before each far call and returns each
* callback to 00d2, so a callback may install the next procedure in the chain.
*/
void borland_run_exit_procedures(void)
{
while (g_borland_exit_proc != 0) {
Win16FarPtr procedure = g_borland_exit_proc;
g_borland_exit_proc = 0;
atomic_store_explicit(
&g_borland_io_result, 0, memory_order_seq_cst);
win16_call_exit_proc(procedure);
}
}
/*
* 1020:0061 — register-entry Borland Halt.
* The caller supplies the status in AX (only AL is eventually returned to DOS).
*/
static _Noreturn void borland_finish_runtime_exit(
uint16_t status, uint16_t error_offset, uint16_t error_segment)
{
g_borland_exit_code_word = status;
g_borland_error_offset = error_offset;
g_borland_error_segment = error_segment;
if (g_borland_windows_mode != 0) {
borland_run_exit_procedures();
}
if (g_borland_error_offset != 0 || g_borland_error_segment != 0) {
(void)borland_prepend_unsigned(
&g_borland_runtime_error_text[
RUNTIME_ERROR_DECIMAL_END - DGROUP_RUNTIME_ERROR_TEXT],
(uint8_t)g_borland_exit_code_word,
10);
(void)borland_prepend_unsigned(
&g_borland_runtime_error_text[
RUNTIME_ERROR_SEGMENT_END - DGROUP_RUNTIME_ERROR_TEXT],
g_borland_error_segment,
16);
(void)borland_prepend_unsigned(
&g_borland_runtime_error_text[
RUNTIME_ERROR_OFFSET_END - DGROUP_RUNTIME_ERROR_TEXT],
g_borland_error_offset,
16);
(void)MessageBox16(
0,
win16_dgroup_pointer(DGROUP_RUNTIME_ERROR_TEXT),
0,
RUNTIME_ERROR_MESSAGEBOX_FLAGS);
}
win16_dos_terminate((uint8_t)g_borland_exit_code_word);
}
_Noreturn void borland_runtime_exit(uint16_t status)
{
borland_finish_runtime_exit(status, 0, 0);
}
/*
* 1020:005d — RuntimeError entry shim.
* It consumes the caller's far return address. Except for selector ffff, the
* word at selector:0000 is the NE logical segment tag recorded in the message.
*/
_Noreturn void borland_runtime_error(uint16_t code)
{
Win16FarPtr return_address = win16_current_return_address();
uint16_t selector = win16_far_selector(return_address);
uint16_t segment = selector;
if (return_address != 0 && selector != 0xffff) {
segment = win16_read_u16(win16_make_far_pointer(selector, 0));
}
borland_finish_runtime_exit(
code, win16_far_offset(return_address), segment);
}