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
368 lines
14 KiB
C
368 lines
14 KiB
C
/* Address-linked keyboard handlers at 1000:638e and 1000:6bf8. */
|
|
|
|
#include "tdkpin_key_input.h"
|
|
|
|
#include "tdkpin_application.h"
|
|
#include "tdkpin_arithmetic.h"
|
|
#include "tdkpin_random.h"
|
|
#include "tdkpin_real48.h"
|
|
#include "tdkpin_sound.h"
|
|
|
|
static Win16FarPtr application_virtual_method(
|
|
Win16FarPtr application, uint16_t slot)
|
|
{
|
|
uint16_t vmt = win16_read_u16(application);
|
|
return win16_read_far_pointer(win16_dgroup_pointer(vmt), slot);
|
|
}
|
|
|
|
static bool no_auxiliary_windows(Win16FarPtr window)
|
|
{
|
|
return win16_read_u16(win16_far_add_offset(window, 0x41)) == 0 &&
|
|
win16_read_u16(win16_far_add_offset(window, 0x43)) == 0 &&
|
|
win16_read_u16(win16_far_add_offset(window, 0x45)) == 0 &&
|
|
win16_read_u16(win16_far_add_offset(window, 0x47)) == 0;
|
|
}
|
|
|
|
static uint16_t open_help_window(Win16FarPtr window)
|
|
{
|
|
Win16FarPtr child = tdkpin_full_child_window_construct(
|
|
0,
|
|
0x017e,
|
|
win16_dgroup_pointer(0x0306),
|
|
window);
|
|
Win16FarPtr application = win16_read_far_pointer(
|
|
win16_dgroup_pointer(0x064e), 0);
|
|
child = win16_call_application_window_method(
|
|
application_virtual_method(application, 0x34),
|
|
application,
|
|
child);
|
|
win16_write_u32(window, 0x45, child);
|
|
object_windows_show(win16_read_far_pointer(window, 0x45), 1);
|
|
return win16_indeterminate_u16();
|
|
}
|
|
|
|
static void apply_down_key(Win16FarPtr window)
|
|
{
|
|
int32_t scalar = (int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x5a));
|
|
int32_t step = borland_multiply_i32(scalar, 50);
|
|
uint32_t velocity = win16_read_u32(
|
|
win16_far_add_offset(window, 0x0bae));
|
|
velocity -= (uint32_t)step;
|
|
win16_write_u32(window, 0x0bae, velocity);
|
|
win16_write_u32(window, 0x0baa, 0);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07d0)) == 0) {
|
|
return;
|
|
}
|
|
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
INT16 y = 231;
|
|
for (int32_t multiple = 1; multiple <= 10; multiple++) {
|
|
int32_t threshold = borland_multiply_i32(step, -multiple);
|
|
if (threshold < (int32_t)velocity) {
|
|
y = (INT16)(multiple * 21);
|
|
break;
|
|
}
|
|
}
|
|
tdkpin_draw_board_decoration_row_on_dc(y, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
uint16_t tdkpin_handle_key_press(
|
|
Win16FarPtr window, Win16FarPtr event)
|
|
{
|
|
if (no_auxiliary_windows(window)) {
|
|
uint16_t scan_code = win16_read_u16(
|
|
win16_far_add_offset(event, 8)) & 0x00ffu;
|
|
if (scan_code == 0x3b) {
|
|
return open_help_window(window);
|
|
}
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) == 0 &&
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdf)) == 0 &&
|
|
scan_code == 0x50) {
|
|
apply_down_key(window);
|
|
} else if (win16_read_u8(win16_dgroup_pointer(0x07c6)) == 0) {
|
|
if (scan_code == 0x1d) {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bda), 1);
|
|
}
|
|
if (scan_code == 0x1c) {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdb), 1);
|
|
}
|
|
}
|
|
}
|
|
win16_write_u16(event, 0x0a, 0);
|
|
win16_write_u16(event, 0x0c, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t random_horizontal_nudge(int32_t scalar)
|
|
{
|
|
static const BorlandReal48 scale =
|
|
{{0x85, 0x00, 0x00, 0x00, 0x00, 0x20}};
|
|
static const BorlandReal48 offset =
|
|
{{0x84, 0x00, 0x00, 0x00, 0x00, 0x20}};
|
|
BorlandReal48 random = borland_random_real48_registers();
|
|
BorlandReal48 factor = borland_real48_subtract(
|
|
borland_real48_multiply(random, scale), offset);
|
|
return borland_real48_round_to_i32(
|
|
borland_real48_multiply(borland_i32_to_real48(scalar), factor));
|
|
}
|
|
|
|
static void apply_release_down_impulse(Win16FarPtr window)
|
|
{
|
|
int32_t scalar = (int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x5a));
|
|
int32_t base = (int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x56));
|
|
uint32_t velocity = win16_read_u32(
|
|
win16_far_add_offset(window, 0x0bae));
|
|
velocity -= (uint32_t)borland_multiply_i32(scalar, 100);
|
|
|
|
int32_t signed_velocity = (int32_t)velocity;
|
|
uint32_t negative_base_bits = 0u - (uint32_t)base;
|
|
int32_t negative_base = (int32_t)negative_base_bits;
|
|
if (signed_velocity < negative_base) {
|
|
velocity = negative_base_bits +
|
|
borland_random_below((uint16_t)base) / 40u;
|
|
} else {
|
|
/* 1000:71eb loads Real48 0.6 as CX:SI:DI =
|
|
* 9a80:9999:1999. Keep the exact target encoding here. */
|
|
static const BorlandReal48 factor_three_fifths =
|
|
{{0x80, 0x9a, 0x99, 0x99, 0x99, 0x19}};
|
|
int32_t scaled_base = borland_real48_round_to_i32(
|
|
borland_real48_multiply(
|
|
borland_i32_to_real48(base), factor_three_fifths));
|
|
int32_t upper = (int32_t)(0u - (uint32_t)scaled_base);
|
|
if (upper < signed_velocity) {
|
|
velocity -= borland_random_below((uint16_t)base) / 40u;
|
|
}
|
|
}
|
|
win16_write_u32(window, 0x0bae, velocity);
|
|
win16_write_u32(window, 0x0baa, 0);
|
|
win16_write_u32(win16_dgroup_pointer(0x07fb), 0, velocity);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdd), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bde), 0);
|
|
tdkpin_play_sound_if_not_tilted(2);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_blit_bitmap_and_present(
|
|
win16_read_u16(win16_dgroup_pointer(0x0865)),
|
|
18,
|
|
21,
|
|
426,
|
|
314,
|
|
dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void add_wrap_to_dgroup(uint16_t address, int32_t amount)
|
|
{
|
|
Win16FarPtr value = win16_dgroup_pointer(address);
|
|
win16_write_u32(value, 0, win16_read_u32(value) + (uint32_t)amount);
|
|
}
|
|
|
|
static void apply_nudge(Win16FarPtr window, uint16_t scan_code)
|
|
{
|
|
int32_t scalar = (int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x5a));
|
|
tdkpin_play_sound_if_not_tilted(19);
|
|
int32_t horizontal = 0;
|
|
int32_t vertical = 0;
|
|
if (scan_code == 0x2a || scan_code == 0x51) {
|
|
int32_t amount = borland_multiply_i32(
|
|
(int32_t)(50u - borland_random_below(20)), scalar);
|
|
horizontal = scan_code == 0x2a ? -amount : amount;
|
|
} else {
|
|
horizontal = random_horizontal_nudge(scalar);
|
|
vertical = (int32_t)(0u - (uint32_t)borland_multiply_i32(
|
|
(int32_t)(60u - borland_random_below(20)), scalar));
|
|
}
|
|
win16_write_u32(
|
|
window,
|
|
0x0baa,
|
|
win16_read_u32(win16_far_add_offset(window, 0x0baa)) +
|
|
(uint32_t)horizontal);
|
|
win16_write_u32(
|
|
window,
|
|
0x0bae,
|
|
win16_read_u32(win16_far_add_offset(window, 0x0bae)) +
|
|
(uint32_t)vertical);
|
|
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x61)) > 1) {
|
|
if (scan_code == 0x2a || scan_code == 0x51) {
|
|
int32_t slot_amount = borland_multiply_i32(scalar, 40);
|
|
if (scan_code == 0x2a) {
|
|
slot_amount = (int32_t)(0u - (uint32_t)slot_amount);
|
|
}
|
|
add_wrap_to_dgroup(0x07f3, slot_amount);
|
|
add_wrap_to_dgroup(0x07f7, slot_amount);
|
|
} else {
|
|
int32_t slot_x = borland_multiply_i32(
|
|
(int32_t)borland_random_below(21) - 10, scalar);
|
|
int32_t slot_y = (int32_t)(0u - (uint32_t)borland_multiply_i32(
|
|
(int32_t)borland_random_below(100) + 50, scalar));
|
|
add_wrap_to_dgroup(0x07f3, slot_x);
|
|
add_wrap_to_dgroup(0x07f7, slot_x);
|
|
add_wrap_to_dgroup(0x07fb, slot_y);
|
|
add_wrap_to_dgroup(0x07ff, slot_y);
|
|
}
|
|
}
|
|
|
|
uint16_t counter = (uint16_t)(
|
|
win16_read_u16(win16_far_add_offset(window, 0x5e)) + 25u);
|
|
win16_write_u16(window, 0x5e, counter);
|
|
uint16_t threshold = (uint16_t)(borland_random_below(10) + 30u);
|
|
if (threshold < counter) {
|
|
tdkpin_play_sound_if_not_tilted(20);
|
|
win16_write_u8(win16_dgroup_pointer(0x07c6), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bda), 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdb), 0);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_restore_background_region_a(20, 148, 243, 84, dc);
|
|
tdkpin_present_background_region(20, 148, 243, 84, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
}
|
|
|
|
static void toggle_sound(void)
|
|
{
|
|
uint8_t enabled = win16_read_u8(win16_dgroup_pointer(0x43f2));
|
|
if (enabled == 0) {
|
|
if (win16_read_u16(win16_dgroup_pointer(0x43ee)) != 0) {
|
|
win16_write_u8(win16_dgroup_pointer(0x43f2), 1);
|
|
}
|
|
} else {
|
|
win16_write_u8(win16_dgroup_pointer(0x43f2), 0);
|
|
}
|
|
}
|
|
|
|
static void add_player(Win16FarPtr window)
|
|
{
|
|
win16_write_u8(win16_dgroup_pointer(0x07c9), 0);
|
|
tdkpin_play_sound_if_not_tilted(1);
|
|
uint8_t player_count = (uint8_t)(
|
|
win16_read_u8(win16_dgroup_pointer(0x07d0)) + 1u);
|
|
win16_write_u8(win16_dgroup_pointer(0x07d0), player_count);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdd), 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x07c5), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
|
|
for (uint16_t player = 1; player <= 4; player++) {
|
|
win16_write_u16(
|
|
win16_dgroup_pointer((uint16_t)(0x081f + player * 2u)),
|
|
0,
|
|
0xffff);
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(
|
|
0x0061 + player * 0x00afu + id)),
|
|
win16_read_u8(win16_far_add_offset(
|
|
window, (uint16_t)(0x03cc + id))));
|
|
win16_write_u16(
|
|
window,
|
|
(uint16_t)(0x036c + player * 0x015eu + id * 2u),
|
|
win16_read_u16(win16_far_add_offset(
|
|
window, (uint16_t)(0x0a42 + id * 2u))));
|
|
}
|
|
HDC16 dc = GetDC16(handle);
|
|
if (player_count < player) {
|
|
win16_write_u32(
|
|
win16_dgroup_pointer((uint16_t)(0x097e + player * 4u)),
|
|
0,
|
|
0);
|
|
tdkpin_restore_background_region_b(
|
|
42,
|
|
284,
|
|
(INT16)((player - 1u) * 45u + 270u),
|
|
348,
|
|
dc);
|
|
tdkpin_present_background_region(
|
|
42,
|
|
284,
|
|
(INT16)((player - 1u) * 45u + 270u),
|
|
348,
|
|
dc);
|
|
}
|
|
tdkpin_draw_item_status(0, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
if (player == 1) {
|
|
dc = GetDC16(handle);
|
|
tdkpin_restore_background_region_a(30, 284, 232, 371, dc);
|
|
tdkpin_present_background_region(30, 284, 232, 371, dc);
|
|
tdkpin_draw_score(dc, 0);
|
|
tdkpin_update_status_display(1, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
}
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
Win16FarPtr record = win16_dgroup_pointer((uint16_t)(
|
|
0x095b + id * 0x53u));
|
|
win16_write_u8(
|
|
win16_far_add_offset(record, 0x34),
|
|
win16_read_u8(win16_far_add_offset(
|
|
window, (uint16_t)(0x03cc + id))));
|
|
win16_write_u16(
|
|
record,
|
|
0x43,
|
|
win16_read_u16(win16_far_add_offset(
|
|
window, (uint16_t)(0x0a42 + id * 2u))));
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(0x07d1), 1);
|
|
}
|
|
|
|
uint16_t tdkpin_handle_key_release(
|
|
Win16FarPtr window, Win16FarPtr event)
|
|
{
|
|
if (no_auxiliary_windows(window)) {
|
|
uint16_t scan_code = win16_read_u16(
|
|
win16_far_add_offset(event, 8)) & 0x00ffu;
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07cf)) != 0 &&
|
|
scan_code == 0x3c) {
|
|
win16_write_u32(win16_dgroup_pointer(0x0855), 0, 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x07c7), 1);
|
|
}
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07cf)) != 0 &&
|
|
scan_code == 0x41) {
|
|
win16_write_u32(
|
|
win16_dgroup_pointer(0x07d3),
|
|
0,
|
|
win16_read_u32(win16_dgroup_pointer(0x3340)));
|
|
win16_write_u32(
|
|
win16_dgroup_pointer(0x07d7),
|
|
0,
|
|
win16_read_u32(win16_dgroup_pointer(0x3344)));
|
|
win16_write_u32(window, 0x0baa, 0);
|
|
win16_write_u32(window, 0x0bae, 0);
|
|
}
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) == 0 &&
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdf)) == 0 &&
|
|
scan_code == 0x50 &&
|
|
win16_read_u8(win16_dgroup_pointer(0x07d0)) != 0) {
|
|
apply_release_down_impulse(window);
|
|
} else if (scan_code == 0x1d) {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bda), 0);
|
|
} else if (scan_code == 0x1c) {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdb), 0);
|
|
} else if (win16_read_u8(win16_dgroup_pointer(0x07c6)) == 0 &&
|
|
(scan_code == 0x2a || scan_code == 0x51 ||
|
|
scan_code == 0x39)) {
|
|
apply_nudge(window, scan_code);
|
|
} else if (scan_code == 0x58) {
|
|
toggle_sound();
|
|
} else if (win16_read_u8(
|
|
win16_far_add_offset(window, 0x0bde)) != 0 &&
|
|
(scan_code == 0x4e || scan_code == 0x1b) &&
|
|
win16_read_u8(win16_dgroup_pointer(0x07d0)) < 4) {
|
|
add_player(window);
|
|
}
|
|
}
|
|
win16_write_u16(event, 0x0a, 0);
|
|
win16_write_u16(event, 0x0c, 0);
|
|
return 0;
|
|
}
|