add GC NTE save file format

This commit is contained in:
Martin Michelsen
2024-05-18 21:25:11 -07:00
parent d8230eb37a
commit c8eab046c0
8 changed files with 213 additions and 64 deletions
+9 -3
View File
@@ -748,8 +748,7 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
auto send_version_detect_or_send_call = [args, ses, send_call]() {
bool is_gc = ::is_gc(ses->version());
bool is_xb = (ses->version() == Version::XB_V3);
if ((is_gc || is_xb) &&
ses->config.specific_version == default_specific_version_for_version(ses->version(), -1)) {
if ((is_gc || is_xb) && specific_version_is_indeterminate(ses->config.specific_version)) {
auto s = ses->require_server_state();
send_function_call(
ses->client_channel,
@@ -1472,6 +1471,7 @@ static void server_command_savechar(shared_ptr<Client> c, const std::string& arg
static void server_command_loadchar(shared_ptr<Client> 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");
@@ -1498,7 +1498,10 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
send_player_leave_notification(l, c->lobby_client_id);
s->send_lobby_join_notifications(l, c);
} else if ((c->version() == Version::DC_V2) || (c->version() == Version::GC_V3) || (c->version() == Version::XB_V3)) {
} else if ((c->version() == Version::DC_V2) ||
(c->version() == Version::GC_NTE) ||
(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) ||
@@ -1539,6 +1542,9 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
if (c->version() == Version::DC_V2) {
auto dc_char = make_shared<PSODCV2CharacterFile>(c->character()->to_dc_v2());
send_set_extended_player_info.operator()<PSODCV2CharacterFile>(c, dc_char);
} else if (c->version() == Version::GC_NTE) {
auto gc_char = make_shared<PSOGCNTECharacterFileCharacter>(c->character()->to_gc_nte());
send_set_extended_player_info.operator()<PSOGCNTECharacterFileCharacter>(c, gc_char);
} else 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);
+5
View File
@@ -3911,6 +3911,11 @@ struct G_SendGuildCard_PC_6x06 {
GuildCardPC guild_card;
} __packed_ws__(G_SendGuildCard_PC_6x06, 0xF4);
struct G_SendGuildCard_GCNTE_6x06 {
G_UnusedHeader header;
GuildCardGCNTE guild_card;
} __packed_ws__(G_SendGuildCard_GCNTE_6x06, 0xA8);
struct G_SendGuildCard_GC_6x06 {
G_UnusedHeader header;
GuildCardGC guild_card;
+34 -17
View File
@@ -359,24 +359,41 @@ struct GuildCardPC {
operator GuildCardBB() const;
} __packed_ws__(GuildCardPC, 0xF0);
template <bool IsBigEndian>
// 0000 | 62 00 AC 00 06 2A 00 00 00 00 01 00 90 96 66 8C | b * f
// 0010 | 31 31 31 31 00 00 00 00 00 00 00 00 00 00 00 00 | 1111
// 0020 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0030 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0040 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0060 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0070 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
// 00A0 | 00 00 00 00 00 00 00 00 01 00 06 00 |
template <bool IsBigEndian, size_t DescriptionLength>
struct GuildCardGCT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T player_tag = 0x00010000;
/* 04 */ U32T guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20 */ pstring<TextEncoding::MARKED, 0x6C> description;
/* 8C */ uint8_t present = 0;
/* 8D */ uint8_t language = 0;
/* 8E */ uint8_t section_id = 0;
/* 8F */ uint8_t char_class = 0;
/* 90 */
/* NTE:Final */
/* 00:00 */ U32T player_tag = 0x00010000;
/* 04:04 */ U32T guild_card_number = 0;
/* 08:08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 20:20 */ pstring<TextEncoding::MARKED, DescriptionLength> description;
/* 8C:8C */ uint8_t present = 0;
/* 8D:8D */ uint8_t language = 0;
/* 8E:8E */ uint8_t section_id = 0;
/* 8F:8F */ uint8_t char_class = 0;
/* 90:90 */
operator GuildCardBB() const;
} __packed__;
using GuildCardGC = GuildCardGCT<false>;
using GuildCardGCBE = GuildCardGCT<true>;
using GuildCardGCNTE = GuildCardGCT<false, 0x80>;
using GuildCardGCNTEBE = GuildCardGCT<true, 0x80>;
using GuildCardGC = GuildCardGCT<false, 0x6C>;
using GuildCardGCBE = GuildCardGCT<true, 0x6C>;
check_struct_size(GuildCardGCNTE, 0xA4);
check_struct_size(GuildCardGCNTEBE, 0xA4);
check_struct_size(GuildCardGC, 0x90);
check_struct_size(GuildCardGCBE, 0x90);
@@ -412,9 +429,9 @@ struct GuildCardBB {
operator GuildCardDCNTE() const;
operator GuildCardDC() const;
operator GuildCardPC() const;
template <bool IsBigEndian>
operator GuildCardGCT<IsBigEndian>() const {
GuildCardGCT<IsBigEndian> ret;
template <bool IsBigEndian, size_t DescriptionLength>
operator GuildCardGCT<IsBigEndian, DescriptionLength>() const {
GuildCardGCT<IsBigEndian, DescriptionLength> ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language);
@@ -428,8 +445,8 @@ struct GuildCardBB {
operator GuildCardXB() const;
} __packed_ws__(GuildCardBB, 0x108);
template <bool IsBigEndian>
GuildCardGCT<IsBigEndian>::operator GuildCardBB() const {
template <bool IsBigEndian, size_t DescriptionLength>
GuildCardGCT<IsBigEndian, DescriptionLength>::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language);
+3 -1
View File
@@ -3291,6 +3291,9 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
case Version::DC_V2:
bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t<PSODCV2CharacterFile>(data));
break;
case Version::GC_NTE:
bb_char = PSOBBCharacterFile::create_from_gc_nte(check_size_t<PSOGCNTECharacterFileCharacter>(data));
break;
case Version::GC_V3:
bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data));
break;
@@ -3302,7 +3305,6 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
case Version::DC_V1:
case Version::PC_NTE:
case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::BB_V4:
+72
View File
@@ -517,6 +517,41 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODC
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = gc_nte.inventory;
// Note: We intentionally do not call ret->inventory.decode_from_client here.
// This is because the GC client byteswaps data2 in each item before sending
// it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does
// not do this, so the data2 fields are already in the correct order here.
uint8_t language = ret->inventory.language;
ret->disp = gc_nte.disp.to_bb(language, language);
ret->creation_timestamp = gc_nte.creation_timestamp.load();
ret->play_time_seconds = gc_nte.play_time_seconds.load();
ret->option_flags = gc_nte.option_flags.load();
ret->save_count = gc_nte.save_count.load();
ret->quest_flags = gc_nte.quest_flags;
ret->bank = gc_nte.bank;
ret->guild_card = gc_nte.guild_card;
for (size_t z = 0; z < std::min<size_t>(ret->symbol_chats.size(), gc_nte.symbol_chats.size()); z++) {
auto& ret_sc = ret->symbol_chats[z];
const auto& gc_sc = gc_nte.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(), gc_nte.shortcuts.size()); z++) {
ret->shortcuts[z] = gc_nte.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
}
ret->battle_records = gc_nte.battle_records;
ret->unknown_a4 = gc_nte.unknown_a4;
ret->challenge_records = gc_nte.challenge_records;
for (size_t z = 0; z < std::min<size_t>(ret->tech_menu_shortcut_entries.size(), gc_nte.tech_menu_shortcut_entries.size()); z++) {
ret->tech_menu_shortcut_entries[z] = gc_nte.tech_menu_shortcut_entries[z].load();
}
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = gc.inventory;
@@ -643,6 +678,43 @@ PSODCV2CharacterFile PSOBBCharacterFile::to_dc_v2() const {
return ret;
}
PSOGCNTECharacterFileCharacter PSOBBCharacterFile::to_gc_nte() const {
uint8_t language = this->inventory.language;
PSOGCNTECharacterFileCharacter ret;
ret.inventory = this->inventory;
// Note: We intentionally do not call ret.inventory.encode_for_client here.
// This is because the GC client byteswaps data2 in each item before sending
// it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does
// not do this, so the data2 fields are already in the correct order here.
ret.disp = this->disp.to_dcpcv3<true>(language, language);
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::GC_V3);
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.bank = this->bank;
ret.guild_card = this->guild_card;
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<true, TextEncoding::MARKED, 0x50>(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();
}
return ret;
}
PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const {
uint8_t language = this->inventory.language;
+29
View File
@@ -412,6 +412,33 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* ECE40 */
} __packed_ws__(PSOPCCharacterFile, 0xECE40);
struct PSOGCNTECharacterFileCharacter {
/* 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<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
/* 0460:0044 */ QuestFlags quest_flags;
/* 0660:0244 */ PlayerBank200BE bank;
/* 1928:150C */ GuildCardGCNTEBE guild_card;
/* 19CC:15B0 */ parray<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 1DEC:19D0 */ parray<SaveFileShortcutEntryGC, 20> shortcuts;
/* 247C:2060 */ PlayerRecordsBattleBE battle_records;
/* 2494:2078 */ parray<uint8_t, 4> unknown_a4;
/* 2498:207C */ PlayerRecordsChallengeDC challenge_records;
/* 2538:211C */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
// TODO: choice_search_config and offline_battle_records may be in here
// somewhere. When they are found, don't forget to update the conversion
// functions in PSOBBCharacterFile.
/* 2560:2144 */ parray<uint8_t, 0x130> unknown_n2;
/* 2690:2274 */
} __packed_ws__(PSOGCNTECharacterFileCharacter, 0x2690);
struct PSOGCCharacterFile {
/* 00000 */ be_uint32_t checksum = 0;
struct Character {
@@ -646,10 +673,12 @@ struct PSOBBCharacterFile {
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_dc_v2(const PSODCV2CharacterFile& dc);
static std::shared_ptr<PSOBBCharacterFile> create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte);
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc);
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb);
PSODCV2CharacterFile to_dc_v2() const;
PSOGCNTECharacterFileCharacter to_gc_nte() const;
PSOGCCharacterFile::Character to_gc() const;
PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const;
+23 -5
View File
@@ -347,8 +347,7 @@ void prepare_client_for_patches(shared_ptr<Client> c, function<void()> on_comple
} else if (c->version() == Version::XB_V3) {
version_detect_name = "VersionDetectXB";
}
if (version_detect_name &&
c->config.specific_version == default_specific_version_for_version(c->version(), -1)) {
if (version_detect_name && specific_version_is_indeterminate(c->config.specific_version)) {
send_function_call(c, s->function_code_index->name_to_function.at(version_detect_name));
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c), on_complete](uint32_t specific_version, uint32_t) -> void {
auto c = wc.lock();
@@ -2379,11 +2378,30 @@ void send_self_leave_notification(shared_ptr<Client> c) {
}
void send_get_player_info(shared_ptr<Client> c, bool request_extended) {
// TODO: Support extended player info on Ep3 JP and NTE
// 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::XB_V3:
break;
default:
throw logic_error("invalid version");
}
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::DC_V2) || (c->version() == Version::GC_V3) || (c->version() == Version::XB_V3))) {
!c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) {
auto s = c->require_server_state();
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() {
auto c = wc.lock();