#include "../tdkpin_highscores.h" #include #include #include #include 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; }