diff --git a/README.md b/README.md index 4cea31c7..d0a3af53 100644 --- a/README.md +++ b/README.md @@ -357,10 +357,14 @@ Exactly which data is saved and loaded depends on the game version: | PSO PC (v2) | Yes | Yes | No | No | No | Save only | | PSO GC NTE | Yes | Yes | Yes | Yes | Yes | Yes | | PSO GC (not Plus) | Yes | Yes | Yes | Yes | Yes | Yes | -| PSO GC Plus | Save only | Save only | No | No | No | Save only | +| PSO GC Plus (1) | Save only | Save only | No | No | No | Save only | +| PSO GC Ep3 (1) | No | Save only | No | No | No | Save only | | PSO Xbox | Yes | Yes | Yes | Yes | Yes | Yes | | PSO BB | Yes | Yes | Yes | Yes | Yes | Yes | +*Notes*: +1. *If EnableSendFunctionCallQuestNumber is enabled in config.json, then $savechar and $loadchar can save and restore all character data on these versions, just like on GC non-Plus. Episode 3 characters exist in a separate namespace; that is, you can't use $savechar and $loadchar to convert an Ep3 character to non-Ep3, or vice versa.* + ## Episode 3 features newserv supports many features unique to Episode 3: diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index d2b165d8..61ff0f31 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1449,6 +1449,11 @@ static void server_command_bbchar_savechar(shared_ptr c, const std::stri auto l = c->require_lobby(); check_is_game(l, false); + if (is_bb_conversion && is_ep3(c->version())) { + send_text_message(c, "$C6Episode 3 players\ncannot be converted\nto BB format"); + return; + } + auto pending_export = make_unique(); if (is_bb_conversion) { @@ -1503,14 +1508,6 @@ static void server_command_savechar(shared_ptr c, const std::string& arg } static void server_command_loadchar(shared_ptr c, const std::string& args) { - if (!is_v1_or_v2(c->version()) && - (c->version() != Version::GC_V3) && - (c->version() != Version::GC_NTE) && - (c->version() != Version::XB_V3) && - (c->version() != Version::BB_V4)) { - send_text_message(c, "$C7This command cannot\nbe used on your\ngame version"); - return; - } if (c->login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT)) { send_text_message(c, "$C7This command cannot\nbe used on a shared\naccount"); return; @@ -1523,7 +1520,13 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg send_text_message(c, "$C6Player index must\nbe in range 1-16"); return; } - c->load_backup_character(c->login->account->account_id, index); + + shared_ptr ep3_char; + if (is_ep3(c->version())) { + ep3_char = c->load_ep3_backup_character(c->login->account->account_id, index); + } else { + c->load_backup_character(c->login->account->account_id, index); + } if (c->version() == Version::BB_V4) { // On BB, it suffices to simply send the character file again @@ -1535,6 +1538,8 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg } else if ((c->version() == Version::DC_V2) || (c->version() == Version::GC_NTE) || (c->version() == Version::GC_V3) || + (c->version() == Version::GC_EP3_NTE) || + (c->version() == Version::GC_EP3) || (c->version() == Version::XB_V3)) { // TODO: Support extended player info on other versions auto s = c->require_server_state(); @@ -1582,6 +1587,11 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg } else if (c->version() == Version::GC_V3) { auto gc_char = make_shared(c->character()->to_gc()); send_set_extended_player_info.operator()(c, gc_char); + } else if (c->version() == Version::GC_EP3_NTE) { + auto nte_char = make_shared(*ep3_char); + send_set_extended_player_info.operator()(c, nte_char); + } else if (c->version() == Version::GC_EP3) { + send_set_extended_player_info.operator()(c, ep3_char); } else if (c->version() == Version::XB_V3) { if (!c->login || !c->login->xb_license) { throw runtime_error("XB client is not logged in"); diff --git a/src/Client.cc b/src/Client.cc index ac2ac3bd..fb961ab9 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -656,8 +656,9 @@ string Client::character_filename(const std::string& bb_username, int8_t index) return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index); } -string Client::backup_character_filename(uint32_t account_id, size_t index) { - return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", account_id, index); +string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) { + return string_printf("system/players/backup_player_%" PRIu32 "_%zu.%s", + account_id, index, is_ep3 ? "pso3char" : "psochar"); } string Client::character_filename(int8_t index) const { @@ -963,6 +964,13 @@ void Client::save_character_file( player_data_log.info("Saved character file %s", filename.c_str()); } +void Client::save_ep3_character_file( + const string& filename, + const PSOGCEp3CharacterFile::Character& character) { + save_file(filename, &character, sizeof(character)); + player_data_log.info("Saved Episode 3 character file %s", filename.c_str()); +} + void Client::save_character_file() { if (!this->system_data.get()) { throw logic_error("no system file loaded"); @@ -993,7 +1001,7 @@ void Client::save_guild_card_file() const { } void Client::load_backup_character(uint32_t account_id, size_t index) { - string filename = this->backup_character_filename(account_id, index); + string filename = this->backup_character_filename(account_id, index, false); auto f = fopen_unique(filename, "rb"); auto header = freadx(f.get()); if (header.size != 0x399C) { @@ -1010,6 +1018,16 @@ void Client::load_backup_character(uint32_t account_id, size_t index) { this->v1_v2_last_reported_disp.reset(); } +shared_ptr Client::load_ep3_backup_character(uint32_t account_id, size_t index) { + string filename = this->backup_character_filename(account_id, index, true); + auto ch = make_shared(load_object_file(filename)); + this->character_data = PSOBBCharacterFile::create_from_ep3(*ch); + this->ep3_config = make_shared(ch->ep3_config); + this->update_character_data_after_load(this->character_data); + this->v1_v2_last_reported_disp.reset(); + return ch; +} + void Client::save_and_unload_character() { if (this->character_data) { this->save_character_file(); diff --git a/src/Client.hh b/src/Client.hh index 1ab91189..7c17ef41 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -364,7 +364,7 @@ public: std::string system_filename() const; static std::string character_filename(const std::string& bb_username, int8_t index); - static std::string backup_character_filename(uint32_t account_id, size_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 guild_card_filename() const; std::string shared_bank_filename() const; @@ -378,11 +378,15 @@ public: const std::string& filename, std::shared_ptr sys, std::shared_ptr character); + static void save_ep3_character_file( + const std::string& filename, + const PSOGCEp3CharacterFile::Character& character); // Note: This function is not const because it updates the player's play time. void save_character_file(); void save_guild_card_file() const; void load_backup_character(uint32_t account_id, size_t index); + std::shared_ptr load_ep3_backup_character(uint32_t account_id, size_t index); void save_and_unload_character(); PlayerBank200& current_bank(); diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index d8620a40..75d5e235 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -1158,6 +1158,62 @@ void PlayerConfig::encrypt(uint8_t basis) { this->basis = basis; } +PlayerConfigNTE::PlayerConfigNTE(const PlayerConfig& config) + : rank_text(config.rank_text), + unknown_a1(config.unknown_a1), + tech_menu_shortcut_entries(config.tech_menu_shortcut_entries), + choice_search_config(config.choice_search_config), + scenario_progress(config.scenario_progress), + unused_offline_records(config.unused_offline_records), + unknown_a4(config.unknown_a4), + is_encrypted(config.is_encrypted), + basis(config.basis), + unused(config.unused), + card_counts(config.card_counts), + card_count_checksums(config.card_count_checksums), + rare_tokens(config.rare_tokens), + decks(config.decks), + unknown_a8(config.unknown_a8), + offline_clv_exp(config.offline_clv_exp), + online_clv_exp(config.online_clv_exp), + recent_human_opponents(config.recent_human_opponents), + unknown_a10(config.unknown_a10), + init_timestamp(config.init_timestamp), + last_online_battle_start_timestamp(config.last_online_battle_start_timestamp), + unknown_t3(config.unknown_t3), + unknown_a14(config.unknown_a14) { + // TODO: Do we need to recompute card_count_checksums? (Here or in operator + // PlayerConfig?) +} + +PlayerConfigNTE::operator PlayerConfig() const { + return { + .rank_text = this->rank_text, + .unknown_a1 = this->unknown_a1, + .tech_menu_shortcut_entries = this->tech_menu_shortcut_entries, + .choice_search_config = this->choice_search_config, + .scenario_progress = this->scenario_progress, + .unused_offline_records = this->unused_offline_records, + .unknown_a4 = this->unknown_a4, + .is_encrypted = this->is_encrypted, + .basis = this->basis, + .unused = this->unused, + .card_counts = this->card_counts, + .card_count_checksums = this->card_count_checksums, + .rare_tokens = this->rare_tokens, + .decks = this->decks, + .unknown_a8 = this->unknown_a8, + .offline_clv_exp = this->offline_clv_exp, + .online_clv_exp = this->online_clv_exp, + .recent_human_opponents = this->recent_human_opponents, + .unknown_a10 = this->unknown_a10, + .init_timestamp = this->init_timestamp, + .last_online_battle_start_timestamp = this->last_online_battle_start_timestamp, + .unknown_t3 = this->unknown_t3, + .unknown_a14 = this->unknown_a14, + }; +} + Rules::Rules(const JSON& json) { this->clear(); this->overall_time_limit = json.get_int("overall_time_limit", this->overall_time_limit); diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 79cfb957..50e62c4b 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -907,12 +907,12 @@ struct PlayerConfig { // card counts array is encrypted in memory most of the time, and they went // out of their way to ensure the game uses an area of memory that almost no // other game uses, which is also used by the Action Replay.) - /* 05A4:0450 */ parray rare_tokens; + /* 05A4:0450 */ parray rare_tokens; /* 13B4:1260 */ parray unknown_a7; /* 1434:12E0 */ parray decks; /* 2118:1FC4 */ parray unknown_a8; - /* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = this / 100 - /* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = this / 100 + /* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = (this / 100) + 1 + /* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = (this / 100) + 1 struct PlayerReference { /* 00 */ be_uint32_t guild_card_number; /* 04 */ pstring name; @@ -941,6 +941,39 @@ struct PlayerConfig { void encrypt(uint8_t basis); } __packed_ws__(PlayerConfig, 0x2350); +struct PlayerConfigNTE { + /* 0000 */ pstring rank_text; + /* 000C */ parray unknown_a1; + /* 0028 */ parray tech_menu_shortcut_entries; + /* 0050 */ parray choice_search_config; + /* 0078 */ parray scenario_progress; // Final has 0x30 entries here + /* 00B8 */ PlayerRecordsBattleBE unused_offline_records; + /* 00D0 */ parray unknown_a4; + /* 00D4 */ uint8_t is_encrypted; + /* 00D5 */ uint8_t basis; + /* 00D6 */ parray unused; + /* 00D8 */ parray card_counts; + /* 04C0 */ parray card_count_checksums; + /* 0524 */ parray rare_tokens; + /* 0E84 */ parray decks; + /* 1B68 */ parray unknown_a8; + /* 1B70 */ be_uint32_t offline_clv_exp; + /* 1B74 */ be_uint32_t online_clv_exp; + /* 1B78 */ parray recent_human_opponents; + /* 1C90 */ parray unknown_a10; + /* 1CB8 */ be_uint32_t init_timestamp; + /* 1CBC */ be_uint32_t last_online_battle_start_timestamp; + /* 1CC0 */ be_uint32_t unknown_t3; + /* 1CC4 */ parray unknown_a14; + /* 1D58 */ + + PlayerConfigNTE(const PlayerConfig& config); + operator PlayerConfig() const; + + void decrypt(); + void encrypt(uint8_t basis); +} __packed_ws__(PlayerConfigNTE, 0x1D58); + enum class HPType : uint8_t { DEFEAT_PLAYER = 0, DEFEAT_TEAM = 1, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index eb647256..7999e8ac 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3250,7 +3250,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri pending_export->dest_bb_license->username, pending_export->character_index); } else { filename = Client::backup_character_filename( - pending_export->dest_account->account_id, pending_export->character_index); + pending_export->dest_account->account_id, pending_export->character_index, is_ep3(c->version())); } if (s->player_files_manager->get_character(filename)) { @@ -3323,7 +3323,8 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { } else { filename = Client::backup_character_filename( pending_export->dest_account->account_id, - pending_export->character_index); + pending_export->character_index, + is_ep3(c->version())); } auto s = c->require_server_state(); @@ -3332,6 +3333,21 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { return; } + if (is_ep3(c->version())) { + try { + if (c->version() == Version::GC_EP3_NTE) { + PSOGCEp3CharacterFile::Character ch(check_size_t(data)); + Client::save_ep3_character_file(filename, ch); + } else { + Client::save_ep3_character_file(filename, check_size_t(data)); + } + send_text_message(c, "$C7Character data saved\n(full save file)"); + } catch (const exception& e) { + send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what()); + } + return; + } + shared_ptr bb_char; switch (c->version()) { case Version::DC_V2: @@ -3346,13 +3362,14 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { case Version::XB_V3: bb_char = PSOBBCharacterFile::create_from_xb(check_size_t(data)); break; + case Version::GC_EP3_NTE: + case Version::GC_EP3: + throw logic_error("Episode 3 case not handled correctly"); case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: case Version::PC_NTE: case Version::PC_V2: - case Version::GC_EP3_NTE: - case Version::GC_EP3: case Version::BB_V4: default: throw logic_error("extended player data command not implemented for version"); diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 1ebd5c7c..b7ffdc7a 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -257,6 +257,65 @@ Image PSOGCSnapshotFile::decode_image() const { return ret; } +PSOGCEp3CharacterFile::Character::Character(const PSOGCEp3NTECharacter& nte) + : inventory(nte.inventory), + disp(nte.disp), + flags(nte.flags), + creation_timestamp(nte.creation_timestamp), + signature(nte.signature), + play_time_seconds(nte.play_time_seconds), + option_flags(nte.option_flags), + save_count(nte.save_count), + ppp_username(nte.ppp_username), + ppp_password(nte.ppp_password), + seq_vars(nte.seq_vars), + death_count(nte.death_count), + bank(nte.bank), + guild_card(nte.guild_card), + symbol_chats(nte.symbol_chats), + chat_shortcuts(nte.chat_shortcuts), + auto_reply(nte.auto_reply), + info_board(nte.info_board), + battle_records(nte.battle_records), + unknown_a10(nte.unknown_a10), + challenge_record_stats(nte.challenge_record_stats), + ep3_config(nte.ep3_config), + unknown_a11(nte.unknown_a11), + unknown_a12(nte.unknown_a12), + unknown_a13(nte.unknown_a13) { + this->ep3_config.backup_visual = this->disp.visual; +} + +PSOGCEp3CharacterFile::Character::operator PSOGCEp3NTECharacter() const { + return { + .inventory = this->inventory, + .disp = this->disp, + .flags = this->flags, + .creation_timestamp = this->creation_timestamp, + .signature = this->signature, + .play_time_seconds = this->play_time_seconds, + .option_flags = this->option_flags, + .save_count = this->save_count, + .ppp_username = this->ppp_username, + .ppp_password = this->ppp_password, + .seq_vars = this->seq_vars, + .death_count = this->death_count, + .bank = this->bank, + .guild_card = this->guild_card, + .symbol_chats = this->symbol_chats, + .chat_shortcuts = this->chat_shortcuts, + .auto_reply = this->auto_reply, + .info_board = this->info_board, + .battle_records = this->battle_records, + .unknown_a10 = this->unknown_a10, + .challenge_record_stats = this->challenge_record_stats, + .ep3_config = this->ep3_config, + .unknown_a11 = this->unknown_a11, + .unknown_a12 = this->unknown_a12, + .unknown_a13 = this->unknown_a13, + }; +} + void PSOBBGuildCardFile::Entry::clear() { this->data.clear(); this->unknown_a1.clear(0); @@ -598,6 +657,48 @@ shared_ptr PSOBBCharacterFile::create_from_gc(const PSOGCCha return ret; } +shared_ptr PSOBBCharacterFile::create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3) { + auto ret = make_shared(); + ret->inventory = ep3.inventory; + uint8_t language = ret->inventory.language; + ret->disp = ep3.disp.to_bb(language, language); + ret->creation_timestamp = ep3.creation_timestamp.load(); + ret->play_time_seconds = ep3.play_time_seconds.load(); + ret->option_flags = ep3.option_flags.load(); + ret->save_count = ep3.save_count.load(); + ret->death_count = ep3.death_count.load(); + ret->bank = ep3.bank; + ret->guild_card = ep3.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), ep3.symbol_chats.size()); z++) { + auto& ret_sc = ret->symbol_chats[z]; + const auto& gc_sc = ep3.symbol_chats[z]; + ret_sc.present = gc_sc.present.load(); + ret_sc.name.encode(gc_sc.name.decode(language), language); + ret_sc.spec = gc_sc.spec; + } + for (size_t z = 0; z < std::min(ret->shortcuts.size(), ep3.chat_shortcuts.size()); z++) { + ret->shortcuts[z] = ep3.chat_shortcuts[z].convert(language); + } + ret->auto_reply.encode(ep3.auto_reply.decode(language), language); + ret->info_board.encode(ep3.info_board.decode(language), language); + ret->battle_records = ep3.battle_records; + ret->unknown_a4 = ep3.ep3_config.unknown_a4; + ret->challenge_records.rank_title.encode(ep3.ep3_config.rank_text.decode(language), language); + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), ep3.ep3_config.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = ep3.ep3_config.tech_menu_shortcut_entries[z].load(); + } + ret->choice_search_config.disabled = !!(ret->option_flags & 0x00040000); + for (size_t z = 0; z < 5; z++) { + ret->choice_search_config.entries[z].parent_choice_id = ep3.ep3_config.choice_search_config[z * 2].load(); + ret->choice_search_config.entries[z].choice_id = ep3.ep3_config.choice_search_config[z * 2 + 1].load(); + } + for (size_t z = 0; z < std::min(ret->quest_counters.size(), ep3.ep3_config.scenario_progress.size()); z++) { + ret->quest_counters[z] = ep3.ep3_config.scenario_progress[z].load(); + } + ret->offline_battle_records = ep3.ep3_config.unused_offline_records; + return ret; +} + shared_ptr PSOBBCharacterFile::create_from_xb(const PSOXBCharacterFileCharacter& xb) { auto ret = make_shared(); ret->inventory = xb.inventory; diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index a45d599f..7143a774 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -531,6 +531,42 @@ struct PSOGCCharacterFile { /* 1156C */ } __packed_ws__(PSOGCCharacterFile, 0x1156C); +struct PSOGCEp3NTECharacter { + // This structure is internally split into two by the game. The offsets here + // are relative to the start of this structure (first column), and relative + // to the start of the second internal structure (second column). + /* 0000:---- */ PlayerInventoryBE inventory; + /* 034C:---- */ PlayerDispDataDCPCV3BE disp; + /* 041C:0000 */ be_uint32_t flags = 0; + /* 0420:0004 */ be_uint32_t creation_timestamp = 0; + /* 0424:0008 */ be_uint32_t signature = 0xA205B064; + /* 0428:000C */ be_uint32_t play_time_seconds = 0; + /* 042C:0010 */ be_uint32_t option_flags = 0x00040058; + /* 0430:0014 */ be_uint32_t save_count = 1; + /* 0434:0018 */ pstring ppp_username; + /* 0450:0034 */ pstring ppp_password; + // seq_vars is an array of 8192 bits, which contain all the Episode 3 quest + // progress flags. This includes things like which maps are unlocked, which + // NPC decks are unlocked, and whether the player has a VIP card or not. + /* 0460:0044 */ parray seq_vars; + /* 0860:0444 */ be_uint32_t death_count = 0; + /* 0864:0448 */ PlayerBank200BE bank; + /* 1B2C:1710 */ GuildCardGCBE guild_card; + /* 1BBC:17A0 */ parray symbol_chats; + /* 1FDC:1BC0 */ parray chat_shortcuts; + /* 266C:2250 */ pstring auto_reply; + /* 2718:22FC */ pstring info_board; + // // In this struct, place_counts[0] is win_count and [1] is loss_count + /* 27C4:23A8 */ PlayerRecordsBattleBE battle_records; + /* 27DC:23C0 */ parray unknown_a10; + /* 27E0:23C4 */ PlayerRecordsChallengeV3BE::Stats challenge_record_stats; + /* 28B8:249C */ Episode3::PlayerConfigNTE ep3_config; + /* 4610:41F4 */ be_uint32_t unknown_a11 = 0; + /* 4614:41F8 */ be_uint32_t unknown_a12 = 0; + /* 4618:41FC */ be_uint32_t unknown_a13 = 0; + /* 461C:4200 */ +} __packed_ws__(PSOGCEp3NTECharacter, 0x461C); + struct PSOGCEp3CharacterFile { /* 00000 */ be_uint32_t checksum = 0; // crc32 of this field (as 0) through end of struct struct Character { @@ -541,10 +577,8 @@ struct PSOGCEp3CharacterFile { /* 034C:---- */ PlayerDispDataDCPCV3BE disp; /* 041C:0000 */ be_uint32_t flags = 0; /* 0420:0004 */ be_uint32_t creation_timestamp = 0; - /* 0424:0008 */ be_uint32_t signature = 0xA204B064; + /* 0424:0008 */ be_uint32_t signature = 0xA205B064; /* 0428:000C */ be_uint32_t play_time_seconds = 0; - // See the comment in PSOGCCharacterFile::Character about what the bits in - // this field mean. /* 042C:0010 */ be_uint32_t option_flags = 0x00040058; /* 0430:0014 */ be_uint32_t save_count = 1; /* 0434:0018 */ pstring ppp_username; @@ -572,6 +606,10 @@ struct PSOGCEp3CharacterFile { /* 39AC:3590 */ be_uint32_t unknown_a12 = 0; /* 39B0:3594 */ be_uint32_t unknown_a13 = 0; /* 39B4:3598 */ + + Character() = default; + explicit Character(const PSOGCEp3NTECharacter& nte); + operator PSOGCEp3NTECharacter() const; } __packed_ws__(Character, 0x39B4); /* 00004 */ parray characters; /* 193F0 */ pstring serial_number; // As %08X (not decimal) @@ -691,6 +729,7 @@ struct PSOBBCharacterFile { static std::shared_ptr create_from_dc_v2(const PSODCV2CharacterFile& dc); static std::shared_ptr create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte); static std::shared_ptr create_from_gc(const PSOGCCharacterFile::Character& gc); + static std::shared_ptr create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3); static std::shared_ptr create_from_xb(const PSOXBCharacterFileCharacter& xb); PSODCV2CharacterFile to_dc_v2() const; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 4971f7b1..2b8ba184 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2385,21 +2385,20 @@ void send_self_leave_notification(shared_ptr c) { } void send_get_player_info(shared_ptr c, bool request_extended) { - // TODO: Support extended player info on Ep3 (JP and NTE) switch (c->version()) { case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: case Version::PC_NTE: case Version::PC_V2: - case Version::GC_EP3_NTE: - case Version::GC_EP3: case Version::BB_V4: request_extended = false; break; case Version::DC_V2: case Version::GC_NTE: case Version::GC_V3: + case Version::GC_EP3_NTE: + case Version::GC_EP3: case Version::XB_V3: break; default: diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE0.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE0.patch.s index ade21c41..3a5b4154 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE0.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE0.patch.s @@ -14,3 +14,4 @@ data: .data 0x805C4D80 # root_protocol (anchor: send_05) .data 0x803DB138 # free9 .data 0x800787B0 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE1.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE1.patch.s index 74c07a16..a7265d43 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE1.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE1.patch.s @@ -14,3 +14,4 @@ data: .data 0x805CBD60 # root_protocol (anchor: send_05) .data 0x803DB190 # free9 .data 0x800787B0 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE2.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE2.patch.s index 08e34666..d93387f1 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE2.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OE2.patch.s @@ -14,3 +14,4 @@ data: .data 0x805D5580 # root_protocol (anchor: send_05) .data 0x803DE890 # free9 .data 0x8007889C # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ2.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ2.patch.s index 34d85c0a..c3aea3bc 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ2.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ2.patch.s @@ -14,3 +14,4 @@ data: .data 0x805C4488 # root_protocol (anchor: send_05) .data 0x803D9E90 # free9 .data 0x8007848C # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ3.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ3.patch.s index 5069c130..fe98f83a 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ3.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ3.patch.s @@ -14,3 +14,4 @@ data: .data 0x805CEA50 # root_protocol (anchor: send_05) .data 0x803DC870 # free9 .data 0x800785F0 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ4.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ4.patch.s index ed3edf84..8f484a73 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ4.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ4.patch.s @@ -14,3 +14,4 @@ data: .data 0x805D5ED0 # root_protocol (anchor: send_05) .data 0x803DE710 # free9 .data 0x80078748 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ5.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ5.patch.s index 91a2b1f4..5864c522 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ5.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OJ5.patch.s @@ -14,3 +14,4 @@ data: .data 0x805D5C70 # root_protocol (anchor: send_05) .data 0x803DE4C0 # free9 .data 0x800786A0 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OP0.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OP0.patch.s index 43ac0688..571f3a29 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OP0.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3OP0.patch.s @@ -14,3 +14,4 @@ data: .data 0x805D17C0 # root_protocol (anchor: send_05) .data 0x803DD380 # free9 .data 0x80078820 # TProtocol_wait_send_drain + .data 0x00002370 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SE0.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SE0.patch.s new file mode 100644 index 00000000..1a8a1f28 --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SE0.patch.s @@ -0,0 +1,17 @@ +.meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include GetExtendedPlayerInfoGC +data: + .data 0x8038C0EC # malloc9 + .data 0x8057A6F0 # char_file_part1 + .data 0x8057A6F4 # char_file_part2 + .data 0x8057A150 # root_protocol (anchor: send_05) + .data 0x8038C144 # free9 + .data 0x80026B88 # TProtocol_wait_send_drain + .data 0x0000358C # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJ0.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJ0.patch.s new file mode 100644 index 00000000..b1a5591b --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJ0.patch.s @@ -0,0 +1,17 @@ +.meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include GetExtendedPlayerInfoGC +data: + .data 0x8038B09C # malloc9 + .data 0x80579880 # char_file_part1 + .data 0x80579884 # char_file_part2 + .data 0x805792E0 # root_protocol (anchor: send_05) + .data 0x8038B0F4 # free9 + .data 0x80026A04 # TProtocol_wait_send_drain + .data 0x0000358C # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJT.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJT.patch.s new file mode 100644 index 00000000..bada637e --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SJT.patch.s @@ -0,0 +1,17 @@ +.meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include GetExtendedPlayerInfoGC +data: + .data 0x80358094 # malloc9 + .data 0x8058B980 # char_file_part1 + .data 0x8058B984 # char_file_part2 + .data 0x8058B3A0 # root_protocol (anchor: send_05) + .data 0x803580EC # free9 + .data 0x80026FE4 # TProtocol_wait_send_drain + .data 0x000041F4 # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SP0.patch.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SP0.patch.s new file mode 100644 index 00000000..a34e3c10 --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfo.3SP0.patch.s @@ -0,0 +1,17 @@ +.meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include GetExtendedPlayerInfoGC +data: + .data 0x8038CF94 # malloc9 + .data 0x8057CB10 # char_file_part1 + .data 0x8057CB14 # char_file_part2 + .data 0x8057C570 # root_protocol (anchor: send_05) + .data 0x8038CFEC # free9 + .data 0x80026BB8 # TProtocol_wait_send_drain + .data 0x0000358C # sizeof(*char_file_part2) diff --git a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfoGC.ppc.inc.s b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfoGC.ppc.inc.s index 466192e0..96599caa 100644 --- a/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfoGC.ppc.inc.s +++ b/system/client-functions/ExtendedPlayerInfo/GetExtendedPlayerInfoGC.ppc.inc.s @@ -1,25 +1,31 @@ - stwu [r1 - 0x20], r1 + stwu [r1 - 0x40], r1 mflr r0 - stw [r1 + 0x24], r0 + stw [r1 + 0x44], r0 stw [r1 + 0x08], r31 stw [r1 + 0x0C], r30 stw [r1 + 0x10], r29 stw [r1 + 0x14], r28 + stw [r1 + 0x18], r27 b get_data_ptr get_data_ptr_ret: mflr r30 - li r3, 0x279C + lwz r27, [r30 + 0x18] # sizeof(part2) + + addi r3, r27, 0x42C # sizeof(header) + sizeof(part1) + sizeof(part2) + sizeof(unused fields after part2) lwz r0, [r30] mtctr r0 bctrl # malloc9 mr. r31, r3 beq malloc9_failed - lis r0, 0x3000 - ori r0, r0, 0x9C27 - stw [r31], r0 # header = 30 00 9C 27 + li r0, 0x3000 + sth [r31], r0 + addi r3, r27, 0x42C + stb [r31 + 2], r3 + rlwinm r3, r3, 24, 24, 31 + stb [r31 + 3], r3 # header = 30 00 SS SS lwz r4, [r30 + 0x04] lwz r4, [r4] # r4 = char_file_part1 @@ -30,16 +36,18 @@ get_data_ptr_ret: lwz r4, [r30 + 0x08] lwz r4, [r4] # r4 = char_file_part2 addi r3, r31, 0x0420 - li r5, 0x2370 # sizeof(part2) + mr r5, r27 bl memcpy li r0, 0 - stw [r31 + 0x2790], r0 - stw [r31 + 0x2794], r0 - stw [r31 + 0x2798], r0 + add r3, r27, r31 + addi r3, r3, 0x420 # r3 = pointer to unused fields after part2 + stw [r3 + 0], r0 + stw [r3 + 4], r0 + stw [r3 + 8], r0 mr r28, r31 - li r29, 0x279C + addi r29, r27, 0x42C send_again: lwz r3, [r30 + 0x0C] lwz r3, [r3] @@ -74,12 +82,13 @@ drain_failed: li r3, 0 malloc9_failed: + lwz r27, [r1 + 0x18] lwz r28, [r1 + 0x14] lwz r29, [r1 + 0x10] lwz r30, [r1 + 0x0C] lwz r31, [r1 + 0x08] - lwz r0, [r1 + 0x24] - addi r1, r1, 0x20 + lwz r0, [r1 + 0x44] + addi r1, r1, 0x40 mtlr r0 blr diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE0.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE0.patch.s index 85faa736..ddf2a440 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE0.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE0.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805C5758 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE1.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE1.patch.s index 63027ef8..8c93331b 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE1.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE1.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805CC738 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE2.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE2.patch.s index f5089367..2ba2dc92 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE2.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OE2.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805D5F58 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ2.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ2.patch.s index 3d94fad1..2973285d 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ2.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ2.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805C4E60 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ3.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ3.patch.s index 50e846bc..49a7c3c2 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ3.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ3.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805CF428 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ4.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ4.patch.s index db1be411..947e0cbc 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ4.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ4.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805D68A8 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ5.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ5.patch.s index 613b3d1a..fd893a10 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ5.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJ5.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805D6648 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJT.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJT.patch.s index 3189c054..619eb7f5 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJT.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OJT.patch.s @@ -6,44 +6,8 @@ entry_ptr: reloc0: .offsetof start start: - mflr r12 - bl get_data_ptr -get_data_ptr_ret: - mflr r11 - - lwz r10, [r11] - - # Copy part1 data into place - lwz r3, [r10 + 0x08] - addi r4, r11, 0x0004 - li r5, 0x41C - bl memcpy - - # Copy part2 data into place, but retain the values of a few metadata fields - # so the game won't think the file is corrupt - lwz r3, [r10 + 0x0C] - lwz r7, [r3 + 0x04] # creation_timestamp - lwz r8, [r3 + 0x08] # signature - lwz r9, [r3 + 0x14] # save_count - addi r4, r11, 0x0420 - li r5, 0x2268 - bl memcpy - lwz r3, [r10 + 0x0C] - stw [r3 + 0x04], r7 # creation_timestamp - stw [r3 + 0x08], r8 # signature - stw [r3 + 0x14], r9 # save_count - - li r3, 1 - mtlr r12 - blr - -memcpy: - .include CopyDataWords - blr - -get_data_ptr: - bl get_data_ptr_ret - + .include SetExtendedPlayerInfoGC data: .data 0x805CC738 # character_file - # Server adds a PSOGCCharacterFile::Character here + .data 0x00002268 # sizeof(part2) + # Server adds a PSOGCNTECharacterFileCharacter here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OP0.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OP0.patch.s index 76213c35..60fc753c 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OP0.patch.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3OP0.patch.s @@ -9,4 +9,5 @@ start: .include SetExtendedPlayerInfoGC data: .data 0x805D2198 # character_file + .data 0x00002370 # sizeof(part2) # Server adds a PSOGCCharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SE0.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SE0.patch.s new file mode 100644 index 00000000..6cd7c790 --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SE0.patch.s @@ -0,0 +1,13 @@ +.meta hide_from_patches_menu +.meta name="SetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include SetExtendedPlayerInfoGC +data: + .data 0x8057A6E8 # character_file + .data 0x0000358C # sizeof(*char_file_part2) + # Server adds a PSOGCEp3CharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJ0.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJ0.patch.s new file mode 100644 index 00000000..2f5cbe92 --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJ0.patch.s @@ -0,0 +1,13 @@ +.meta hide_from_patches_menu +.meta name="SetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include SetExtendedPlayerInfoGC +data: + .data 0x80579878 # character_file + .data 0x0000358C # sizeof(*char_file_part2) + # Server adds a PSOGCEp3CharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJT.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJT.patch.s new file mode 100644 index 00000000..ae55bdf8 --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SJT.patch.s @@ -0,0 +1,13 @@ +.meta hide_from_patches_menu +.meta name="SetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include SetExtendedPlayerInfoGC +data: + .data 0x8058B978 # character_file + .data 0x0000358C # sizeof(*char_file_part2) + # Server adds a PSOGCEp3CharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SP0.patch.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SP0.patch.s new file mode 100644 index 00000000..0a43606b --- /dev/null +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfo.3SP0.patch.s @@ -0,0 +1,13 @@ +.meta hide_from_patches_menu +.meta name="SetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include SetExtendedPlayerInfoGC +data: + .data 0x8057CB08 # character_file + .data 0x0000358C # sizeof(*char_file_part2) + # Server adds a PSOGCEp3CharacterFile::Character here diff --git a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfoGC.ppc.inc.s b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfoGC.ppc.inc.s index 84b2207d..57298193 100644 --- a/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfoGC.ppc.inc.s +++ b/system/client-functions/ExtendedPlayerInfo/SetExtendedPlayerInfoGC.ppc.inc.s @@ -7,7 +7,7 @@ get_data_ptr_ret: # Copy part1 data into place lwz r3, [r10 + 0x08] - addi r4, r11, 0x0004 + addi r4, r11, 0x0008 li r5, 0x41C bl memcpy @@ -17,8 +17,8 @@ get_data_ptr_ret: lwz r7, [r3 + 0x04] # creation_timestamp lwz r8, [r3 + 0x08] # signature lwz r9, [r3 + 0x14] # save_count - addi r4, r11, 0x0420 - li r5, 0x2370 + addi r4, r11, 0x0424 + lwz r5, [r11 + 4] bl memcpy lwz r3, [r10 + 0x0C] stw [r3 + 0x04], r7 # creation_timestamp diff --git a/system/config.example.json b/system/config.example.json index a647bd25..78256bd3 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -1054,6 +1054,8 @@ // has to wait for the initial server connection, then wait for the quest to // load, then wait for the client to leave the "game", before even getting to // the welcome message. + // To enable this feature, set this value to 88500. This is the number of the + // quest in system/quests/hidden that implements the patch. // This quest is not intended to be localized since it should not contain any // user-visible text, so the server uses the language field to determine // which quest to send based on the client's version: