support GetExtendedPlayerInfo on xbox
This commit is contained in:
+41
-27
@@ -1476,7 +1476,7 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
}
|
||||
c->load_backup_character(c->login->account->account_id, index);
|
||||
|
||||
if (c->version() == Version::GC_V3) {
|
||||
if ((c->version() == Version::GC_V3) || (c->version() == Version::XB_V3)) {
|
||||
// TODO: Support extended player info on other versions
|
||||
auto s = c->require_server_state();
|
||||
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) ||
|
||||
@@ -1485,33 +1485,47 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
return;
|
||||
}
|
||||
|
||||
auto gc_char = make_shared<PSOGCCharacterFile::Character>(c->character()->to_gc());
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), gc_char]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
auto send_set_extended_player_info = []<typename CharT>(shared_ptr<Client> c, shared_ptr<const CharT> char_file) -> void {
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), char_file]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("SetExtendedPlayerInfo", c->config.specific_version);
|
||||
send_function_call(c, fn, {}, char_file.get(), sizeof(CharT));
|
||||
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c)](uint32_t, uint32_t) -> void {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
auto s = c->require_server_state();
|
||||
send_player_leave_notification(l, c->lobby_client_id);
|
||||
s->send_lobby_join_notifications(l, c);
|
||||
}
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Failed to set extended player info: %s", e.what());
|
||||
send_text_message_printf(c, "Failed to set\nplayer info:\n%s", e.what());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (c->version() == Version::GC_V3) {
|
||||
auto gc_char = make_shared<PSOGCCharacterFile::Character>(c->character()->to_gc());
|
||||
send_set_extended_player_info.operator()<PSOGCCharacterFile::Character>(c, gc_char);
|
||||
} else if (c->version() == Version::XB_V3) {
|
||||
if (!c->login || !c->login->xb_license) {
|
||||
throw runtime_error("XB client is not logged in");
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("SetExtendedPlayerInfo", c->config.specific_version);
|
||||
send_function_call(c, fn, {}, gc_char.get(), sizeof(PSOGCCharacterFile::Character));
|
||||
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c)](uint32_t, uint32_t) -> void {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
auto s = c->require_server_state();
|
||||
send_player_leave_notification(l, c->lobby_client_id);
|
||||
s->send_lobby_join_notifications(l, c);
|
||||
}
|
||||
});
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Failed to set extended player info: %s", e.what());
|
||||
send_text_message_printf(c, "Failed to set\nplayer info:\n%s", e.what());
|
||||
}
|
||||
});
|
||||
auto xb_char = make_shared<PSOXBCharacterFileCharacter>(c->character()->to_xb(c->login->xb_license->user_id));
|
||||
send_set_extended_player_info.operator()<PSOXBCharacterFileCharacter>(c, xb_char);
|
||||
} else {
|
||||
throw logic_error("unimplemented extended player info version");
|
||||
}
|
||||
|
||||
} else {
|
||||
// On v1 and v2, the client will assign its character data from the lobby
|
||||
|
||||
@@ -7193,4 +7193,4 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 {
|
||||
|
||||
// 30 (C->S): Extended player info
|
||||
// Requested with the GetExtendedPlayerInfo patch.
|
||||
// Format is PSOGCCharacterFile::Character.
|
||||
// Format is PSOGCCharacterFile::Character or PSOXBCharacterFileCharacter.
|
||||
|
||||
@@ -3269,11 +3269,12 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> bb_char;
|
||||
switch (c->version()) {
|
||||
case Version::GC_V3: {
|
||||
auto gc_char = check_size_t<PSOGCCharacterFile::Character>(data);
|
||||
bb_char = PSOBBCharacterFile::create_from_gc(gc_char);
|
||||
case Version::GC_V3:
|
||||
bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
bb_char = PSOBBCharacterFile::create_from_xb(check_size_t<PSOXBCharacterFileCharacter>(data));
|
||||
break;
|
||||
}
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
@@ -3283,7 +3284,6 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
case Version::XB_V3:
|
||||
case Version::BB_V4:
|
||||
default:
|
||||
throw logic_error("extended player data command not implemented for version");
|
||||
|
||||
@@ -459,6 +459,47 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCha
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_xb(const PSOXBCharacterFileCharacter& xb) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = xb.inventory;
|
||||
uint8_t language = ret->inventory.language;
|
||||
ret->disp = xb.disp.to_bb(language, language);
|
||||
ret->creation_timestamp = xb.creation_timestamp.load();
|
||||
ret->play_time_seconds = xb.play_time_seconds.load();
|
||||
ret->option_flags = xb.option_flags.load();
|
||||
ret->save_count = xb.save_count.load();
|
||||
ret->quest_flags = xb.quest_flags;
|
||||
ret->death_count = xb.death_count.load();
|
||||
ret->bank = xb.bank;
|
||||
ret->guild_card = xb.guild_card;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->symbol_chats.size(), xb.symbol_chats.size()); z++) {
|
||||
auto& ret_sc = ret->symbol_chats[z];
|
||||
const auto& xb_sc = xb.symbol_chats[z];
|
||||
ret_sc.present = xb_sc.present.load();
|
||||
ret_sc.name.encode(xb_sc.name.decode(language), language);
|
||||
ret_sc.spec = xb_sc.spec;
|
||||
}
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), xb.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = xb.shortcuts[z].convert<false, TextEncoding::UTF16>(language);
|
||||
}
|
||||
ret->auto_reply.encode(xb.auto_reply.decode(language), language);
|
||||
ret->info_board.encode(xb.info_board.decode(language), language);
|
||||
ret->battle_records = xb.battle_records;
|
||||
ret->unknown_a4 = xb.unknown_a4;
|
||||
ret->challenge_records = xb.challenge_records;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->tech_menu_shortcut_entries.size(), xb.tech_menu_shortcut_entries.size()); z++) {
|
||||
ret->tech_menu_shortcut_entries[z] = xb.tech_menu_shortcut_entries[z].load();
|
||||
}
|
||||
ret->choice_search_config = xb.choice_search_config;
|
||||
ret->unknown_a6 = xb.unknown_a6;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->quest_counters.size(), xb.quest_counters.size()); z++) {
|
||||
ret->quest_counters[z] = xb.quest_counters[z].load();
|
||||
}
|
||||
ret->offline_battle_records = xb.offline_battle_records;
|
||||
ret->unknown_a7 = xb.unknown_a7;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const {
|
||||
uint8_t language = this->inventory.language;
|
||||
|
||||
@@ -501,6 +542,50 @@ PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const {
|
||||
uint8_t language = this->inventory.language;
|
||||
|
||||
PSOXBCharacterFileCharacter ret;
|
||||
ret.inventory = this->inventory;
|
||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
||||
ret.creation_timestamp = this->creation_timestamp.load();
|
||||
ret.play_time_seconds = this->play_time_seconds.load();
|
||||
ret.option_flags = this->option_flags.load();
|
||||
ret.save_count = this->save_count.load();
|
||||
ret.quest_flags = this->quest_flags;
|
||||
ret.death_count = this->death_count.load();
|
||||
ret.bank = this->bank;
|
||||
ret.guild_card = this->guild_card;
|
||||
ret.guild_card.xb_user_id_high = (xb_user_id >> 32) & 0xFFFFFFFF;
|
||||
ret.guild_card.xb_user_id_low = xb_user_id & 0xFFFFFFFF;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.symbol_chats.size(), this->symbol_chats.size()); z++) {
|
||||
auto& ret_sc = ret.symbol_chats[z];
|
||||
const auto& gc_sc = this->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<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
|
||||
ret.shortcuts[z] = this->shortcuts[z].convert<false, TextEncoding::MARKED>(language);
|
||||
}
|
||||
ret.auto_reply.encode(this->auto_reply.decode(language), language);
|
||||
ret.info_board.encode(this->info_board.decode(language), language);
|
||||
ret.battle_records = this->battle_records;
|
||||
ret.unknown_a4 = this->unknown_a4;
|
||||
ret.challenge_records = this->challenge_records;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.tech_menu_shortcut_entries.size(), this->tech_menu_shortcut_entries.size()); z++) {
|
||||
ret.tech_menu_shortcut_entries[z] = this->tech_menu_shortcut_entries[z].load();
|
||||
}
|
||||
ret.choice_search_config = this->choice_search_config;
|
||||
ret.unknown_a6 = this->unknown_a6;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.quest_counters.size(), this->quest_counters.size()); z++) {
|
||||
ret.quest_counters[z] = this->quest_counters[z].load();
|
||||
}
|
||||
ret.offline_battle_records = this->offline_battle_records;
|
||||
ret.unknown_a7 = this->unknown_a7;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SaveFileSymbolChatEntryBB PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(uint8_t language) const {
|
||||
SaveFileSymbolChatEntryBB ret;
|
||||
ret.present = 1;
|
||||
|
||||
+54
-5
@@ -124,17 +124,19 @@ template <bool IsBigEndian, TextEncoding Encoding, size_t NameLength>
|
||||
struct SaveFileSymbolChatEntryT {
|
||||
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
|
||||
|
||||
/* PC:GC:BB */
|
||||
/* 00:00:00 */ U32T present;
|
||||
/* 04:04:04 */ pstring<Encoding, NameLength> name;
|
||||
/* 34:1C:2C */ SymbolChatT<IsBigEndian> spec;
|
||||
/* 70:58:68 */
|
||||
/* PC:GC:XB:BB */
|
||||
/* 00:00:00:00 */ U32T present;
|
||||
/* 04:04:04:04 */ pstring<Encoding, NameLength> name;
|
||||
/* 34:1C:1C:2C */ SymbolChatT<IsBigEndian> spec;
|
||||
/* 70:58:58:68 */
|
||||
} __packed__;
|
||||
using SaveFileSymbolChatEntryPC = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x18>;
|
||||
using SaveFileSymbolChatEntryGC = SaveFileSymbolChatEntryT<true, TextEncoding::MARKED, 0x18>;
|
||||
using SaveFileSymbolChatEntryXB = SaveFileSymbolChatEntryT<false, TextEncoding::MARKED, 0x18>;
|
||||
using SaveFileSymbolChatEntryBB = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x14>;
|
||||
check_struct_size(SaveFileSymbolChatEntryPC, 0x70);
|
||||
check_struct_size(SaveFileSymbolChatEntryGC, 0x58);
|
||||
check_struct_size(SaveFileSymbolChatEntryXB, 0x58);
|
||||
check_struct_size(SaveFileSymbolChatEntryBB, 0x68);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
@@ -208,8 +210,10 @@ struct SaveFileChatShortcutEntryT {
|
||||
}
|
||||
} __packed__;
|
||||
using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT<true, TextEncoding::MARKED>;
|
||||
using SaveFileShortcutEntryXB = SaveFileChatShortcutEntryT<false, TextEncoding::MARKED>;
|
||||
using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT<false, TextEncoding::UTF16>;
|
||||
check_struct_size(SaveFileShortcutEntryGC, 0x54);
|
||||
check_struct_size(SaveFileShortcutEntryXB, 0x54);
|
||||
check_struct_size(SaveFileShortcutEntryBB, 0xA4);
|
||||
|
||||
struct PSOGCCharacterFile {
|
||||
@@ -389,6 +393,49 @@ struct PSOGCSnapshotFile {
|
||||
Image decode_image() const;
|
||||
} __packed_ws__(PSOGCSnapshotFile, 0x1818C);
|
||||
|
||||
struct PSOXBCharacterFileCharacter {
|
||||
// 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).
|
||||
// Most fields have the same meanings as in PSOGCCharacterFile::Character.
|
||||
/* 0000:---- */ PlayerInventory inventory;
|
||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
||||
/* 041C:0000 */ le_uint32_t flags = 0;
|
||||
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
||||
/* 0424:0008 */ le_uint32_t signature = 0xC87ED5B1;
|
||||
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
|
||||
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
|
||||
/* 0430:0014 */ le_uint32_t save_count = 0;
|
||||
/* 0434:0018 */ parray<uint8_t, 0x1C> unknown_a2;
|
||||
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
|
||||
/* 0460:0044 */ QuestFlags quest_flags;
|
||||
/* 0660:0244 */ le_uint32_t death_count = 0;
|
||||
/* 0664:0248 */ PlayerBank bank;
|
||||
/* 192C:1510 */ GuildCardXB guild_card;
|
||||
/* 1B58:173C */ parray<SaveFileSymbolChatEntryXB, 12> symbol_chats;
|
||||
/* 1F78:1B5C */ parray<SaveFileShortcutEntryXB, 16> shortcuts;
|
||||
/* 24B8:209C */ pstring<TextEncoding::MARKED, 0xAC> auto_reply;
|
||||
/* 2518:20FC */ pstring<TextEncoding::MARKED, 0xAC> info_board;
|
||||
// // TODO: The following fields are guesses and have not been verified.
|
||||
/* 2610:21F4 */ PlayerRecordsBattle battle_records;
|
||||
/* 2628:220C */ parray<uint8_t, 4> unknown_a4;
|
||||
/* 262C:2210 */ PlayerRecordsChallengeV3 challenge_records;
|
||||
/* 272C:2310 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
|
||||
/* 2754:2338 */ ChoiceSearchConfig choice_search_config;
|
||||
/* 276C:2350 */ parray<uint8_t, 0x10> unknown_a6;
|
||||
/* 277C:2360 */ parray<be_uint32_t, 0x10> quest_counters;
|
||||
/* 27BC:23A0 */ PlayerRecordsBattle offline_battle_records;
|
||||
/* 27D4:23B8 */ parray<uint8_t, 4> unknown_a7;
|
||||
struct UnknownA8Entry {
|
||||
/* 00 */ le_uint32_t unknown_a1;
|
||||
/* 04 */ parray<uint8_t, 0x1C> unknown_a2;
|
||||
/* 20 */ parray<le_float, 4> unknown_a3;
|
||||
/* 30 */
|
||||
} __packed_ws__(UnknownA8Entry, 0x30);
|
||||
/* 27D8:23BC */ parray<UnknownA8Entry, 5> unknown_a8;
|
||||
/* 28C8:24AC */
|
||||
} __packed_ws__(PSOXBCharacterFileCharacter, 0x28C8);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string decrypt_data_section(const void* data_section, size_t size, uint32_t round1_seed, size_t max_decrypt_bytes = 0) {
|
||||
if (max_decrypt_bytes == 0) {
|
||||
@@ -745,6 +792,7 @@ struct PSOBBCharacterFile {
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
std::shared_ptr<const LevelTable> level_table);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc_char);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb_char);
|
||||
|
||||
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
|
||||
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
|
||||
@@ -769,6 +817,7 @@ struct PSOBBCharacterFile {
|
||||
void clear_all_material_usage();
|
||||
|
||||
PSOGCCharacterFile::Character to_gc() const;
|
||||
PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const;
|
||||
} __packed_ws__(PSOBBCharacterFile, 0x2EA4);
|
||||
|
||||
struct PSOBBGuildCardFile {
|
||||
|
||||
+22
-30
@@ -2336,37 +2336,29 @@ void send_self_leave_notification(shared_ptr<Client> c) {
|
||||
send_command_t(c, 0x69, c->lobby_client_id, cmd);
|
||||
}
|
||||
|
||||
static bool send_get_extended_player_info(shared_ptr<Client> c) {
|
||||
// TODO: Support extended player info on other versions.
|
||||
if (c->version() != Version::GC_V3) {
|
||||
return false;
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) ||
|
||||
c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("GetExtendedPlayerInfo", c->config.specific_version);
|
||||
send_function_call(c, fn);
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Failed to send extended player info request: %s", e.what());
|
||||
send_get_player_info(c, false);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void send_get_player_info(shared_ptr<Client> c, bool request_extended) {
|
||||
if (!request_extended || !send_get_extended_player_info(c)) {
|
||||
// TODO: Support extended player info on other versions.
|
||||
if (request_extended &&
|
||||
!c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) &&
|
||||
!c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY) &&
|
||||
((c->version() == Version::GC_V3) || (c->version() == Version::XB_V3))) {
|
||||
auto s = c->require_server_state();
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("GetExtendedPlayerInfo", c->config.specific_version);
|
||||
send_function_call(c, fn);
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
} catch (const exception& e) {
|
||||
c->log.warning("Failed to send extended player info request: %s", e.what());
|
||||
send_get_player_info(c, false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user