/* Address-linked high-score persistence from TDKPIN Code1. */ #include "tdkpin_highscores.h" #include "tdkpin_application.h" #include "tdkpin_dialog.h" #include "tdkpin_game_windows.h" enum { HIGHSCORE_XOR_KEY = 0x0010, HIGHSCORE_FILE_HEADER = 0x0014, HIGHSCORE_FILE_HEADER_BYTES = 16, HIGHSCORE_FILE_NAME = 0x0312, HIGHSCORE_LOAD_FILE_NAME = 0x0320, HIGHSCORE_DEFAULT_NAME = 0x032d, HIGHSCORE_EDIT_INITIAL_NAME = 0x0336, HIGHSCORE_EDIT_CAPTION = 0x0349, HIGHSCORE_EDIT_LABEL = 0x0373, HIGHSCORE_WINDOW_TITLE = 0x038b, HIGHSCORE_HEADING = 0x02f4, HIGHSCORE_SINGLE_DIGIT_PREFIX = 0x0300, HIGHSCORE_RANK_SUFFIX = 0x0303, OBJECT_WINDOWS_APPLICATION_POINTER = 0x064e, CURRENT_PLAYER = 0x07d1, HIGHSCORE_INSERT_INDEX = 0x07d2, HIGHSCORE_SCORE_INDEX_ZERO = 0x087a, HIGHSCORE_NAME_INDEX_ZERO = 0x0890, PLAYER_SCORE_INDEX_ZERO = 0x097e, HIGHSCORE_SCORES_BYTES = TDKPIN_HIGHSCORE_COUNT * 4, HIGHSCORE_NAMES_BYTES = TDKPIN_HIGHSCORE_COUNT * TDKPIN_HIGHSCORE_NAME_BYTES, }; static Win16FarPtr highscore_name(uint16_t one_based_index) { return win16_dgroup_pointer((uint16_t)( HIGHSCORE_NAME_INDEX_ZERO + one_based_index * TDKPIN_HIGHSCORE_NAME_BYTES)); } static Win16FarPtr highscore_score(uint16_t one_based_index) { return win16_dgroup_pointer((uint16_t)( HIGHSCORE_SCORE_INDEX_ZERO + one_based_index * 4u)); } static Win16FarPtr highscore_caller_receiver(uint16_t caller_bp) { uint16_t offset = win16_read_stack_u16((uint16_t)(caller_bp + 6)); uint16_t selector = win16_read_stack_u16((uint16_t)(caller_bp + 8)); return win16_make_far_pointer(selector, offset); } 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 void fill_indeterminate_stack_bytes( Win16FarPtr destination, uint16_t count) { /* * ENTER does not initialize SS. The portable model exposes one selected * residue word instead of introducing undefined C reads; alternating its * low/high bytes preserves the target's word-oriented stale-stack model. */ uint16_t residue = win16_indeterminate_u16(); for (uint16_t index = 0; index < count; index++) { win16_write_u8( win16_far_add_offset(destination, index), (uint8_t)(index & 1u ? residue >> 8 : residue)); } } /* * 1000:a8ea -- persist the complete high-score table. * * The binary's 260-byte local payload has the 220-byte name table first and * the 40-byte encrypted score table immediately after it. Each one-based * global score is XORed with the same static dword key. The file order is * header, encrypted scores, then names, despite the reverse local ordering. */ void tdkpin_save_highscores(uint16_t ignored_static_link) { (void)ignored_static_link; HFILE16 file = LCreate16(win16_dgroup_pointer(HIGHSCORE_FILE_NAME), 0); if (file == (HFILE16)-1) { return; } uint8_t local_payload[HIGHSCORE_NAMES_BYTES + HIGHSCORE_SCORES_BYTES]; Win16FarPtr names = win16_stack_pointer(local_payload); Win16FarPtr encrypted_scores = win16_far_add_offset(names, HIGHSCORE_NAMES_BYTES); borland_fill_bytes(names, HIGHSCORE_NAMES_BYTES, 0); uint32_t key = win16_read_u32( win16_dgroup_pointer(HIGHSCORE_XOR_KEY)); for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) { uint16_t score_offset = (uint16_t)( HIGHSCORE_SCORE_INDEX_ZERO + index * 4u); uint16_t name_offset = (uint16_t)( HIGHSCORE_NAME_INDEX_ZERO + index * TDKPIN_HIGHSCORE_NAME_BYTES); win16_write_u32( encrypted_scores, (uint16_t)((index - 1u) * 4u), win16_read_u32(win16_dgroup_pointer(score_offset)) ^ key); (void)borland_far_strcpy( win16_far_add_offset( names, (uint16_t)((index - 1u) * TDKPIN_HIGHSCORE_NAME_BYTES)), win16_dgroup_pointer(name_offset)); } (void)LWrite16( file, win16_dgroup_pointer(HIGHSCORE_FILE_HEADER), HIGHSCORE_FILE_HEADER_BYTES); (void)LWrite16(file, encrypted_scores, HIGHSCORE_SCORES_BYTES); (void)LWrite16(file, names, HIGHSCORE_NAMES_BYTES); (void)LClose16(file); } /* * 1000:a99c -- load and authenticate the complete high-score table. * * The same file is first opened with mode 0 and retried with mode 0x40. A * 16-byte header is compared through the compiler's ShortString helpers. All * _lread returns are ignored by the original: a short score read therefore * leaves stale SS bytes to be decoded, while a short name read leaves the * untouched tail of the global name table. Invalid or unavailable files * install descending 1,000,000..100,000 scores and ten copies of "No Name". */ void tdkpin_load_highscores(uint16_t ignored_static_link) { (void)ignored_static_link; bool loaded = false; HFILE16 file = LOpen16( win16_dgroup_pointer(HIGHSCORE_LOAD_FILE_NAME), 0); if (file == (HFILE16)-1) { file = LOpen16( win16_dgroup_pointer(HIGHSCORE_LOAD_FILE_NAME), 0x40); } if (file != (HFILE16)-1) { uint8_t raw_header[HIGHSCORE_FILE_HEADER_BYTES]; uint8_t encrypted_score_bytes[HIGHSCORE_SCORES_BYTES]; uint8_t file_header_short[256]; uint8_t expected_header_short[256]; Win16FarPtr raw_header_far = win16_stack_pointer(raw_header); Win16FarPtr encrypted_scores = win16_stack_pointer(encrypted_score_bytes); Win16FarPtr file_header = win16_stack_pointer(file_header_short); Win16FarPtr expected_header = win16_stack_pointer(expected_header_short); fill_indeterminate_stack_bytes( raw_header_far, HIGHSCORE_FILE_HEADER_BYTES); fill_indeterminate_stack_bytes( encrypted_scores, HIGHSCORE_SCORES_BYTES); (void)LRead16( file, raw_header_far, HIGHSCORE_FILE_HEADER_BYTES); borland_bytes_to_short( file_header, raw_header_far, HIGHSCORE_FILE_HEADER_BYTES); borland_bytes_to_short( expected_header, win16_dgroup_pointer(HIGHSCORE_FILE_HEADER), HIGHSCORE_FILE_HEADER_BYTES); if (borland_short_compare(file_header, expected_header) == 0) { loaded = true; (void)LRead16(file, encrypted_scores, HIGHSCORE_SCORES_BYTES); (void)LRead16( file, win16_dgroup_pointer( HIGHSCORE_NAME_INDEX_ZERO + TDKPIN_HIGHSCORE_NAME_BYTES), HIGHSCORE_NAMES_BYTES); uint32_t key = win16_read_u32( win16_dgroup_pointer(HIGHSCORE_XOR_KEY)); for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) { uint32_t encrypted = win16_read_u32( win16_far_add_offset( encrypted_scores, (uint16_t)((index - 1u) * 4u))); win16_write_u32( win16_dgroup_pointer((uint16_t)( HIGHSCORE_SCORE_INDEX_ZERO + index * 4u)), 0, encrypted ^ key); } } (void)LClose16(file); } if (!loaded) { for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) { int32_t score = borland_multiply_i32( (int32_t)(11u - index), 100000); win16_write_u32( win16_dgroup_pointer((uint16_t)( HIGHSCORE_SCORE_INDEX_ZERO + index * 4u)), 0, (uint32_t)score); (void)borland_far_strcpy( win16_dgroup_pointer((uint16_t)( HIGHSCORE_NAME_INDEX_ZERO + index * TDKPIN_HIGHSCORE_NAME_BYTES)), win16_dgroup_pointer(HIGHSCORE_DEFAULT_NAME)); } } } /* * 1000:ab3c -- run optional name editing, then create/show the score window. * The argument is the caller's BP; SS:[BP+6] is the actual window receiver. */ void tdkpin_show_highscore_windows(uint16_t caller_bp) { Win16FarPtr receiver = highscore_caller_receiver(caller_bp); uint8_t insertion = win16_read_u8( win16_dgroup_pointer(HIGHSCORE_INSERT_INDEX)); Win16FarPtr application = win16_read_far_pointer( win16_dgroup_pointer(OBJECT_WINDOWS_APPLICATION_POINTER), 0); if (insertion != 0) { Win16FarPtr name = highscore_name(insertion); (void)borland_far_strcpy( name, win16_dgroup_pointer(HIGHSCORE_EDIT_INITIAL_NAME)); Win16FarPtr dialog = object_windows_text_dialog_construct( 0, 0x01ce, 0x0016, name, win16_dgroup_pointer(HIGHSCORE_EDIT_LABEL), win16_dgroup_pointer(HIGHSCORE_EDIT_CAPTION), receiver); (void)win16_call_application_dialog_method( application_virtual_method(application, 0x38), application, dialog); } Win16FarPtr child = tdkpin_centered_child_window_construct( 0, 0x010e, win16_dgroup_pointer(HIGHSCORE_WINDOW_TITLE), receiver); child = win16_call_application_window_method( application_virtual_method(application, 0x34), application, child); win16_write_u32(receiver, 0x41, child); object_windows_show( win16_read_far_pointer(receiver, 0x41), 1); } static bool score_is_greater(uint32_t left, uint32_t right) { int16_t left_high = (int16_t)(left >> 16); int16_t right_high = (int16_t)(right >> 16); return left_high > right_high || (left_high == right_high && (uint16_t)left > (uint16_t)right); } /* 1000:abe5 -- insert the active player's score into the descending top ten. */ void tdkpin_insert_current_score(uint16_t caller_bp) { uint8_t current_player = win16_read_u8( win16_dgroup_pointer(CURRENT_PLAYER)); uint32_t new_score = win16_read_u32( win16_dgroup_pointer((uint16_t)( PLAYER_SCORE_INDEX_ZERO + (uint16_t)current_player * 4u))); uint16_t insertion = 1; while (!score_is_greater( new_score, win16_read_u32(highscore_score(insertion)))) { if (insertion == TDKPIN_HIGHSCORE_COUNT) { return; } insertion++; } if ((uint16_t)(insertion + 1u) <= TDKPIN_HIGHSCORE_COUNT) { for (uint16_t destination = TDKPIN_HIGHSCORE_COUNT; destination != insertion; destination--) { win16_write_u32( highscore_score(destination), 0, win16_read_u32(highscore_score( (uint16_t)(destination - 1u)))); borland_far_copy( highscore_name(destination), highscore_name((uint16_t)(destination - 1u)), TDKPIN_HIGHSCORE_NAME_BYTES); } } win16_write_u32(highscore_score(insertion), 0, new_score); win16_write_u8( win16_dgroup_pointer(HIGHSCORE_INSERT_INDEX), (uint8_t)insertion); tdkpin_show_highscore_windows(caller_bp); tdkpin_save_highscores(caller_bp); } static void text_out_c_string( HDC16 dc, INT16 x, INT16 y, Win16FarPtr text) { (void)TextOut16( dc, x, y, text, (INT16)borland_far_strlen(text)); } /* * 1000:5c13 -- TWindow +4c paint override for the 360x300 high-score panel. * The receiver and PAINTSTRUCT are ABI-visible but unused; all drawing uses * the HDC supplied as the fifth word in the ten-byte Pascal argument frame. */ void tdkpin_paint_highscores( Win16FarPtr ignored_window, PAINTSTRUCT16 *ignored_paint, HDC16 dc) { (void)ignored_window; (void)ignored_paint; HPALETTE16 palette = win16_read_u16( win16_dgroup_pointer(0x444e)); HBITMAP16 background = win16_read_u16( win16_dgroup_pointer(0x0867)); HPALETTE16 old_palette = SelectPalette16(dc, palette, 0); (void)UnrealizeObject16(palette); (void)RealizePalette16(dc); HDC16 background_dc = CreateCompatibleDC16(dc); HGDIOBJ16 old_background_bitmap = SelectObject16(background_dc, background); HDC16 buffer_dc = CreateCompatibleDC16(dc); HBITMAP16 buffer_bitmap = CreateCompatibleBitmap16(dc, 360, 300); HGDIOBJ16 old_buffer_bitmap = SelectObject16(buffer_dc, buffer_bitmap); COLORREF16 old_text_color = GetTextColor16(buffer_dc); UINT16 old_text_alignment = SetTextAlign16(buffer_dc, 6); INT16 old_background_mode = SetBkMode16(buffer_dc, 1); (void)BitBlt16( buffer_dc, 0, 0, 360, 300, background_dc, 32, 204, 0x00cc0020u); (void)SetTextColor16(buffer_dc, 0); uint8_t display_bytes[41]; uint8_t conversion_bytes[41]; uint8_t number_short_bytes[256]; uint8_t name_short_bytes[256]; Win16FarPtr display = win16_stack_pointer(display_bytes); Win16FarPtr conversion = win16_stack_pointer(conversion_bytes); Win16FarPtr number_short = win16_stack_pointer(number_short_bytes); Win16FarPtr name_short = win16_stack_pointer(name_short_bytes); (void)borland_far_strcpy( display, win16_dgroup_pointer(HIGHSCORE_HEADING)); text_out_c_string(buffer_dc, 180, 27, display); for (uint16_t index = 1; index <= TDKPIN_HIGHSCORE_COUNT; index++) { INT16 y = (INT16)(index * 20u + 40u); (void)SetTextAlign16(buffer_dc, 0); (void)borland_far_strcpy( display, win16_dgroup_pointer( index < 10 ? HIGHSCORE_SINGLE_DIGIT_PREFIX : 0x02ff)); borland_format_i32_short_string(255, number_short, 0, index); (void)borland_short_to_c_string(conversion, number_short); (void)borland_far_strcat(display, conversion); (void)borland_far_strcat( display, win16_dgroup_pointer(HIGHSCORE_RANK_SUFFIX)); borland_bytes_to_short( name_short, highscore_name(index), TDKPIN_HIGHSCORE_NAME_BYTES); (void)borland_short_to_c_string(conversion, name_short); (void)borland_far_strcat(display, conversion); text_out_c_string(buffer_dc, 30, y, display); (void)SetTextAlign16(buffer_dc, 2); borland_format_i32_short_string( 255, number_short, 0, (int32_t)win16_read_u32(highscore_score(index))); (void)borland_short_to_c_string(display, number_short); text_out_c_string(buffer_dc, 320, y, display); } (void)BitBlt16( dc, 0, 0, 360, 300, buffer_dc, 0, 0, 0x00cc0020u); (void)SetTextColor16(buffer_dc, old_text_color); (void)SetTextAlign16(buffer_dc, old_text_alignment); (void)SetBkMode16(buffer_dc, old_background_mode); (void)SelectObject16(buffer_dc, old_buffer_bitmap); (void)DeleteObject16(buffer_bitmap); (void)DeleteDC16(buffer_dc); (void)SelectObject16(background_dc, old_background_bitmap); (void)DeleteDC16(background_dc); (void)SelectPalette16(dc, old_palette, 0); }