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
689 lines
22 KiB
C
689 lines
22 KiB
C
#include "../tdkpin_highscores.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
enum {
|
|
FILE_HEADER_BYTES = 16,
|
|
SCORE_BYTES = TDKPIN_HIGHSCORE_COUNT * 4,
|
|
NAME_BYTES = TDKPIN_HIGHSCORE_COUNT * TDKPIN_HIGHSCORE_NAME_BYTES,
|
|
FILE_BYTES = FILE_HEADER_BYTES + SCORE_BYTES + NAME_BYTES,
|
|
};
|
|
|
|
static uint8_t g_dgroup[0x5000];
|
|
static uint8_t g_objects[0x1000];
|
|
static uint8_t g_call_stack[0x1000];
|
|
static uint8_t g_file_bytes[FILE_BYTES];
|
|
static uint8_t g_input_bytes[FILE_BYTES];
|
|
static bool g_create_fails;
|
|
static unsigned g_create_calls;
|
|
static HFILE16 g_open_results[2];
|
|
static unsigned g_open_calls;
|
|
static uint16_t g_input_position;
|
|
static uint16_t g_input_size;
|
|
static uint16_t g_read_limits[3];
|
|
static unsigned g_read_calls;
|
|
static unsigned g_write_calls;
|
|
static unsigned g_close_calls;
|
|
static uint16_t g_file_position;
|
|
static unsigned g_dialog_construct_calls;
|
|
static unsigned g_dialog_exec_calls;
|
|
static unsigned g_child_construct_calls;
|
|
static unsigned g_make_window_calls;
|
|
static unsigned g_show_calls;
|
|
static Win16FarPtr g_dialog;
|
|
static Win16FarPtr g_child;
|
|
static Win16FarPtr g_made_child;
|
|
static Win16FarPtr g_application;
|
|
static Win16FarPtr g_receiver;
|
|
static Win16FarPtr g_exec_dialog_procedure;
|
|
static Win16FarPtr g_make_window_procedure;
|
|
static unsigned g_select_palette_calls;
|
|
static unsigned g_unrealize_calls;
|
|
static unsigned g_realize_calls;
|
|
static unsigned g_compatible_dc_calls;
|
|
static unsigned g_compatible_bitmap_calls;
|
|
static unsigned g_select_object_calls;
|
|
static unsigned g_bitblt_calls;
|
|
static unsigned g_text_color_calls;
|
|
static unsigned g_text_align_calls;
|
|
static unsigned g_bk_mode_calls;
|
|
static unsigned g_text_out_calls;
|
|
static unsigned g_delete_object_calls;
|
|
static unsigned g_delete_dc_calls;
|
|
static char g_drawn_text[21][64];
|
|
static INT16 g_drawn_x[21];
|
|
static INT16 g_drawn_y[21];
|
|
|
|
static Win16FarPtr dgroup_at(uint16_t offset)
|
|
{
|
|
return win16_make_far_pointer(0x5000, offset);
|
|
}
|
|
|
|
static Win16FarPtr object_at(uint16_t offset)
|
|
{
|
|
return win16_make_far_pointer(0x6000, offset);
|
|
}
|
|
|
|
Win16FarPtr object_windows_text_dialog_construct(
|
|
Win16FarPtr dialog,
|
|
uint16_t vmt,
|
|
INT16 control_id,
|
|
Win16FarPtr text_buffer,
|
|
Win16FarPtr label,
|
|
Win16FarPtr caption,
|
|
Win16FarPtr parent)
|
|
{
|
|
assert(dialog == 0 && vmt == 0x01ce && control_id == 0x0016);
|
|
uint8_t insertion = g_dgroup[0x07d2];
|
|
assert(text_buffer == dgroup_at((uint16_t)(0x0890 + insertion * 22u)));
|
|
assert(label == dgroup_at(0x0373));
|
|
assert(caption == dgroup_at(0x0349));
|
|
assert(parent == g_receiver);
|
|
g_dialog_construct_calls++;
|
|
return g_dialog;
|
|
}
|
|
|
|
Win16FarPtr tdkpin_centered_child_window_construct(
|
|
Win16FarPtr window,
|
|
uint16_t vmt,
|
|
Win16FarPtr title,
|
|
Win16FarPtr parent)
|
|
{
|
|
assert(window == 0 && vmt == 0x010e);
|
|
assert(title == dgroup_at(0x038b) && parent == g_receiver);
|
|
g_child_construct_calls++;
|
|
return g_child;
|
|
}
|
|
|
|
int16_t win16_call_application_dialog_method(
|
|
Win16FarPtr procedure,
|
|
Win16FarPtr application,
|
|
Win16FarPtr dialog)
|
|
{
|
|
assert(procedure == g_exec_dialog_procedure);
|
|
assert(application == g_application && dialog == g_dialog);
|
|
g_dialog_exec_calls++;
|
|
return 17;
|
|
}
|
|
|
|
Win16FarPtr win16_call_application_window_method(
|
|
Win16FarPtr procedure,
|
|
Win16FarPtr application,
|
|
Win16FarPtr window)
|
|
{
|
|
assert(procedure == g_make_window_procedure);
|
|
assert(application == g_application && window == g_child);
|
|
g_make_window_calls++;
|
|
return g_made_child;
|
|
}
|
|
|
|
void object_windows_show(Win16FarPtr object, INT16 command)
|
|
{
|
|
assert(object == g_made_child && command == 1);
|
|
g_show_calls++;
|
|
}
|
|
|
|
HPALETTE16 SelectPalette16(
|
|
HDC16 dc, HPALETTE16 palette, BOOL16 force_background)
|
|
{
|
|
assert(dc == 0x1111 && force_background == 0);
|
|
if (g_select_palette_calls == 0) {
|
|
assert(palette == 0x2222);
|
|
g_select_palette_calls++;
|
|
return 0x3333;
|
|
}
|
|
assert(g_select_palette_calls == 1 && palette == 0x3333);
|
|
g_select_palette_calls++;
|
|
return 0x2222;
|
|
}
|
|
|
|
BOOL16 UnrealizeObject16(HGDIOBJ16 object)
|
|
{
|
|
assert(object == 0x2222);
|
|
g_unrealize_calls++;
|
|
return 1;
|
|
}
|
|
|
|
UINT16 RealizePalette16(HDC16 dc)
|
|
{
|
|
assert(dc == 0x1111);
|
|
g_realize_calls++;
|
|
return 5;
|
|
}
|
|
|
|
HDC16 CreateCompatibleDC16(HDC16 dc)
|
|
{
|
|
assert(dc == 0x1111 && g_compatible_dc_calls < 2);
|
|
return (HDC16)(0x4001 + g_compatible_dc_calls++);
|
|
}
|
|
|
|
HBITMAP16 CreateCompatibleBitmap16(HDC16 dc, INT16 width, INT16 height)
|
|
{
|
|
assert(dc == 0x1111 && width == 360 && height == 300);
|
|
g_compatible_bitmap_calls++;
|
|
return 0x5002;
|
|
}
|
|
|
|
HGDIOBJ16 SelectObject16(HDC16 dc, HGDIOBJ16 object)
|
|
{
|
|
switch (g_select_object_calls++) {
|
|
case 0:
|
|
assert(dc == 0x4001 && object == 0x5001);
|
|
return 0x6001;
|
|
case 1:
|
|
assert(dc == 0x4002 && object == 0x5002);
|
|
return 0x6002;
|
|
case 2:
|
|
assert(dc == 0x4002 && object == 0x6002);
|
|
return 0x5002;
|
|
default:
|
|
assert(g_select_object_calls == 4);
|
|
assert(dc == 0x4001 && object == 0x6001);
|
|
return 0x5001;
|
|
}
|
|
}
|
|
|
|
COLORREF16 GetTextColor16(HDC16 dc)
|
|
{
|
|
assert(dc == 0x4002);
|
|
return 0x00112233u;
|
|
}
|
|
|
|
COLORREF16 SetTextColor16(HDC16 dc, COLORREF16 color)
|
|
{
|
|
assert(dc == 0x4002 && g_text_color_calls < 2);
|
|
assert(color == (g_text_color_calls == 0 ? 0 : 0x00112233u));
|
|
g_text_color_calls++;
|
|
return color == 0 ? 0x00112233u : 0;
|
|
}
|
|
|
|
UINT16 SetTextAlign16(HDC16 dc, UINT16 alignment)
|
|
{
|
|
assert(dc == 0x4002 && g_text_align_calls < 22);
|
|
if (g_text_align_calls == 0) {
|
|
assert(alignment == 6);
|
|
} else if (g_text_align_calls == 21) {
|
|
assert(alignment == 7);
|
|
} else {
|
|
assert(alignment == (g_text_align_calls & 1u ? 0 : 2));
|
|
}
|
|
g_text_align_calls++;
|
|
return g_text_align_calls == 1 ? 7 : 0;
|
|
}
|
|
|
|
INT16 SetBkMode16(HDC16 dc, INT16 mode)
|
|
{
|
|
assert(dc == 0x4002 && g_bk_mode_calls < 2);
|
|
assert(mode == (g_bk_mode_calls == 0 ? 1 : 2));
|
|
g_bk_mode_calls++;
|
|
return mode == 1 ? 2 : 1;
|
|
}
|
|
|
|
BOOL16 BitBlt16(
|
|
HDC16 destination,
|
|
INT16 destination_x,
|
|
INT16 destination_y,
|
|
INT16 width,
|
|
INT16 height,
|
|
HDC16 source,
|
|
INT16 source_x,
|
|
INT16 source_y,
|
|
uint32_t raster_operation)
|
|
{
|
|
assert(destination_x == 0 && destination_y == 0);
|
|
assert(width == 360 && height == 300);
|
|
assert(raster_operation == 0x00cc0020u);
|
|
if (g_bitblt_calls == 0) {
|
|
assert(destination == 0x4002 && source == 0x4001);
|
|
assert(source_x == 32 && source_y == 204);
|
|
} else {
|
|
assert(g_bitblt_calls == 1);
|
|
assert(destination == 0x1111 && source == 0x4002);
|
|
assert(source_x == 0 && source_y == 0);
|
|
}
|
|
g_bitblt_calls++;
|
|
return 1;
|
|
}
|
|
|
|
BOOL16 TextOut16(
|
|
HDC16 dc,
|
|
INT16 x,
|
|
INT16 y,
|
|
Win16FarPtr text,
|
|
INT16 character_count)
|
|
{
|
|
assert(dc == 0x4002 && g_text_out_calls < 21);
|
|
assert(character_count >= 0 && character_count < 64);
|
|
for (INT16 index = 0; index < character_count; index++) {
|
|
g_drawn_text[g_text_out_calls][index] = (char)win16_read_u8(
|
|
win16_far_add_offset(text, (uint16_t)index));
|
|
}
|
|
g_drawn_text[g_text_out_calls][character_count] = '\0';
|
|
g_drawn_x[g_text_out_calls] = x;
|
|
g_drawn_y[g_text_out_calls] = y;
|
|
g_text_out_calls++;
|
|
return 1;
|
|
}
|
|
|
|
BOOL16 DeleteObject16(HGDIOBJ16 object)
|
|
{
|
|
assert(object == 0x5002);
|
|
g_delete_object_calls++;
|
|
return 1;
|
|
}
|
|
|
|
BOOL16 DeleteDC16(HDC16 dc)
|
|
{
|
|
assert(dc == (g_delete_dc_calls == 0 ? 0x4002 : 0x4001));
|
|
g_delete_dc_calls++;
|
|
return 1;
|
|
}
|
|
|
|
HFILE16 LCreate16(Win16FarPtr file_name, INT16 attributes)
|
|
{
|
|
static const char expected_name[] = "HiScores.Dat";
|
|
assert(file_name == dgroup_at(0x0312));
|
|
assert(attributes == 0);
|
|
for (size_t index = 0; index < sizeof(expected_name); index++) {
|
|
assert(win16_read_u8(win16_far_add_offset(file_name, index)) ==
|
|
(uint8_t)expected_name[index]);
|
|
}
|
|
g_create_calls++;
|
|
return g_create_fails ? (HFILE16)-1 : (HFILE16)7;
|
|
}
|
|
|
|
HFILE16 LOpen16(Win16FarPtr file_name, INT16 mode)
|
|
{
|
|
assert(file_name == dgroup_at(0x0320));
|
|
assert(g_open_calls < 2);
|
|
assert(mode == (g_open_calls == 0 ? 0 : 0x40));
|
|
return g_open_results[g_open_calls++];
|
|
}
|
|
|
|
UINT16 LRead16(HFILE16 file, Win16FarPtr bytes, UINT16 byte_count)
|
|
{
|
|
assert(file == 9 && g_read_calls < 3);
|
|
static const uint16_t expected_counts[3] = {
|
|
FILE_HEADER_BYTES, SCORE_BYTES, NAME_BYTES};
|
|
assert(byte_count == expected_counts[g_read_calls]);
|
|
uint16_t copied = byte_count;
|
|
if (copied > g_read_limits[g_read_calls]) {
|
|
copied = g_read_limits[g_read_calls];
|
|
}
|
|
uint16_t available = (uint16_t)(g_input_size - g_input_position);
|
|
if (copied > available) {
|
|
copied = available;
|
|
}
|
|
for (uint16_t index = 0; index < copied; index++) {
|
|
win16_write_u8(
|
|
win16_far_add_offset(bytes, index),
|
|
g_input_bytes[g_input_position + index]);
|
|
}
|
|
g_input_position = (uint16_t)(g_input_position + copied);
|
|
g_read_calls++;
|
|
return copied;
|
|
}
|
|
|
|
UINT16 LWrite16(HFILE16 file, Win16FarPtr bytes, UINT16 byte_count)
|
|
{
|
|
assert(file == 7);
|
|
if (g_write_calls == 0) {
|
|
assert(bytes == dgroup_at(0x0014));
|
|
assert(byte_count == FILE_HEADER_BYTES);
|
|
} else if (g_write_calls == 1) {
|
|
assert(byte_count == SCORE_BYTES);
|
|
} else {
|
|
assert(g_write_calls == 2 && byte_count == NAME_BYTES);
|
|
}
|
|
for (uint16_t index = 0; index < byte_count; index++) {
|
|
g_file_bytes[g_file_position + index] =
|
|
win16_read_u8(win16_far_add_offset(bytes, index));
|
|
}
|
|
g_file_position = (uint16_t)(g_file_position + byte_count);
|
|
g_write_calls++;
|
|
return byte_count;
|
|
}
|
|
|
|
HFILE16 LClose16(HFILE16 file)
|
|
{
|
|
assert(file == 7 || file == 9);
|
|
g_close_calls++;
|
|
return 0;
|
|
}
|
|
|
|
static void prepare_fixture(void)
|
|
{
|
|
memset(g_dgroup, 0, sizeof(g_dgroup));
|
|
memset(g_objects, 0, sizeof(g_objects));
|
|
memset(g_call_stack, 0, sizeof(g_call_stack));
|
|
memset(g_file_bytes, 0xcc, sizeof(g_file_bytes));
|
|
memset(g_input_bytes, 0, sizeof(g_input_bytes));
|
|
memcpy(&g_dgroup[0x0010], "IWIKTDK Highscores\0\0", 20);
|
|
memcpy(&g_dgroup[0x0312], "HiScores.Dat", 13);
|
|
memcpy(&g_dgroup[0x0320], "HiScores.Dat", 13);
|
|
memcpy(&g_dgroup[0x032d], "No Name", 8);
|
|
memcpy(&g_dgroup[0x0336], "TDK Pinball Player", 19);
|
|
memcpy(
|
|
&g_dgroup[0x0349],
|
|
"Congratulations! This is a Top Ten Score!",
|
|
42);
|
|
memcpy(&g_dgroup[0x0373], "Please input your name:", 24);
|
|
memcpy(&g_dgroup[0x038b], "HighScore", 10);
|
|
memcpy(&g_dgroup[0x02f4], "HIGH SCORES", 12);
|
|
memcpy(&g_dgroup[0x0300], " ", 3);
|
|
memcpy(&g_dgroup[0x0303], ". ", 3);
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
uint32_t score = 0x10203040u + (uint32_t)index * 0x01010101u;
|
|
g_dgroup[0x087a + index * 4u] = (uint8_t)score;
|
|
g_dgroup[0x087b + index * 4u] = (uint8_t)(score >> 8);
|
|
g_dgroup[0x087c + index * 4u] = (uint8_t)(score >> 16);
|
|
g_dgroup[0x087d + index * 4u] = (uint8_t)(score >> 24);
|
|
char name[TDKPIN_HIGHSCORE_NAME_BYTES];
|
|
memset(name, 0, sizeof(name));
|
|
name[0] = 'P';
|
|
name[1] = (char)('0' + index / 10u);
|
|
name[2] = (char)('0' + index % 10u);
|
|
memcpy(
|
|
&g_dgroup[0x0890 + index * TDKPIN_HIGHSCORE_NAME_BYTES],
|
|
name,
|
|
sizeof(name));
|
|
}
|
|
g_create_fails = false;
|
|
g_create_calls = 0;
|
|
g_open_results[0] = (HFILE16)-1;
|
|
g_open_results[1] = (HFILE16)-1;
|
|
g_open_calls = 0;
|
|
g_input_position = 0;
|
|
g_input_size = FILE_BYTES;
|
|
for (unsigned index = 0; index < 3; index++) {
|
|
g_read_limits[index] = UINT16_MAX;
|
|
}
|
|
g_read_calls = 0;
|
|
g_write_calls = 0;
|
|
g_close_calls = 0;
|
|
g_file_position = 0;
|
|
g_dialog_construct_calls = 0;
|
|
g_dialog_exec_calls = 0;
|
|
g_child_construct_calls = 0;
|
|
g_make_window_calls = 0;
|
|
g_show_calls = 0;
|
|
g_dialog = object_at(0x200);
|
|
g_child = object_at(0x300);
|
|
g_made_child = object_at(0x350);
|
|
g_application = object_at(0x400);
|
|
g_receiver = object_at(0x100);
|
|
g_exec_dialog_procedure = win16_make_far_pointer(0x8000, 0x0038);
|
|
g_make_window_procedure = win16_make_far_pointer(0x8000, 0x0034);
|
|
g_select_palette_calls = 0;
|
|
g_unrealize_calls = 0;
|
|
g_realize_calls = 0;
|
|
g_compatible_dc_calls = 0;
|
|
g_compatible_bitmap_calls = 0;
|
|
g_select_object_calls = 0;
|
|
g_bitblt_calls = 0;
|
|
g_text_color_calls = 0;
|
|
g_text_align_calls = 0;
|
|
g_bk_mode_calls = 0;
|
|
g_text_out_calls = 0;
|
|
g_delete_object_calls = 0;
|
|
g_delete_dc_calls = 0;
|
|
memset(g_drawn_text, 0, sizeof(g_drawn_text));
|
|
|
|
win16_reset_segment_bindings();
|
|
win16_bind_segment(0x5000, g_dgroup, sizeof(g_dgroup), true);
|
|
win16_bind_segment(0x6000, g_objects, sizeof(g_objects), true);
|
|
win16_bind_segment(0x7100, g_call_stack, sizeof(g_call_stack), true);
|
|
win16_set_dgroup_selector(0x5000);
|
|
uint8_t stack_anchor = 0;
|
|
uintptr_t stack_base = (uintptr_t)&stack_anchor - 0x8000u;
|
|
win16_bind_segment(0x7000, (void *)stack_base, 0x10000, true);
|
|
win16_set_stack_state(0x7100, 0);
|
|
win16_write_stack_u16(0x0106, win16_far_offset(g_receiver));
|
|
win16_write_stack_u16(0x0108, win16_far_selector(g_receiver));
|
|
win16_write_u16(g_application, 0, 0x0200);
|
|
win16_write_u32(dgroup_at(0x0234), 0, g_make_window_procedure);
|
|
win16_write_u32(dgroup_at(0x0238), 0, g_exec_dialog_procedure);
|
|
win16_write_u32(
|
|
dgroup_at(0x064e), 0, g_application);
|
|
win16_write_u16(dgroup_at(0x444e), 0, 0x2222);
|
|
win16_write_u16(dgroup_at(0x0867), 0, 0x5001);
|
|
}
|
|
|
|
static uint32_t read_dgroup_u32(uint16_t offset)
|
|
{
|
|
return (uint32_t)g_dgroup[offset] |
|
|
((uint32_t)g_dgroup[offset + 1] << 8) |
|
|
((uint32_t)g_dgroup[offset + 2] << 16) |
|
|
((uint32_t)g_dgroup[offset + 3] << 24);
|
|
}
|
|
|
|
static void prepare_valid_input_file(void)
|
|
{
|
|
memcpy(g_input_bytes, &g_dgroup[0x0014], FILE_HEADER_BYTES);
|
|
const uint32_t key = 0x4b495749u;
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
uint32_t score = 0x01020304u * index;
|
|
uint32_t encrypted = score ^ key;
|
|
uint16_t score_offset = (uint16_t)(
|
|
FILE_HEADER_BYTES + (index - 1u) * 4u);
|
|
g_input_bytes[score_offset] = (uint8_t)encrypted;
|
|
g_input_bytes[score_offset + 1] = (uint8_t)(encrypted >> 8);
|
|
g_input_bytes[score_offset + 2] = (uint8_t)(encrypted >> 16);
|
|
g_input_bytes[score_offset + 3] = (uint8_t)(encrypted >> 24);
|
|
uint16_t name_offset = (uint16_t)(
|
|
FILE_HEADER_BYTES + SCORE_BYTES +
|
|
(index - 1u) * TDKPIN_HIGHSCORE_NAME_BYTES);
|
|
g_input_bytes[name_offset] = 'L';
|
|
g_input_bytes[name_offset + 1] = (uint8_t)('0' + index / 10u);
|
|
g_input_bytes[name_offset + 2] = (uint8_t)('0' + index % 10u);
|
|
}
|
|
}
|
|
|
|
static uint32_t file_u32(uint16_t offset)
|
|
{
|
|
return (uint32_t)g_file_bytes[offset] |
|
|
((uint32_t)g_file_bytes[offset + 1] << 8) |
|
|
((uint32_t)g_file_bytes[offset + 2] << 16) |
|
|
((uint32_t)g_file_bytes[offset + 3] << 24);
|
|
}
|
|
|
|
static void test_complete_file_layout(void)
|
|
{
|
|
prepare_fixture();
|
|
tdkpin_save_highscores(0xbeef);
|
|
assert(g_create_calls == 1 && g_write_calls == 3 && g_close_calls == 1);
|
|
assert(g_file_position == FILE_BYTES);
|
|
assert(memcmp(g_file_bytes, "TDK Highscores\0\0", FILE_HEADER_BYTES) == 0);
|
|
|
|
const uint32_t key = 0x4b495749u;
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
uint32_t score = 0x10203040u + (uint32_t)index * 0x01010101u;
|
|
assert(file_u32((uint16_t)(
|
|
FILE_HEADER_BYTES + (index - 1u) * 4u)) ==
|
|
(score ^ key));
|
|
const uint8_t *name = &g_file_bytes[
|
|
FILE_HEADER_BYTES + SCORE_BYTES +
|
|
(index - 1u) * TDKPIN_HIGHSCORE_NAME_BYTES];
|
|
assert(name[0] == 'P');
|
|
assert(name[1] == (uint8_t)('0' + index / 10u));
|
|
assert(name[2] == (uint8_t)('0' + index % 10u));
|
|
for (uint16_t byte = 3; byte < TDKPIN_HIGHSCORE_NAME_BYTES; byte++) {
|
|
assert(name[byte] == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_create_failure_is_silent(void)
|
|
{
|
|
prepare_fixture();
|
|
g_create_fails = true;
|
|
tdkpin_save_highscores(0x1234);
|
|
assert(g_create_calls == 1);
|
|
assert(g_write_calls == 0 && g_close_calls == 0 && g_file_position == 0);
|
|
}
|
|
|
|
static void assert_default_table(void)
|
|
{
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
assert(read_dgroup_u32((uint16_t)(0x087a + index * 4u)) ==
|
|
(uint32_t)(11u - index) * 100000u);
|
|
const uint8_t *name =
|
|
&g_dgroup[0x0890 + index * TDKPIN_HIGHSCORE_NAME_BYTES];
|
|
assert(memcmp(name, "No Name", 8) == 0);
|
|
}
|
|
}
|
|
|
|
static void test_valid_load_and_open_retry(void)
|
|
{
|
|
prepare_fixture();
|
|
prepare_valid_input_file();
|
|
g_open_results[0] = (HFILE16)-1;
|
|
g_open_results[1] = 9;
|
|
tdkpin_load_highscores(0xaaaa);
|
|
assert(g_open_calls == 2 && g_read_calls == 3 && g_close_calls == 1);
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
assert(read_dgroup_u32((uint16_t)(0x087a + index * 4u)) ==
|
|
0x01020304u * index);
|
|
const uint8_t *name =
|
|
&g_dgroup[0x0890 + index * TDKPIN_HIGHSCORE_NAME_BYTES];
|
|
assert(name[0] == 'L');
|
|
assert(name[1] == (uint8_t)('0' + index / 10u));
|
|
assert(name[2] == (uint8_t)('0' + index % 10u));
|
|
}
|
|
}
|
|
|
|
static void test_missing_and_invalid_files_install_defaults(void)
|
|
{
|
|
prepare_fixture();
|
|
tdkpin_load_highscores(0);
|
|
assert(g_open_calls == 2 && g_read_calls == 0 && g_close_calls == 0);
|
|
assert_default_table();
|
|
|
|
prepare_fixture();
|
|
prepare_valid_input_file();
|
|
g_input_bytes[0] ^= 0xffu;
|
|
g_open_results[0] = 9;
|
|
tdkpin_load_highscores(0);
|
|
assert(g_open_calls == 1 && g_read_calls == 1 && g_close_calls == 1);
|
|
assert_default_table();
|
|
}
|
|
|
|
static void test_short_score_read_retains_stack_residue(void)
|
|
{
|
|
prepare_fixture();
|
|
prepare_valid_input_file();
|
|
g_open_results[0] = 9;
|
|
g_read_limits[1] = 4;
|
|
win16_set_indeterminate_u16(0x5a5a);
|
|
tdkpin_load_highscores(0);
|
|
assert(g_read_calls == 3 && g_close_calls == 1);
|
|
assert(read_dgroup_u32(0x087e) == 0x01020304u);
|
|
for (uint16_t index = 2; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
assert(read_dgroup_u32((uint16_t)(0x087a + index * 4u)) ==
|
|
(0x5a5a5a5au ^ 0x4b495749u));
|
|
}
|
|
}
|
|
|
|
static void test_highscore_windows(void)
|
|
{
|
|
prepare_fixture();
|
|
g_dgroup[0x07d2] = 0;
|
|
tdkpin_show_highscore_windows(0x0100);
|
|
assert(g_dialog_construct_calls == 0 && g_dialog_exec_calls == 0);
|
|
assert(g_child_construct_calls == 1 && g_make_window_calls == 1);
|
|
assert(g_show_calls == 1);
|
|
assert(win16_read_far_pointer(g_receiver, 0x41) == g_made_child);
|
|
|
|
prepare_fixture();
|
|
g_dgroup[0x07d2] = 3;
|
|
memcpy(&g_dgroup[0x0890 + 3 * 22], "old", 4);
|
|
tdkpin_show_highscore_windows(0x0100);
|
|
assert(memcmp(&g_dgroup[0x0890 + 3 * 22],
|
|
"TDK Pinball Player", 19) == 0);
|
|
assert(g_dialog_construct_calls == 1 && g_dialog_exec_calls == 1);
|
|
assert(g_child_construct_calls == 1 && g_make_window_calls == 1);
|
|
assert(g_show_calls == 1);
|
|
}
|
|
|
|
static void prepare_descending_scores(void)
|
|
{
|
|
for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) {
|
|
uint32_t score = (uint32_t)(11u - index) * 100u;
|
|
win16_write_u32(dgroup_at((uint16_t)(0x087a + index * 4u)), 0, score);
|
|
uint8_t *name = &g_dgroup[0x0890 + index * 22u];
|
|
memset(name, 0, 22);
|
|
name[0] = 'P';
|
|
name[1] = (uint8_t)('0' + index / 10u);
|
|
name[2] = (uint8_t)('0' + index % 10u);
|
|
}
|
|
g_dgroup[0x07d1] = 1;
|
|
}
|
|
|
|
static void test_score_insertion_and_no_qualification(void)
|
|
{
|
|
prepare_fixture();
|
|
prepare_descending_scores();
|
|
win16_write_u32(dgroup_at(0x0982), 0, 750);
|
|
tdkpin_insert_current_score(0x0100);
|
|
assert(g_dgroup[0x07d2] == 4);
|
|
assert(read_dgroup_u32(0x088a) == 750);
|
|
assert(read_dgroup_u32(0x088e) == 700);
|
|
assert(memcmp(&g_dgroup[0x0890 + 5 * 22], "P04", 4) == 0);
|
|
assert(memcmp(&g_dgroup[0x0890 + 4 * 22],
|
|
"TDK Pinball Player", 19) == 0);
|
|
assert(g_dialog_exec_calls == 1 && g_show_calls == 1);
|
|
assert(g_create_calls == 1 && g_write_calls == 3 && g_close_calls == 1);
|
|
|
|
prepare_fixture();
|
|
prepare_descending_scores();
|
|
win16_write_u32(dgroup_at(0x0982), 0, 100);
|
|
tdkpin_insert_current_score(0x0100);
|
|
assert(g_dgroup[0x07d2] == 0);
|
|
assert(g_dialog_exec_calls == 0 && g_show_calls == 0);
|
|
assert(g_create_calls == 0 && g_write_calls == 0 && g_close_calls == 0);
|
|
}
|
|
|
|
static void test_highscore_paint(void)
|
|
{
|
|
prepare_fixture();
|
|
prepare_descending_scores();
|
|
PAINTSTRUCT16 ignored_paint;
|
|
memset(&ignored_paint, 0xa5, sizeof(ignored_paint));
|
|
tdkpin_paint_highscores(object_at(0x777), &ignored_paint, 0x1111);
|
|
|
|
assert(g_select_palette_calls == 2);
|
|
assert(g_unrealize_calls == 1 && g_realize_calls == 1);
|
|
assert(g_compatible_dc_calls == 2 && g_compatible_bitmap_calls == 1);
|
|
assert(g_select_object_calls == 4 && g_bitblt_calls == 2);
|
|
assert(g_text_color_calls == 2 && g_text_align_calls == 22);
|
|
assert(g_bk_mode_calls == 2 && g_text_out_calls == 21);
|
|
assert(g_delete_object_calls == 1 && g_delete_dc_calls == 2);
|
|
|
|
assert(strcmp(g_drawn_text[0], "HIGH SCORES") == 0);
|
|
assert(g_drawn_x[0] == 180 && g_drawn_y[0] == 27);
|
|
assert(strcmp(g_drawn_text[1], " 1. P01") == 0);
|
|
assert(g_drawn_x[1] == 30 && g_drawn_y[1] == 60);
|
|
assert(strcmp(g_drawn_text[2], "1000") == 0);
|
|
assert(g_drawn_x[2] == 320 && g_drawn_y[2] == 60);
|
|
assert(strcmp(g_drawn_text[19], "10. P10") == 0);
|
|
assert(g_drawn_x[19] == 30 && g_drawn_y[19] == 240);
|
|
assert(strcmp(g_drawn_text[20], "100") == 0);
|
|
assert(g_drawn_x[20] == 320 && g_drawn_y[20] == 240);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_complete_file_layout();
|
|
test_create_failure_is_silent();
|
|
test_valid_load_and_open_retry();
|
|
test_missing_and_invalid_files_install_defaults();
|
|
test_short_score_read_retains_stack_residue();
|
|
test_highscore_windows();
|
|
test_score_insertion_and_no_qualification();
|
|
test_highscore_paint();
|
|
return 0;
|
|
}
|