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
371 lines
12 KiB
C
371 lines
12 KiB
C
#include "../tdkpin_game_setup.h"
|
|
|
|
#include <assert.h>
|
|
#include <setjmp.h>
|
|
#include <string.h>
|
|
|
|
static uint8_t g_dgroup[0x5000];
|
|
static uint8_t g_window_bytes[0x0c00];
|
|
static uint8_t g_code[0x300];
|
|
static Win16FarPtr g_window;
|
|
static uint32_t g_heap_free;
|
|
static uint16_t g_language;
|
|
static uint16_t g_speed;
|
|
static Win16FarPtr g_timer_result;
|
|
static unsigned g_setup_window_calls;
|
|
static unsigned g_palette_realize_calls;
|
|
static unsigned g_palette_load_calls;
|
|
static unsigned g_palette_create_calls;
|
|
static unsigned g_timer_calls;
|
|
static unsigned g_collision_calls;
|
|
static unsigned g_quad_calls;
|
|
static unsigned g_message_calls;
|
|
static unsigned g_close_calls;
|
|
static uint16_t g_collision_ids[177];
|
|
static uint8_t g_final_active[176];
|
|
static uint16_t g_final_contact[176];
|
|
static TdkpinWordQuad g_quads[21];
|
|
static Win16FarPtr g_last_message_text;
|
|
static HWND16 g_last_message_owner;
|
|
static jmp_buf g_exit_jump;
|
|
static bool g_expect_exit;
|
|
|
|
uint32_t borland_heap_free_space(void)
|
|
{
|
|
return g_heap_free;
|
|
}
|
|
|
|
void object_windows_setup_window(Win16FarPtr window)
|
|
{
|
|
assert(window == g_window);
|
|
g_setup_window_calls++;
|
|
}
|
|
|
|
void object_windows_close_if_allowed(Win16FarPtr window)
|
|
{
|
|
assert(window == g_window);
|
|
g_close_calls++;
|
|
}
|
|
|
|
_Noreturn void borland_runtime_exit(uint16_t status)
|
|
{
|
|
assert(g_expect_exit && status == 0);
|
|
longjmp(g_exit_jump, 1);
|
|
}
|
|
|
|
INT16 win16_call_message_box_far(
|
|
Win16FarPtr procedure,
|
|
HWND16 owner,
|
|
Win16FarPtr text,
|
|
Win16FarPtr caption,
|
|
UINT16 type)
|
|
{
|
|
assert(procedure == win16_make_far_pointer(0x8000, 0x1111));
|
|
assert(caption == win16_dgroup_pointer(0x02aa));
|
|
assert(type == 0x2010);
|
|
g_last_message_owner = owner;
|
|
g_last_message_text = text;
|
|
g_message_calls++;
|
|
return 1;
|
|
}
|
|
|
|
HCURSOR16 LoadCursor16(HINSTANCE16 instance, Win16FarPtr resource)
|
|
{
|
|
assert(instance == 0 && resource == win16_make_far_pointer(0, 0x7f02));
|
|
return 0x4444;
|
|
}
|
|
|
|
HCURSOR16 SetCursor16(HCURSOR16 cursor)
|
|
{
|
|
assert(cursor == 0x4444);
|
|
return 0x5555;
|
|
}
|
|
|
|
void tdkpin_realize_nocollapse_palette(HWND16 window)
|
|
{
|
|
assert(window == 0x2222);
|
|
g_palette_realize_calls++;
|
|
}
|
|
|
|
void tdkpin_load_palette_resource(
|
|
Win16FarPtr destination,
|
|
Win16FarPtr resource_name,
|
|
HINSTANCE16 instance)
|
|
{
|
|
assert(destination == win16_dgroup_pointer(0x4450));
|
|
assert(resource_name == win16_make_far_pointer(0, 999));
|
|
assert(instance == 0x1234);
|
|
g_palette_load_calls++;
|
|
}
|
|
|
|
void tdkpin_create_palette_from_rgb_table(void)
|
|
{
|
|
g_palette_create_calls++;
|
|
}
|
|
|
|
uint32_t GetTickCount16(void)
|
|
{
|
|
return 0x11223344u;
|
|
}
|
|
|
|
BorlandDivI32Result borland_divide_i32(int32_t dividend, int32_t divisor)
|
|
{
|
|
assert(divisor == 1000);
|
|
return (BorlandDivI32Result){dividend / divisor, dividend % divisor};
|
|
}
|
|
|
|
void borland_parameter_string(uint16_t index, Win16FarPtr destination)
|
|
{
|
|
static const char module[] = "C:\\TDKPIN.EXE";
|
|
assert(index == 0);
|
|
win16_write_u8(destination, (uint8_t)(sizeof(module) - 1));
|
|
for (uint16_t byte = 0; byte < sizeof(module) - 1; byte++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(destination, (uint16_t)(byte + 1)),
|
|
(uint8_t)module[byte]);
|
|
}
|
|
}
|
|
|
|
Win16FarPtr borland_short_to_c_string(
|
|
Win16FarPtr destination, Win16FarPtr source)
|
|
{
|
|
uint8_t length = win16_read_u8(source);
|
|
for (uint16_t byte = 0; byte < length; byte++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(destination, byte),
|
|
win16_read_u8(win16_far_add_offset(source, (uint16_t)(byte + 1))));
|
|
}
|
|
win16_write_u8(win16_far_add_offset(destination, length), 0);
|
|
return destination;
|
|
}
|
|
|
|
uint16_t borland_short_position(Win16FarPtr pattern, Win16FarPtr text)
|
|
{
|
|
assert(pattern == win16_make_far_pointer(0x7300, 0x026e));
|
|
assert(win16_read_u8(text) == 13);
|
|
return 10;
|
|
}
|
|
|
|
Win16FarPtr borland_far_strncpy(
|
|
Win16FarPtr destination, Win16FarPtr source, uint16_t maximum)
|
|
{
|
|
assert(maximum == 10);
|
|
for (uint16_t byte = 0; byte < maximum; byte++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(destination, byte),
|
|
win16_read_u8(win16_far_add_offset(source, byte)));
|
|
}
|
|
win16_write_u8(win16_far_add_offset(destination, maximum), 0);
|
|
return destination;
|
|
}
|
|
|
|
Win16FarPtr borland_far_strcat(Win16FarPtr destination, Win16FarPtr source)
|
|
{
|
|
uint16_t end = 0;
|
|
while (win16_read_u8(win16_far_add_offset(destination, end)) != 0) {
|
|
end++;
|
|
}
|
|
uint16_t byte = 0;
|
|
do {
|
|
uint8_t value = win16_read_u8(win16_far_add_offset(source, byte));
|
|
win16_write_u8(
|
|
win16_far_add_offset(destination, (uint16_t)(end + byte)), value);
|
|
byte++;
|
|
if (value == 0) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
return destination;
|
|
}
|
|
|
|
static void assert_far_c_string(Win16FarPtr string, const char *expected)
|
|
{
|
|
for (uint16_t index = 0;; index++) {
|
|
uint8_t actual = win16_read_u8(win16_far_add_offset(string, index));
|
|
assert(actual == (uint8_t)expected[index]);
|
|
if (actual == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UINT16 GetPrivateProfileInt16(
|
|
Win16FarPtr section,
|
|
Win16FarPtr key,
|
|
INT16 default_value,
|
|
Win16FarPtr file_name)
|
|
{
|
|
assert(section == win16_dgroup_pointer(0x02c7));
|
|
assert_far_c_string(file_name, "C:\\TDKPIN.INI");
|
|
if (key == win16_dgroup_pointer(0x02d0)) {
|
|
assert(default_value == 1);
|
|
return g_language;
|
|
}
|
|
assert(key == win16_dgroup_pointer(0x02d9) && default_value == 3);
|
|
return g_speed;
|
|
}
|
|
|
|
Win16FarPtr tdkpin_system_timer_construct(
|
|
Win16FarPtr object,
|
|
uint16_t vmt,
|
|
uint8_t one_shot,
|
|
uint16_t resolution_ms,
|
|
uint16_t delay_ms,
|
|
HWND16 recipient)
|
|
{
|
|
assert(object == 0 && vmt == 0x05c0 && one_shot == 0);
|
|
assert(resolution_ms == 1 && delay_ms == 20 && recipient == 0x2222);
|
|
g_timer_calls++;
|
|
return g_timer_result;
|
|
}
|
|
|
|
void tdkpin_initialize_collision_record(
|
|
const TdkpinCollisionInitArgs *arguments)
|
|
{
|
|
assert(g_collision_calls < 177 && arguments->index <= 175);
|
|
g_collision_ids[g_collision_calls++] = arguments->index;
|
|
g_final_active[arguments->index] = (uint8_t)arguments->active;
|
|
g_final_contact[arguments->index] = arguments->contact_state;
|
|
Win16FarPtr target = win16_dgroup_pointer((uint16_t)(
|
|
0x095b + arguments->index * 0x53u));
|
|
win16_write_u8(win16_far_add_offset(target, 0x34),
|
|
(uint8_t)arguments->active);
|
|
win16_write_u16(target, 0x43, arguments->contact_state);
|
|
}
|
|
|
|
void tdkpin_set_word_quad(
|
|
uint16_t source_word_6,
|
|
uint16_t source_word_4,
|
|
uint16_t source_word_2,
|
|
uint16_t source_word_0,
|
|
uint16_t index)
|
|
{
|
|
assert(index == g_quad_calls + 1 && index <= 20);
|
|
g_quads[index] = (TdkpinWordQuad){
|
|
source_word_0, source_word_2, source_word_4, source_word_6};
|
|
g_quad_calls++;
|
|
}
|
|
|
|
static void prepare_fixture(void)
|
|
{
|
|
memset(g_dgroup, 0, sizeof(g_dgroup));
|
|
memset(g_window_bytes, 0xa5, sizeof(g_window_bytes));
|
|
memset(g_code, 0, sizeof(g_code));
|
|
memset(g_collision_ids, 0, sizeof(g_collision_ids));
|
|
memset(g_final_active, 0, sizeof(g_final_active));
|
|
memset(g_final_contact, 0, sizeof(g_final_contact));
|
|
memset(g_quads, 0, sizeof(g_quads));
|
|
win16_reset_segment_bindings();
|
|
win16_bind_segment(0x7100, g_window_bytes, sizeof(g_window_bytes), true);
|
|
win16_bind_segment(0x7200, g_dgroup, sizeof(g_dgroup), true);
|
|
win16_bind_ne_segment(1, 0x7300, g_code, sizeof(g_code), false);
|
|
win16_set_dgroup_selector(0x7200);
|
|
uint8_t stack_anchor = 0;
|
|
uintptr_t stack_base = (uintptr_t)&stack_anchor - 0x8000u;
|
|
win16_bind_segment(0x7000, (void *)stack_base, 0x10000, true);
|
|
g_window = win16_make_far_pointer(0x7100, 0);
|
|
win16_write_u16(g_window, 4, 0x2222);
|
|
win16_write_u16(win16_dgroup_pointer(0x06fe), 0, 0x1234);
|
|
win16_write_u32(
|
|
win16_dgroup_pointer(0x0666), 0,
|
|
win16_make_far_pointer(0x8000, 0x1111));
|
|
memcpy(&g_dgroup[0x02aa], "TDK Pinball Machine 1.00", 25);
|
|
memcpy(&g_dgroup[0x02c3], "INI", 4);
|
|
memcpy(&g_dgroup[0x02c7], "Settings", 9);
|
|
memcpy(&g_dgroup[0x02d0], "Language", 9);
|
|
memcpy(&g_dgroup[0x02d9], "Speed", 6);
|
|
memcpy(&g_dgroup[0x02df], "No timer available!", 20);
|
|
g_heap_free = UINT32_C(0x00400000);
|
|
g_language = 7;
|
|
g_speed = 4;
|
|
g_timer_result = win16_make_far_pointer(0x7400, 0x0100);
|
|
g_setup_window_calls = 0;
|
|
g_palette_realize_calls = 0;
|
|
g_palette_load_calls = 0;
|
|
g_palette_create_calls = 0;
|
|
g_timer_calls = 0;
|
|
g_collision_calls = 0;
|
|
g_quad_calls = 0;
|
|
g_message_calls = 0;
|
|
g_close_calls = 0;
|
|
g_last_message_text = 0;
|
|
g_last_message_owner = 0;
|
|
g_expect_exit = false;
|
|
}
|
|
|
|
static void test_successful_setup(void)
|
|
{
|
|
prepare_fixture();
|
|
tdkpin_setup_game_window(g_window);
|
|
assert(g_setup_window_calls == 1);
|
|
assert(g_palette_realize_calls == 1 && g_palette_load_calls == 1);
|
|
assert(g_palette_create_calls == 1 && g_timer_calls == 1);
|
|
assert(g_collision_calls == 177 && g_quad_calls == 20);
|
|
assert(g_collision_ids[0] == 1 && g_collision_ids[1] == 2);
|
|
assert(g_collision_ids[2] == 169 && g_collision_ids[3] == 170);
|
|
assert(g_collision_ids[4] == 3);
|
|
assert(g_collision_ids[170] == 169 && g_collision_ids[171] == 170);
|
|
assert(g_collision_ids[176] == 175);
|
|
assert(win16_read_u16(win16_dgroup_pointer(0x07cd)) == 1);
|
|
assert(win16_read_u8(win16_far_add_offset(g_window, 0x60)) == 4);
|
|
assert(win16_read_u16(win16_dgroup_pointer(0x0871)) == 2);
|
|
assert(win16_read_far_pointer(g_window, 0x49) == g_timer_result);
|
|
assert(win16_read_u32(win16_dgroup_pointer(0x0720)) == 0x11223344u);
|
|
assert(win16_read_u32(win16_dgroup_pointer(0x0829)) == 140000u);
|
|
assert(win16_read_u32(win16_dgroup_pointer(0x082d)) == 650000u);
|
|
assert(win16_read_u32(win16_dgroup_pointer(0x0831)) == 1300000u);
|
|
assert(win16_read_u32(win16_dgroup_pointer(0x0835)) == 4000000u);
|
|
assert(win16_read_u32(win16_far_add_offset(g_window, 0x0ba2)) == 325u);
|
|
assert(win16_read_u32(win16_far_add_offset(g_window, 0x0ba6)) == 413u);
|
|
assert(g_quads[1].word_0 == 0x0067 && g_quads[1].word_6 == 0x0143);
|
|
assert(g_quads[20].word_0 == 0x0009 && g_quads[20].word_6 == 0x001e);
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
assert(win16_read_u8(win16_far_add_offset(
|
|
g_window, (uint16_t)(0x03cc + id))) ==
|
|
g_final_active[id]);
|
|
assert(win16_read_u16(win16_far_add_offset(
|
|
g_window, (uint16_t)(0x0a42 + id * 2u))) ==
|
|
g_final_contact[id]);
|
|
assert(win16_read_u8(win16_far_add_offset(
|
|
g_window, (uint16_t)(0x61 + id))) == 0);
|
|
}
|
|
assert(g_message_calls == 0 && g_close_calls == 0);
|
|
}
|
|
|
|
static void test_low_memory_failure(void)
|
|
{
|
|
prepare_fixture();
|
|
g_heap_free = UINT32_C(0x003fffff);
|
|
g_expect_exit = true;
|
|
if (setjmp(g_exit_jump) == 0) {
|
|
tdkpin_setup_game_window(g_window);
|
|
assert(false);
|
|
}
|
|
assert(g_message_calls == 1 && g_last_message_owner == 0);
|
|
assert(g_last_message_text == win16_dgroup_pointer(0x0260));
|
|
assert(g_close_calls == 1 && g_setup_window_calls == 0);
|
|
assert(g_collision_calls == 0);
|
|
}
|
|
|
|
static void test_timer_failure(void)
|
|
{
|
|
prepare_fixture();
|
|
g_timer_result = 0;
|
|
g_expect_exit = true;
|
|
if (setjmp(g_exit_jump) == 0) {
|
|
tdkpin_setup_game_window(g_window);
|
|
assert(false);
|
|
}
|
|
assert(g_message_calls == 1 && g_last_message_owner == 0x2222);
|
|
assert(g_last_message_text == win16_dgroup_pointer(0x02df));
|
|
assert(g_close_calls == 0 && g_collision_calls == 0);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_successful_setup();
|
|
test_low_memory_failure();
|
|
test_timer_failure();
|
|
return 0;
|
|
}
|