From 140d48823988b098fb2a80e7daa046dc191c4350 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 12 Apr 2025 23:35:00 -0700 Subject: [PATCH] support more BB save slots; add client patch --- src/Client.cc | 12 +- src/Client.hh | 10 +- src/CommandFormats.hh | 6 +- src/SendCommands.cc | 2 +- .../MoreSaveSlots.59NL.patch.s | 594 ++++++++++++++++++ .../System/WriteCallToCode-59NL.x86.inc.s | 29 +- 6 files changed, 624 insertions(+), 29 deletions(-) create mode 100644 system/client-functions/BlueBurstExclusive/MoreSaveSlots.59NL.patch.s diff --git a/src/Client.cc b/src/Client.cc index 9318337a..2ca72f8f 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -672,14 +672,14 @@ string Client::system_filename() const { return phosg::string_printf("system/players/system_%s.psosys", this->login->bb_license->username.c_str()); } -string Client::character_filename(const std::string& bb_username, int8_t index) { +string Client::character_filename(const std::string& bb_username, ssize_t index) { if (bb_username.empty()) { throw logic_error("non-BB players do not have character data"); } if (index < 0) { throw logic_error("character index is not set"); } - return phosg::string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index); + return phosg::string_printf("system/players/player_%s_%zd.psochar", bb_username.c_str(), index); } string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) { @@ -687,7 +687,7 @@ string Client::backup_character_filename(uint32_t account_id, size_t index, bool account_id, index, is_ep3 ? "pso3char" : "psochar"); } -string Client::character_filename(int8_t index) const { +string Client::character_filename(ssize_t index) const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } @@ -738,9 +738,9 @@ string Client::legacy_player_filename() const { throw logic_error("character index is not set"); } return phosg::string_printf( - "system/players/player_%s_%hhd.nsc", + "system/players/player_%s_%zd.nsc", this->login->bb_license->username.c_str(), - static_cast(this->bb_character_index + 1)); + static_cast(this->bb_character_index + 1)); } void Client::create_character_file( @@ -1088,7 +1088,7 @@ bool Client::use_shared_bank() { } } -void Client::use_character_bank(int8_t index) { +void Client::use_character_bank(ssize_t index) { this->use_default_bank(); if (index != this->bb_character_index) { auto files_manager = this->require_server_state()->player_files_manager; diff --git a/src/Client.hh b/src/Client.hh index 3132fe06..b82ff93e 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -252,7 +252,7 @@ public: uint32_t telepipe_lobby_id; TelepipeState telepipe_state; std::shared_ptr ep3_config; // Null for non-Ep3 - int8_t bb_character_index; + ssize_t bb_character_index; // -1 = not set ItemData bb_identify_result; std::array, 3> bb_shop_contents; @@ -361,9 +361,9 @@ public: std::shared_ptr level_table); std::string system_filename() const; - static std::string character_filename(const std::string& bb_username, int8_t index); + static std::string character_filename(const std::string& bb_username, ssize_t index); static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3); - std::string character_filename(int8_t index = -1) const; + std::string character_filename(ssize_t index = -1) const; std::string guild_card_filename() const; std::string shared_bank_filename() const; @@ -391,7 +391,7 @@ public: const PlayerBank200& current_bank() const; std::shared_ptr current_bank_character(); bool use_shared_bank(); // Returns true if the bank exists; false if it was created - void use_character_bank(int8_t bb_character_index); + void use_character_bank(ssize_t bb_character_index); void use_default_bank(); void print_inventory(FILE* stream) const; @@ -407,7 +407,7 @@ private: std::shared_ptr guild_card_data; std::shared_ptr external_bank; std::shared_ptr external_bank_character; - int8_t external_bank_character_index; + ssize_t external_bank_character_index; uint64_t last_play_time_update; void save_and_clear_external_bank(); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index aeca365b..e2b8e494 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3112,7 +3112,7 @@ check_struct_size(S_TournamentGameDetails_Ep3_E3, 0x73C); struct C_PlayerPreviewRequest_BB_E3 { le_int32_t character_index = 0; - le_uint32_t unused = 0; + le_uint32_t unknown_a1 = 0; } __packed_ws__(C_PlayerPreviewRequest_BB_E3, 0x08); // E4: CARD lobby battle table state (Episode 3) @@ -3319,7 +3319,7 @@ struct C_GuildCardChecksum_01E8 { // 02E8 (S->C): Accept/decline guild card file checksum // If needs_update is nonzero, the client will request the guild card file by // sending an 03E8 command. If needs_update is zero, the client will skip -// downloading the guild card file and send a 04EB command (requesting the +// downloading the guild card file and send an 04EB command (requesting the // stream file) instead. struct S_GuildCardChecksumResponse_BB_02E8 { @@ -3643,7 +3643,7 @@ struct S_StreamFileChunk_BB_02EB { struct C_LeaveCharacterSelect_BB_00EC { // Reason codes: // 0 = canceled - // 1 = recreate character + // 1 = create or recreate character // 2 = dressing room le_uint32_t reason = 0; } __packed_ws__(C_LeaveCharacterSelect_BB_00EC, 4); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 6e29bcb6..1dec3130 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -739,7 +739,7 @@ void send_stream_file_chunk_bb(shared_ptr c, uint32_t chunk_index) { } void send_approve_player_choice_bb(shared_ptr c) { - S_ApprovePlayerChoice_BB_00E4 cmd = {c->bb_character_index, 1}; + S_ApprovePlayerChoice_BB_00E4 cmd = {static_cast(c->bb_character_index), 1}; send_command_t(c, 0x00E4, 0x00000000, cmd); } diff --git a/system/client-functions/BlueBurstExclusive/MoreSaveSlots.59NL.patch.s b/system/client-functions/BlueBurstExclusive/MoreSaveSlots.59NL.patch.s new file mode 100644 index 00000000..8f71812e --- /dev/null +++ b/system/client-functions/BlueBurstExclusive/MoreSaveSlots.59NL.patch.s @@ -0,0 +1,594 @@ +# This patch changes the number of BB character save slots from 4 to any number +# up to 127. + +# This patch is for documentation purposes only; it works when used as a server +# patch via newserv, but is decidedly inconvenient to use via this method. This +# is because it affects logic that runs before any patches can be sent by the +# server, so the player has to connect once to get the patch, then disconnect +# and connect again to use the additional slots. + +# As written, this patch changes the slot count from 4 to 12. To use a +# different slot count, first compute the following values: +# slot count = your desired number of player slots (must be >= 4, <= 127) +# total file size = (slot count * 0x2EA4) + 0x14 +# bgm_test_songs_unlocked offset = total file size - 0x10 +# save_count offset = total file size - 8 +# round2_seed offset = total file size - 4 +# Then, for each of the above, search for the string to the left of the = sign +# and change the values used in all of the matching lines. + +.meta name="More save slots" +.meta description="" +.meta hide_from_patches_menu + +entry_ptr: +reloc0: + .offsetof start + + # Include a few functions first +write_call_to_code: + .include WriteCallToCode-59NL +memcpy: + .include CopyData + ret + +start: + # Apply all necessary patches + call apply_enable_scroll_patch + call apply_fix_scroll_patch1 + call apply_fix_scroll_patch2 + call apply_fix_file_index + call apply_preview_window_fix + call apply_static_patches + # Rewrite the existing char file to have the appropriate size; this must be + # done after all patches are applied + call update_existing_char_file_list + jmp update_existing_char_file_list_memcard + + + +apply_enable_scroll_patch: + # This patch enables scrolling behavior within the character list + push -5 # Jump size (negative = jmp instead of call) + push 0x00413B7F # Jump address + call get_code_size_for_enable_scroll + .deltaof enable_scroll_start, enable_scroll_end +get_code_size_for_enable_scroll: + pop eax + push dword [eax] + call enable_scroll_end +enable_scroll_start: + mov eax, dword ptr [edi + 0x28] # cursor = char_select_menu->cursor_obj (TAdSelectCurGC*) + or dword [eax + 0x01F8], 3 # cursor->flags |= 3 # Enable scrolling + mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0] + mov ecx, [eax + 0xEC] # ecx = scroll_bar->client_id + imul ecx, ecx, 0x24 + # Set up scroll bar graphics (in struct at scroll_bar + 0x1C) + mov dword [eax + ecx + 0x1C], 0x439D0000 + mov dword [eax + ecx + 0x20], 0x43360000 + mov dword [eax + ecx + 0x24], 0x439D0000 + mov dword [eax + ecx + 0x28], 0x4392AB85 + mov dword [eax + ecx + 0x2C], 0x40400000 + mov dword [eax + ecx + 0x30], 0x425EA3D7 + mov dword [eax + ecx + 0x34], 0x00000008 + mov dword [eax + ecx + 0x38], 0x00000000 + mov dword [eax + ecx + 0x2C], 0x00000000 + or dword [eax + 0xF0], 1 # scroll_bar->flags |= 1 + mov ecx, [eax + 0xEC] + shl ecx, 4 + mov dword [eax + ecx + 0xAC], 0 # scroll_bar->selection_state[client_id].scroll_offset = 0 + mov dword [eax + ecx + 0xB0], 0 # scroll_bar->selection_state[client_id].selected_index = 0 + mov dword [eax + ecx + 0xB4], 4 # scroll_bar->selection_state[client_id].num_items_in_view = 4 + mov dword [eax + ecx + 0xB8], 0x0B # scroll_bar->selection_state[client_id].last_item_index = (slot count - 1) + pop edi + ret +enable_scroll_end: + call write_call_to_code + ret + + + +apply_fix_scroll_patch1: + # This patch fixes the preview display so it will show the correct character + push 6 # Call size + push 0x00413C38 # Call address + call get_code_size_for_fix_scroll_patch1 + .deltaof fix_scroll_patch1_start, fix_scroll_patch1_end +get_code_size_for_fix_scroll_patch1: + pop eax + push dword [eax] + call fix_scroll_patch1_end +fix_scroll_patch1_start: + mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*) + mov ebp, [edx + 0x44] # ebp = cursor->selected_index_within_view + mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0] + add ebp, [eax + 0xAC] # ebp += scroll_bar->selection_state[0].scroll_offset + ret +fix_scroll_patch1_end: + call write_call_to_code + ret + + + +apply_fix_scroll_patch2: + # This patch fixes the preview display so it will show the correct character + push 6 # Call size + push 0x00413CD8 # Call address + call get_code_size_for_fix_scroll_patch2 + .deltaof fix_scroll_patch2_start, fix_scroll_patch2_end +get_code_size_for_fix_scroll_patch2: + pop eax + push dword [eax] + call fix_scroll_patch2_end +fix_scroll_patch2_start: + mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0] + mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset + mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*) + add eax, [edx + 0x44] # eax += cursor->selected_index_within_view + ret +fix_scroll_patch2_end: + call write_call_to_code + ret + + + +apply_fix_file_index: + # This patch fixes the character file indexing so it will account for the + # scroll position + push 5 # Call size + push 0x00413CF0 # Call address + call get_code_size_for_selection_index_fix2 + .deltaof selection_index_fix2_start, selection_index_fix2_end +get_code_size_for_selection_index_fix2: + pop eax + push dword [eax] + call selection_index_fix2_end +selection_index_fix2_start: + mov eax, [0x00A3B050] + mov eax, [eax + 0xAC] # eax = TAdScrollBarXb_objs[0]->selection_state[0].scroll_offset + add ebp, eax # arg0 += eax + mov [esp + 4], ebp + mov eax, 0x006C1A80 + jmp eax # set_current_char_slot +selection_index_fix2_end: + call write_call_to_code + ret + + + +apply_preview_window_fix: + # This patch fixes the preview display so it will show the correct section + # ID, etc. + push 5 # Call size + push 0x0040216C # Call address + call get_code_size_for_preview_window_fix + .deltaof preview_window_fix_start, preview_window_fix_end +get_code_size_for_preview_window_fix: + pop eax + push dword [eax] + call preview_window_fix_end +preview_window_fix_start: + mov eax, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0] + mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset + add [esp + 4], eax + mov eax, 0x006C44D0 # get_player_preview_info + jmp eax +preview_window_fix_end: + # This patch applies in two places, so push the second set of args now, then + # apply it twice + push 5 # Call size + push 0x00401842 # Call address + push dword [esp + 0x10] # Code size + push dword [esp + 0x10] # Code address + call write_call_to_code + call write_call_to_code + ret + + + +apply_static_patches: + .include WriteCodeBlocksBB + + # These patches change various places where the character data size and slot + # count are referenced + .data 0x004751A4 + .data 0x00000001 + .binary 0C # slot count; TDataProtocol::handle_E5 + .data 0x0047525B + .data 0x00000001 + .binary 0C # slot count; import_player_preview + .data 0x004785E1 + .data 0x00000001 + .binary 0C # slot count; TDataProtocol::handle_E4 + .data 0x0048242D + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C17BF + .data 0x00000001 + .binary 0C # slot count + .data 0x006C1CCB + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C1CFE + .data 0x00000001 + .binary 0C # slot count + .data 0x006C1D1C + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C1DD7 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C222E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C226D + .data 0x00000001 + .binary 0C # slot count + .data 0x006C228E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C229E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C24DB + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C2643 + .data 0x00000004 + .binary BC2F0200 # save_count offset + .data 0x006C264D + .data 0x00000004 + .binary BC2F0200 # save_count offset + .data 0x006C26EF + .data 0x00000004 + .binary BC2F0200 # save_count offset + .data 0x006C2705 + .data 0x00000004 + .binary C02F0200 # round2_seed offset + .data 0x006C2793 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C286C + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C3113 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C353F + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C357E + .data 0x00000001 + .binary 0C # slot count + .data 0x006C35AA + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C35B7 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C35D2 + .data 0x00000004 + .binary BC2F0200 # save_count offset + .data 0x006C35DB + .data 0x00000004 + .binary BC2F0200 # save_count offset + .data 0x006C36E0 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C3B1E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4209 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C47EF + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4826 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C4962 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4999 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C4A81 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4ABA + .data 0x00000001 + .binary 0C # slot count + .data 0x006C4C9A + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4CD1 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C4DB9 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4DF2 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C4F58 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C4F94 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5181 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C51BD + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5332 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C536C + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5501 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C553D + .data 0x00000001 + .binary 0C # slot count + .data 0x006C56B2 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C56EC + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5872 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C58AC + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5A41 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C5A7D + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5B6E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C5BA8 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5D2E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C5D68 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C5EEE + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C5F28 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C60AE + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C60E8 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C6303 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C633D + .data 0x00000001 + .binary 0C # slot count + .data 0x006C64C1 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C64FD + .data 0x00000001 + .binary 0C # slot count + .data 0x006C65EE + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C6628 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C67AE + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C67E8 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C696E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C69A8 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C6B43 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C6B74 + .data 0x00000004 + .binary 5D000000 # memcard block count + .data 0x006C6BF6 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C6C30 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C6E3E + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C6E78 + .data 0x00000001 + .binary 0C # slot count + .data 0x006C7075 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C70AF + .data 0x00000001 + .binary 0C # slot count + .data 0x006C7A02 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C7D22 + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x006C7D5E + .data 0x00000001 + .binary 0C # slot count + .data 0x006C7D7C + .data 0x00000004 + .data 0x00022FC4 # total file size + .data 0x0077BE92 + .data 0x00000004 + .binary B42F0200 # bgm_test_songs_unlocked offset + + # Signature check on all files (rewritten as loop) + .data 0x006C1C2D + .deltaof sig_check_begin, sig_check_end +sig_check_begin: + mov edx, 0xC87ED5B1 # Expected signature value + add eax, 0x04E8 # &char_file_list->chars[0].part2.signature + mov ecx, 0x0C # slot count +again: + cmp dword [eax], 0 # signature == 0 (no char in slot) + je sig_ok + cmp dword [eax], edx # signature == expected value + jne sig_bad +sig_ok: + add eax, 0x2EA4 # Advance to next slot + dec ecx + jnz again + xor eax, eax # All signatures OK (eax = 0) + jmp sig_check_end +sig_bad: + xor eax, eax # Bad signature (eax = 1) + inc eax + jmp sig_check_end + .binary CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +sig_check_end: # 006C1C76 + + # Show slot number in each menu item + .data 0x00401D57 + .deltaof show_slot_number_begin, show_slot_number_end +show_slot_number_begin: + # Original call (sprintf(line_buf, "LV%d", preview_info->visual.disp.level + 1)) + lea edx, [esp + 0x02C4] + mov ebx, [ebx + 8] + inc ebx + push ebx + mov ecx, esi + push edx + mov eax, 0x00402604 + call eax + # Find the end of the string + lea eax, [esp + 0x02C4] +show_slot_number_strend_again: + cmp word [eax], 0 + je show_slot_number_strend_done + add eax, 2 + jmp show_slot_number_strend_again +show_slot_number_strend_done: + # Format the slot number and append it to the string + mov ecx, [0x00A3B050] # scroll_bar = TAdScrollBarXb_objs[0] + mov ecx, [ecx + 0xAC] # ecx = scroll_bar->selection_state[0].scroll_offset + lea ecx, [ecx + ebp + 1] + push ecx # Slot number (scroll_offset + z) + call get_show_slot_number_suffix_fmt + .binary 2000280023002500640029000000 # L" (#%d)" +get_show_slot_number_suffix_fmt: + push eax # Destination buffer + mov eax, 0x00857E29 # _swprintf + call eax + add esp, 0x0C + jmp show_slot_number_end + .zero 0x98 +show_slot_number_end: # 00401E4D + + # End static patches + .data 0x00000000 + .data 0x00000000 + + + +update_existing_char_file_list: + # Replace the existing character list with an appropriately-longer one. This + # part does not need to be done if the patch is applied statically to the + # executable; this is only necessary when used as a server patch because the + # character list is already allocated at the time the patch is applied in + # that case + push 0x00022FC4 # total file size + mov eax, 0x008581C5 # operator_new + call eax + add esp, 4 + mov edx, [0x00A95E44] # edx = old char_file_list + mov [0x00A95E44], eax + mov ecx, [edx + 0xBA94] # Copy bgm_test_songs_unlocked_high to new file + mov [eax + 0x00022FB4], ecx + mov ecx, [edx + 0xBA98] # Copy bgm_test_songs_unlocked_low to new file + mov [eax + 0x00022FB8], ecx + mov ecx, [edx + 0xBA9C] # Copy save_count to new file + mov [eax + 0x00022FBC], ecx + mov ecx, [edx + 0xBAA0] # Copy round2_seed to new file + mov [eax + 0x00022FC0], ecx + add eax, 4 + add edx, 4 + mov ecx, 0xBA90 + call memcpy # Copy the existing 4 characters over + mov eax, [0x00A95E44] + add eax, 0xBA94 + mov ecx, 4 +clear_next_char: + cmp ecx, 0x0C # slot count + jge clear_next_char_done + lea edx, [eax + 0x2EA4] # edx = ptr to next char (or footer) +clear_next_char_write_again: + mov dword [eax], 0 + add eax, 4 + cmp eax, edx + jl clear_next_char_write_again +clear_next_char_done: + + # Call eh_vector_constructor_iterator( + # &char_file_list.chars[4], + # sizeof(char_file_list.chars[0]), + # countof(char_file_list.chars) - 4, + # PSOCharacterFile::init, + # PSOCharacterFile::destroy) + push 0x006C1940 # PSOCharacterFile::destroy + push 0x006C17F0 # PSOCharacterFile::init + push 0x08 # slot count - 4 + push 0x2EA4 # sizeof(PSOCharacterFile) + mov eax, [0x00A95E44] + add eax, 0xBA94 + push eax + mov eax, 0x00858736 + call eax + + # Fix the file's checksum + mov eax, [0x00A95E44] + mov ecx, 0x006C26FC + jmp ecx # PSOBBCharacterFileList::checksum(char_file_list) + +update_existing_char_file_list_memcard: + # Allocate a new memory card file area and copy the data there too. It seems + # Sega didn't fully strip out the local saving code from PSOBB; instead, they + # just made it write to a heap-allocated buffer. Since the file is much + # bigger now, we also have to make that heap-allocated buffer larger. We add + # a few "blocks" on the end, since the original code in the game does that + # too, but it's probably not strictly necessary. + mov eax, 0x00022FC4 # total file size + add eax, 0x0000FFFF + and eax, 0xFFFFC000 + push eax + mov eax, 0x0082E940 + call eax # malloc10(total file size) + add esp, 4 + mov [0x00A95E2C], eax + mov edx, [0x00A95E44] + mov ecx, 0x00022FC4 # total file size + jmp memcpy diff --git a/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s b/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s index 44c9413d..c3236bb6 100644 --- a/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s +++ b/system/client-functions/System/WriteCallToCode-59NL.x86.inc.s @@ -3,15 +3,15 @@ # const void* patch_code, # size_t patch_code_size, # void* call_opcode_address, -# size_t call_opcode_bytes); +# ssize_t call_opcode_bytes); # This function allocates memory for patch_code, copies patch_code to that -# memory, then writes a call opcode to call_opcode_address that calls the code -# in the allocated memory region. The allocated memory is never freed. +# memory, then writes a call or jmp opcode to call_opcode_address that calls +# the code in the allocated memory region. The allocated memory is never freed. # call_opcode_bytes specifies how many bytes at the callsite should be -# overwritten; this value must be at least 5. The first 5 bytes are overwritten -# with the call opcode itself; the rest are overwritten with nop opcodes. If -# the existing data at the call address is already a call opcode, this function -# does nothing. +# overwritten. This value must be at least 5; the first 5 bytes are overwritten +# with the call/jmp opcode itself; the rest are overwritten with nop opcodes. +# If call_opcode_bytes is positive, a call opcode is written; if it's negative, +# a jmp opcode is written. # This function pops its arguments off the stack before returning. write_call_to_code: @@ -20,11 +20,6 @@ write_call_to_code: # [esp + 0x0C] = jump callsite # [esp + 0x10] = callsite size - # Check if the opcode is already a call; if so, do nothing - mov edx, [esp + 0x0C] - cmp byte [edx], 0xE8 - je done - # Allocate memory for the copied code mov ecx, [0x00AAB404] push dword [esp + 0x08] @@ -46,16 +41,22 @@ memcpy_again: jne memcpy_again pop ebx - # Write the call opcode + # Write the call or jmp opcode mov edx, [esp + 0x0C] # edx = jump callsite lea ecx, [eax - 5] sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5 - mov byte [edx], 0xE8 + cmp dword [esp + 0x10], 0 + setl al + or al, 0xE8 + mov [edx], al # Write E8 (call), or E9 (jmp) if size was negative mov [edx + 1], ecx # Write E8 (call) followed by delta # Write as many nops after the call opcode as necessary mov ecx, 5 mov eax, [esp + 0x10] + cmp eax, 0 + jge write_nop_again + neg eax write_nop_again: cmp ecx, eax jge done