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
661 lines
22 KiB
C
661 lines
22 KiB
C
/* Multimedia timer callback exported at 1000:eab1. */
|
|
|
|
#include "tdkpin_timer_tick.h"
|
|
|
|
#include "tdkpin_arithmetic.h"
|
|
#include "tdkpin_collision_records.h"
|
|
#include "tdkpin_flippers.h"
|
|
#include "tdkpin_gameplay_helpers.h"
|
|
#include "tdkpin_mechanism_render.h"
|
|
#include "tdkpin_message_pump.h"
|
|
#include "tdkpin_panel_animation.h"
|
|
#include "tdkpin_physics.h"
|
|
#include "tdkpin_player_state.h"
|
|
#include "tdkpin_real48.h"
|
|
#include "tdkpin_render.h"
|
|
#include "tdkpin_sound.h"
|
|
#include "tdkpin_turn_state.h"
|
|
|
|
enum {
|
|
RECORDS_BASE = 0x095b,
|
|
RECORD_BYTES = 0x53,
|
|
RECORD_ACTIVE = 0x34,
|
|
};
|
|
|
|
typedef struct {
|
|
uint16_t claw_position;
|
|
int32_t ball_x;
|
|
int32_t ball_y;
|
|
BorlandReal48 velocity_x_factor;
|
|
BorlandReal48 velocity_y_factor;
|
|
bool direct_velocity;
|
|
} ClawRelease;
|
|
|
|
static const ClawRelease g_claw_releases[] = {
|
|
{1, 258000, 78000,
|
|
{{0x80,0x9a,0x99,0x99,0x99,0x99}}, {{0,0,0,0,0,0}}, false},
|
|
{2, 259000, 81000,
|
|
{{0x7f,0x66,0x66,0x66,0x66,0xe6}},
|
|
{{0x7c,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{3, 258000, 78000,
|
|
{{0x80,0x33,0x33,0x33,0x33,0xb3}},
|
|
{{0x7f,0x9a,0x99,0x99,0x99,0x19}}, false},
|
|
{4, 261000, 86000,
|
|
{{0x7f,0x9a,0x99,0x99,0x99,0x99}},
|
|
{{0x7e,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{5, 266000, 90000,
|
|
{{0x7f,0x71,0x3d,0x0a,0xd7,0xa3}},
|
|
{{0x7e,0x7b,0x14,0xae,0x47,0x61}}, false},
|
|
{6, 270000, 94000,
|
|
{{0x7f,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0x9a,0x99,0x99,0x99,0x19}}, false},
|
|
{7, 275000, 97000,
|
|
{{0x7f,0x85,0xeb,0x51,0xb8,0x9e}},
|
|
{{0x80,0x33,0x33,0x33,0x33,0x33}}, false},
|
|
{8, 278000, 98000,
|
|
{{0x7e,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}}, false},
|
|
{9, 282000, 101000,
|
|
{{0x7d,0xcd,0xcc,0xcc,0xcc,0xcc}},
|
|
{{0x80,0x66,0x66,0x66,0x66,0x66}}, false},
|
|
{18, 325000, 92000,
|
|
{{0,0,0,0,0,0}}, {{0,0,0,0,0,0}}, true},
|
|
};
|
|
|
|
static int32_t dgroup_i32(uint16_t offset)
|
|
{
|
|
return (int32_t)win16_read_u32(win16_dgroup_pointer(offset));
|
|
}
|
|
|
|
static void write_dgroup_i32(uint16_t offset, int32_t value)
|
|
{
|
|
win16_write_u32(win16_dgroup_pointer(offset), 0, (uint32_t)value);
|
|
}
|
|
|
|
static uint32_t tick_count(void)
|
|
{
|
|
return win16_read_u32(win16_dgroup_pointer(0x0876));
|
|
}
|
|
|
|
static int32_t quotient_i32(int32_t dividend, int32_t divisor)
|
|
{
|
|
return borland_divide_i32(dividend, divisor).quotient;
|
|
}
|
|
|
|
static int32_t remainder_i32(int32_t dividend, int32_t divisor)
|
|
{
|
|
return borland_divide_i32(dividend, divisor).remainder;
|
|
}
|
|
|
|
static Win16FarPtr collision_record(uint16_t id)
|
|
{
|
|
return win16_dgroup_pointer((uint16_t)(RECORDS_BASE + id * RECORD_BYTES));
|
|
}
|
|
|
|
static void write_receiver_i32(
|
|
Win16FarPtr window, uint16_t offset, int32_t value)
|
|
{
|
|
win16_write_u32(window, offset, (uint32_t)value);
|
|
}
|
|
|
|
static void refresh_region(
|
|
Win16FarPtr window,
|
|
bool background_b,
|
|
INT16 height,
|
|
INT16 width,
|
|
INT16 y,
|
|
INT16 x)
|
|
{
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
if (background_b) {
|
|
tdkpin_restore_background_region_b(height, width, y, x, dc);
|
|
} else {
|
|
tdkpin_restore_background_region_a(height, width, y, x, dc);
|
|
}
|
|
tdkpin_present_background_region(height, width, y, x, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void draw_claw(
|
|
Win16FarPtr window, uint8_t alternate, uint16_t frame)
|
|
{
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_draw_claw_frame(alternate, frame, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static const ClawRelease *find_claw_release(uint16_t claw_position)
|
|
{
|
|
for (size_t index = 0;
|
|
index < sizeof(g_claw_releases) / sizeof(g_claw_releases[0]);
|
|
index++) {
|
|
if (g_claw_releases[index].claw_position == claw_position) {
|
|
return &g_claw_releases[index];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void release_ball_from_claw(
|
|
uint16_t frame_bp, Win16FarPtr window, uint16_t claw_position)
|
|
{
|
|
draw_claw(window, 0, claw_position);
|
|
win16_write_u16(win16_dgroup_pointer(0x086f), 0, 10);
|
|
win16_write_u8(win16_dgroup_pointer(0x0873), 0);
|
|
win16_write_u16(win16_dgroup_pointer(0x2679), 0, 0);
|
|
write_receiver_i32(window, 0x0bca, 0);
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0);
|
|
}
|
|
|
|
const ClawRelease *release = find_claw_release(claw_position);
|
|
if (release != NULL) {
|
|
write_dgroup_i32(0x07d3, release->ball_x);
|
|
write_dgroup_i32(0x07d7, release->ball_y);
|
|
if (release->direct_velocity) {
|
|
write_receiver_i32(window, 0x0baa, 0);
|
|
write_receiver_i32(window, 0x0bae, 1000);
|
|
} else {
|
|
BorlandReal48 scalar = borland_i32_to_real48(
|
|
(int32_t)win16_read_u32(win16_far_add_offset(window, 0x56)));
|
|
write_receiver_i32(
|
|
window,
|
|
0x0baa,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
scalar, release->velocity_x_factor)));
|
|
write_receiver_i32(
|
|
window,
|
|
0x0bae,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
scalar, release->velocity_y_factor)));
|
|
}
|
|
}
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_render_ball_from_caller_frame(frame_bp);
|
|
tdkpin_play_sound_if_not_tilted(16);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0);
|
|
}
|
|
|
|
static void update_claw_state(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07ca)) == 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x0874)) != 0) {
|
|
return;
|
|
}
|
|
uint16_t current = win16_read_u16(win16_dgroup_pointer(0x086d));
|
|
uint16_t target = win16_read_u16(win16_dgroup_pointer(0x086f));
|
|
if (current != target) {
|
|
current = target > current ? (uint16_t)(current + 1u)
|
|
: (uint16_t)(current - 1u);
|
|
win16_write_u16(win16_dgroup_pointer(0x086d), 0, current);
|
|
draw_claw(
|
|
window,
|
|
win16_read_u8(win16_dgroup_pointer(0x0873)),
|
|
current);
|
|
return;
|
|
}
|
|
if (current != 10) {
|
|
release_ball_from_claw(frame_bp, window, current);
|
|
return;
|
|
}
|
|
|
|
draw_claw(window, 0, 0);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x0873)) == 0) {
|
|
win16_write_u8(win16_dgroup_pointer(0x07ca), 0);
|
|
for (uint16_t id = 12; id <= 20; id++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(collision_record(id), RECORD_ACTIVE), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_record_countdowns(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
Win16FarPtr counter = win16_far_add_offset(
|
|
window, (uint16_t)(0x61 + id));
|
|
uint8_t value = win16_read_u8(counter);
|
|
if (value != 0) {
|
|
value--;
|
|
win16_write_u8(counter, value);
|
|
if (value == 0) {
|
|
tdkpin_restore_record_b(frame_bp, id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_target_rotation(uint16_t frame_bp)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c7)) == 0) {
|
|
return;
|
|
}
|
|
int32_t state = dgroup_i32(0x0855);
|
|
if (state < 6) {
|
|
write_dgroup_i32(0x0855, (int32_t)((uint32_t)state + 1u));
|
|
tdkpin_rotate_and_draw_targets(frame_bp);
|
|
} else {
|
|
win16_write_u8(win16_dgroup_pointer(0x07c7), 0);
|
|
}
|
|
}
|
|
|
|
static void publish_previous_position_from_slot(
|
|
Win16FarPtr window, uint16_t slot)
|
|
{
|
|
uint16_t stride = (uint16_t)(slot * 4u);
|
|
write_receiver_i32(window, 0x0bc2, dgroup_i32((uint16_t)(0x07e3 + stride)));
|
|
write_receiver_i32(window, 0x0bc6, dgroup_i32((uint16_t)(0x07eb + stride)));
|
|
}
|
|
|
|
static void restore_slot_region_or_clear_suppression(
|
|
uint16_t frame_bp, Win16FarPtr window, uint16_t slot)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be1)) == 0) {
|
|
publish_previous_position_from_slot(window, slot);
|
|
tdkpin_restore_previous_ball_region(frame_bp, 0);
|
|
} else {
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be1), 0);
|
|
}
|
|
}
|
|
|
|
static void copy_ball_slot_one_to_zero(void)
|
|
{
|
|
write_dgroup_i32(0x07e3, dgroup_i32(0x07e7));
|
|
write_dgroup_i32(0x07eb, dgroup_i32(0x07ef));
|
|
write_dgroup_i32(0x07f3, dgroup_i32(0x07f7));
|
|
write_dgroup_i32(0x07fb, dgroup_i32(0x07ff));
|
|
write_dgroup_i32(0x0803, dgroup_i32(0x0807));
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_dgroup_pointer((uint16_t)(0x080b + index)),
|
|
win16_read_u8(win16_dgroup_pointer(
|
|
(uint16_t)(0x0811 + index))));
|
|
}
|
|
}
|
|
|
|
static void collapse_finished_multiball(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x61)) <= 1) {
|
|
return;
|
|
}
|
|
if (win16_read_u8(win16_dgroup_pointer(0x41f9)) == 0) {
|
|
restore_slot_region_or_clear_suppression(frame_bp, window, 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x41f9), 1);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) {
|
|
publish_previous_position_from_slot(window, 1);
|
|
tdkpin_finish_ball_or_advance_player(frame_bp);
|
|
} else {
|
|
copy_ball_slot_one_to_zero();
|
|
tdkpin_load_ball_slot(frame_bp, 0);
|
|
win16_write_u8(win16_dgroup_pointer(0x424c), 0);
|
|
}
|
|
} else if (win16_read_u8(win16_dgroup_pointer(0x424c)) == 0) {
|
|
restore_slot_region_or_clear_suppression(frame_bp, window, 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 0);
|
|
tdkpin_load_ball_slot(frame_bp, 0);
|
|
}
|
|
}
|
|
|
|
static void handle_ball_reset_request(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be2)) != 1) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be2), 0);
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 2);
|
|
tdkpin_reset_ball_state(frame_bp);
|
|
refresh_region(window, true, 20, 148, 243, 84);
|
|
tdkpin_save_ball_slot(frame_bp, 0);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 1);
|
|
}
|
|
|
|
static void handle_multiball_spawn_request(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0be0)) != 1) {
|
|
return;
|
|
}
|
|
tdkpin_save_ball_slot(frame_bp, 0);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 1);
|
|
write_dgroup_i32(0x07d3, dgroup_i32(0x3969));
|
|
write_dgroup_i32(0x07d7, dgroup_i32(0x396d));
|
|
write_receiver_i32(window, 0x0bc2, dgroup_i32(0x07d3));
|
|
write_receiver_i32(window, 0x0bc6, dgroup_i32(0x07d7));
|
|
tdkpin_restore_previous_ball_region(frame_bp, 0);
|
|
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(player * 0x14u + 0x047b)), 0);
|
|
tdkpin_restore_record_b(frame_bp, 1020);
|
|
write_receiver_i32(window, 0x0baa, 0);
|
|
static const BorlandReal48 four_fifths =
|
|
{{0x80,0xcd,0xcc,0xcc,0xcc,0x4c}};
|
|
write_receiver_i32(
|
|
window,
|
|
0x0bae,
|
|
borland_real48_round_to_i32(borland_real48_multiply(
|
|
borland_i32_to_real48((int32_t)win16_read_u32(
|
|
win16_far_add_offset(window, 0x56))),
|
|
four_fifths)));
|
|
write_receiver_i32(window, 0x0bca, 0);
|
|
for (uint16_t index = 0; index < 6; index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(window, (uint16_t)(0x0bce + index)), 0);
|
|
}
|
|
win16_write_u16(win16_dgroup_pointer(0x399a), 0, 2);
|
|
tdkpin_save_ball_slot(frame_bp, 1);
|
|
tdkpin_update_dynamic_ball_record(frame_bp, 2);
|
|
win16_write_u8(win16_dgroup_pointer(0x41f9), 1);
|
|
win16_write_u8(win16_dgroup_pointer(0x424c), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x61), 2);
|
|
win16_write_u8(win16_dgroup_pointer(0x0874), 1);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0be0), 0);
|
|
}
|
|
|
|
static void active_ball_tick(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
update_claw_state(frame_bp, window);
|
|
update_record_countdowns(frame_bp, window);
|
|
update_target_rotation(frame_bp);
|
|
collapse_finished_multiball(frame_bp, window);
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bd8)) == 0 &&
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdf)) == 0) {
|
|
tdkpin_simulate_ball_and_dispatch_collision(frame_bp);
|
|
}
|
|
handle_ball_reset_request(frame_bp, window);
|
|
handle_multiball_spawn_request(frame_bp, window);
|
|
}
|
|
|
|
static void update_intro_collision_visibility(uint16_t frame_bp, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 2) == 0) {
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 32), 2);
|
|
if (phase >= 0 && phase <= 7) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(phase + 140));
|
|
} else if (phase >= 8 && phase <= 15) {
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(phase + 132));
|
|
}
|
|
}
|
|
if (remainder_i32(tick, 4) == 0) {
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 32), 4);
|
|
if (phase == 0) {
|
|
tdkpin_restore_record_or_item_a(frame_bp, 1001);
|
|
tdkpin_restore_record_b(frame_bp, 1002);
|
|
} else if (phase >= 1 && phase <= 4) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(phase + 1001));
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(phase + 1000));
|
|
} else if (phase >= 5 && phase <= 8) {
|
|
tdkpin_restore_record_or_item_a(
|
|
frame_bp, (uint16_t)(1009 - phase));
|
|
tdkpin_restore_record_b(
|
|
frame_bp, (uint16_t)(1010 - phase));
|
|
}
|
|
}
|
|
int32_t phase32 = remainder_i32(tick, 32);
|
|
if (phase32 == 0 || phase32 == 16) {
|
|
bool background_b = phase32 == 16;
|
|
uint16_t ids[] = {153, 154, 6};
|
|
for (size_t index = 0; index < sizeof(ids) / sizeof(ids[0]); index++) {
|
|
if (background_b) {
|
|
tdkpin_restore_record_b(frame_bp, ids[index]);
|
|
} else {
|
|
tdkpin_restore_record_or_item_a(frame_bp, ids[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_intro_record_strip(
|
|
Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 4) != 0) {
|
|
return;
|
|
}
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 40), 4);
|
|
bool background_b = phase >= 5;
|
|
int32_t normalized = background_b ? phase - 5 : phase;
|
|
uint16_t id = (uint16_t)(normalized * 3 + 90);
|
|
Win16FarPtr record = collision_record(id);
|
|
INT16 left = (INT16)win16_read_u16(
|
|
win16_far_add_offset(record, 0x4b));
|
|
INT16 top = (INT16)win16_read_u16(
|
|
win16_far_add_offset(record, 0x4d));
|
|
INT16 width = (INT16)(
|
|
win16_read_u16(win16_far_add_offset(record, 0x4f)) -
|
|
(uint16_t)left - 10u);
|
|
INT16 height = (INT16)(
|
|
win16_read_u16(win16_far_add_offset(record, 0x51)) -
|
|
(uint16_t)top + 1u);
|
|
refresh_region(window, background_b, height, width, top, left);
|
|
}
|
|
|
|
static void update_intro_four_point_chase(
|
|
Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 4) != 0) {
|
|
return;
|
|
}
|
|
static const INT16 y[4] = {209, 214, 219, 224};
|
|
static const INT16 x[4] = {150, 164, 178, 192};
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 16), 4);
|
|
if (phase < 0 || phase > 3) {
|
|
return;
|
|
}
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_restore_background_region_a(9, 9, y[phase], x[phase], dc);
|
|
tdkpin_present_background_region(9, 9, y[phase], x[phase], dc);
|
|
uint16_t previous = phase == 0 ? 3u : (uint16_t)(phase - 1);
|
|
tdkpin_restore_background_region_b(9, 9, y[previous], x[previous], dc);
|
|
tdkpin_present_background_region(9, 9, y[previous], x[previous], dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void update_intro_item_status(Win16FarPtr window, int32_t tick)
|
|
{
|
|
if (remainder_i32(tick, 8) != 0) {
|
|
return;
|
|
}
|
|
int32_t phase = quotient_i32(quotient_i32(tick, 144), 8);
|
|
int16_t item = (int16_t)(phase < 10 ? phase : 18 - phase);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
tdkpin_draw_item_status(item, dc);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void draw_intro_score_marquee(Win16FarPtr window)
|
|
{
|
|
uint32_t counter = win16_read_u32(win16_dgroup_pointer(0x087a)) + 1u;
|
|
win16_write_u32(win16_dgroup_pointer(0x087a), 0, counter);
|
|
HWND16 handle = win16_read_u16(win16_far_add_offset(window, 4));
|
|
HDC16 dc = GetDC16(handle);
|
|
HDC16 source = CreateCompatibleDC16(dc);
|
|
HDC16 temporary = CreateCompatibleDC16(dc);
|
|
HBITMAP16 bitmap = CreateCompatibleBitmap16(dc, 30, 284);
|
|
HGDIOBJ16 old_source = SelectObject16(
|
|
source, win16_read_u16(win16_dgroup_pointer(0x085f)));
|
|
HGDIOBJ16 old_temporary = SelectObject16(temporary, bitmap);
|
|
(void)BitBlt16(
|
|
temporary, 0, 0, 30, 100, source, 473, 277, 0x00cc0020u);
|
|
(void)BitBlt16(
|
|
temporary, 0, 100, 30, 137, source, 472, 233, 0x00cc0020u);
|
|
(void)SelectObject16(
|
|
source, win16_read_u16(win16_dgroup_pointer(0x0861)));
|
|
|
|
int32_t phase = remainder_i32((int32_t)counter, 49);
|
|
if (phase > 0 && phase < 22) {
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
0,
|
|
30,
|
|
237,
|
|
source,
|
|
(INT16)(400 + (phase - 1) * 16),
|
|
0,
|
|
0x008800c6u);
|
|
} else {
|
|
INT16 split = (INT16)((phase - 22) * 16);
|
|
INT16 upper_height = (INT16)(237 - split);
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
0,
|
|
30,
|
|
upper_height,
|
|
source,
|
|
(INT16)(400 + (phase - 1) * 16),
|
|
0,
|
|
0x008800c6u);
|
|
(void)BitBlt16(
|
|
temporary,
|
|
0,
|
|
upper_height,
|
|
30,
|
|
split,
|
|
source,
|
|
160,
|
|
0,
|
|
0x008800c6u);
|
|
}
|
|
(void)BitBlt16(
|
|
dc, 233, 372, 30, 237, temporary, 0, 0, 0x00cc0020u);
|
|
(void)SelectObject16(source, old_source);
|
|
(void)SelectObject16(temporary, old_temporary);
|
|
(void)DeleteObject16(bitmap);
|
|
(void)DeleteDC16(source);
|
|
(void)DeleteDC16(temporary);
|
|
(void)ReleaseDC16(handle, dc);
|
|
}
|
|
|
|
static void idle_transition_tick(uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c9)) == 0) {
|
|
return;
|
|
}
|
|
int32_t tick = (int32_t)tick_count();
|
|
update_intro_collision_visibility(frame_bp, tick);
|
|
update_intro_record_strip(window, tick);
|
|
update_intro_four_point_chase(window, tick);
|
|
update_intro_item_status(window, tick);
|
|
if (remainder_i32(tick, 6) == 0) {
|
|
draw_intro_score_marquee(window);
|
|
}
|
|
}
|
|
|
|
static void update_panel_completion(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 1);
|
|
int32_t frame = dgroup_i32(0x0851);
|
|
if (frame < 281) {
|
|
if (frame == 1) {
|
|
for (uint16_t id = 129; id <= 133; id++) {
|
|
win16_write_u16(
|
|
win16_far_add_offset(collision_record(id), 0x43), 0, 0);
|
|
}
|
|
}
|
|
write_dgroup_i32(0x0851, (int32_t)((uint32_t)frame + 1u));
|
|
tdkpin_draw_panel_animation(frame_bp);
|
|
return;
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(0x07c8), 0);
|
|
win16_write_u8(win16_far_add_offset(window, 0x0bdf), 0);
|
|
write_dgroup_i32(0x0851, 0);
|
|
refresh_region(window, true, 70, 68, 41, 87);
|
|
uint8_t player = win16_read_u8(win16_dgroup_pointer(0x07d1));
|
|
for (uint16_t item = 15; item <= 19; item++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(
|
|
window,
|
|
(uint16_t)(player * 0x14u + item + 0x0467u)),
|
|
0);
|
|
}
|
|
}
|
|
|
|
static void initialize_game_if_requested(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
uint8_t player_count = win16_read_u8(win16_dgroup_pointer(0x07d0));
|
|
int16_t marker = (int16_t)win16_read_u16(win16_dgroup_pointer(
|
|
(uint16_t)(0x081f + player_count * 2u)));
|
|
if (marker != -1) {
|
|
return;
|
|
}
|
|
tdkpin_initialize_player_state(frame_bp);
|
|
tdkpin_redraw_all_players(frame_bp);
|
|
tdkpin_refresh_player_progress_markers(frame_bp);
|
|
tdkpin_reset_ball_state(frame_bp);
|
|
refresh_region(window, true, 20, 148, 243, 84);
|
|
win16_write_u32(win16_dgroup_pointer(0x0876), 0, 0);
|
|
}
|
|
|
|
void tdkpin_timer_tick_with_frame(
|
|
uint16_t frame_bp, Win16FarPtr window)
|
|
{
|
|
if (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 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bd8)) != 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x0bd9)) != 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x07cc)) != 0 ||
|
|
win16_read_u8(win16_far_add_offset(window, 0x51)) != 0 ||
|
|
win16_read_u8(win16_dgroup_pointer(0x07c4)) == 0) {
|
|
return;
|
|
}
|
|
win16_write_u8(win16_far_add_offset(window, 0x51), 1);
|
|
if (win16_read_u8(win16_dgroup_pointer(0x07c5)) != 0) {
|
|
for (uint16_t id = 1; id <= 175; id++) {
|
|
tdkpin_refresh_collision_record(frame_bp, id);
|
|
}
|
|
win16_write_u8(win16_dgroup_pointer(0x07c5), 0);
|
|
}
|
|
win16_write_u32(
|
|
win16_dgroup_pointer(0x0876), 0, tick_count() + 1u);
|
|
|
|
if (win16_read_u8(win16_far_add_offset(window, 0x0bdd)) != 0) {
|
|
active_ball_tick(frame_bp, window);
|
|
} else {
|
|
idle_transition_tick(frame_bp, window);
|
|
update_panel_completion(frame_bp, window);
|
|
initialize_game_if_requested(frame_bp, window);
|
|
}
|
|
|
|
tdkpin_update_flippers(frame_bp);
|
|
tdkpin_drain_messages_except_input();
|
|
win16_write_u8(win16_far_add_offset(window, 0x51), 0);
|
|
uint16_t tilt_counter = win16_read_u16(
|
|
win16_far_add_offset(window, 0x5e));
|
|
if (tilt_counter != 0) {
|
|
/* 1000:fd73 reads game_tilted but both branches converge here. */
|
|
(void)win16_read_u8(win16_dgroup_pointer(0x07c6));
|
|
win16_write_u16(window, 0x5e, (uint16_t)(tilt_counter - 1u));
|
|
}
|
|
}
|
|
|
|
void tdkpin_timer_tick(
|
|
Win16FarPtr window, uint32_t ignored_callback_argument)
|
|
{
|
|
(void)ignored_callback_argument;
|
|
tdkpin_timer_tick_with_frame(win16_current_stack_pointer(), window);
|
|
}
|