add GC NTE save file format
This commit is contained in:
+9
-3
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user