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
+38 -38
View File
@@ -343,7 +343,7 @@ Exactly which data is saved and loaded depends on the game version:
| PSO DC v1 | Yes | Yes | No | No | No | N/A | | PSO DC v1 | Yes | Yes | No | No | No | N/A |
| PSO DC v2 | Yes | Yes | Yes | Yes | Yes | Yes | | PSO DC v2 | Yes | Yes | Yes | Yes | Yes | Yes |
| PSO PC (v2) | Yes | Yes | No | No | No | Save only | | PSO PC (v2) | Yes | Yes | No | No | No | Save only |
| PSO GC NTE | 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 (not Plus) | Yes | Yes | Yes | Yes | Yes | Yes |
| PSO GC Plus | Save only | Save only | No | No | No | Save only | | PSO GC Plus | Save only | Save only | No | No | No | Save only |
| PSO Xbox | Yes | Yes | Yes | Yes | Yes | Yes | | PSO Xbox | Yes | Yes | Yes | Yes | Yes | Yes |
@@ -396,48 +396,48 @@ You can put assembly files in the system/client-functions directory with filenam
The VERS token in client function filenames refers to the specific version of the game that the client function applies to. Some versions do not support receiving client functions at all. The specific versions are: The VERS token in client function filenames refers to the specific version of the game that the client function applies to. Some versions do not support receiving client functions at all. The specific versions are:
| Game | VERS | Supported | | Game | VERS | Architecture |
|-------------------|------|-----------| |-------------------|------|---------------|
| PSO DC NTE | 1OJ1 | No | | PSO DC NTE | 1OJ1 | Not supported |
| PSO DC 11/2000 | 1OJ2 | No | | PSO DC 11/2000 | 1OJ2 | Not supported |
| PSO DC 12/2000 | 1OJ3 | No | | PSO DC 12/2000 | 1OJ3 | Not supported |
| PSO DC 01/2001 | 1OJ4 | No | | PSO DC 01/2001 | 1OJ4 | Not supported |
| PSO DC v1 JP | 1OJF | No | | PSO DC v1 JP | 1OJF | Not supported |
| PSO DC v1 US | 1OEF | No | | PSO DC v1 US | 1OEF | Not supported |
| PSO DC v1 EU | 1OPF | No | | PSO DC v1 EU | 1OPF | Not supported |
| PSO DC 08/2001 | 2OJ5 | Yes | | PSO DC 08/2001 | 2OJ5 | SH-4 |
| PSO DC v2 JP | 2OJF | Yes | | PSO DC v2 JP | 2OJF | SH-4 |
| PSO DC v2 US | 2OEF | Yes | | PSO DC v2 US | 2OEF | SH-4 |
| PSO DC v2 EU | 2OPF | Yes | | PSO DC v2 EU | 2OPF | SH-4 |
| PSO PC (v2) | 2OJW | No | | PSO PC (v2) | 2OJW | Not supported |
| PSO GC NTE | 3OJT | Yes | | PSO GC NTE | 3OJT | PowerPC |
| PSO GC v1.2 JP | 3OJ2 | Yes | | PSO GC v1.2 JP | 3OJ2 | PowerPC |
| PSO GC v1.3 JP | 3OJ3 | Yes | | PSO GC v1.3 JP | 3OJ3 | PowerPC |
| PSO GC v1.4 JP | 3OJ4 | Yes | | PSO GC v1.4 JP | 3OJ4 | PowerPC |
| PSO GC v1.5 JP | 3OJ5 | No | | PSO GC v1.5 JP | 3OJ5 | Not supported |
| PSO GC v1.0 US | 3OE0 | Yes | | PSO GC v1.0 US | 3OE0 | PowerPC |
| PSO GC v1.1 US | 3OE1 | Yes | | PSO GC v1.1 US | 3OE1 | PowerPC |
| PSO GC v1.2 US | 3OE2 | No | | PSO GC v1.2 US | 3OE2 | Not supported |
| PSO GC v1.0 EU | 3OP0 | Yes | | PSO GC v1.0 EU | 3OP0 | PowerPC |
| PSO GC Ep3 NTE | 3SJT | Yes | | PSO GC Ep3 NTE | 3SJT | PowerPC |
| PSO GC Ep3 JP | 3SJ0 | Yes | | PSO GC Ep3 JP | 3SJ0 | PowerPC |
| PSO GC Ep3 US | 3SE0 | No | | PSO GC Ep3 US | 3SE0 | Not supported |
| PSO GC Ep3 EU | 3SP0 | No | | PSO GC Ep3 EU | 3SP0 | Not supported |
| PSO Xbox Beta | 4OJB | Yes | | PSO Xbox Beta | 4OJB | x86 |
| PSO Xbox JP Disc | 4OJD | Yes | | PSO Xbox JP Disc | 4OJD | x86 |
| PSO Xbox JP TU | 4OJU | Yes | | PSO Xbox JP TU | 4OJU | x86 |
| PSO Xbox US Disc | 4OED | Yes | | PSO Xbox US Disc | 4OED | x86 |
| PSO Xbox US TU | 4OEU | Yes | | PSO Xbox US TU | 4OEU | x86 |
| PSO Xbox EU Disc | 4OPD | Yes | | PSO Xbox EU Disc | 4OPD | x86 |
| PSO Xbox EU TU | 4OPU | Yes | | PSO Xbox EU TU | 4OPU | x86 |
| PSO BB JP 1.25.13 | 51OC | Yes | | PSO BB JP 1.25.13 | 51OC | x86 |
| PSO BB Tethealla | 51OC | Yes | | PSO BB Tethealla | 51OC | x86 |
*Note: newserv uses the shorter GameCube versioning convention, where discs labeled DOL-XXXX-0-0Y are version 1.Y. The PSO community seems to use the convention 1.0Y in some places instead, but these are the same version. For example, the version that newserv calls v1.4 is the same as v1.04, and is labeled DOL-GPOJ-0-04 on the underside of the disc.* *Note: newserv uses the shorter GameCube versioning convention, where discs labeled DOL-XXXX-0-0Y are version 1.Y. The PSO community seems to use the convention 1.0Y in some places instead, but these are the same version. For example, the version that newserv calls v1.4 is the same as v1.04, and is labeled DOL-GPOJ-0-04 on the underside of the disc.*
newserv comes with a set of patches for some of the above versions, based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050). newserv comes with a set of patches for some of the above versions, based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050).
You can also put DOL files in the system/dol directory, and they will appear in the Programs menu for GC clients. Selecting a DOL file there will load the file into the GameCube's memory and run it, just like the old homebrew loaders (PSUL and PSOload) did. For this to work, ReadMemoryWord.ppc.s, WriteMemory.ppc.s, and RunDOL.ppc.s must be present in the system/client-functions directory. This has been tested on Dolphin but not on a real GameCube, so results may vary. You can also put DOL files in the system/dol directory, and they will appear in the Programs menu for GC clients. Selecting a DOL file there will load the file into the GameCube's memory and run it, just like the old homebrew loaders (PSUL and PSOload) did. For this to work, ReadMemoryWord.ppc.s, WriteMemory.ppc.s, and RunDOL.ppc.s must be present in the system/client-functions/System directory. This has been tested on Dolphin but not on a real GameCube, so results may vary.
Like other kinds of data, functions and DOL files are cached in memory. If you've changed any of these files, you can run `reload functions` or `reload dol-files` in the interactive shell to make the changes take effect without restarting the server. Like other kinds of data, functions and DOL files are cached in memory. If you've changed any of these files, you can run `reload functions` or `reload dol-files` in the interactive shell to make the changes take effect without restarting the server.
+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]() { auto send_version_detect_or_send_call = [args, ses, send_call]() {
bool is_gc = ::is_gc(ses->version()); bool is_gc = ::is_gc(ses->version());
bool is_xb = (ses->version() == Version::XB_V3); bool is_xb = (ses->version() == Version::XB_V3);
if ((is_gc || is_xb) && if ((is_gc || is_xb) && specific_version_is_indeterminate(ses->config.specific_version)) {
ses->config.specific_version == default_specific_version_for_version(ses->version(), -1)) {
auto s = ses->require_server_state(); auto s = ses->require_server_state();
send_function_call( send_function_call(
ses->client_channel, 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) { static void server_command_loadchar(shared_ptr<Client> c, const std::string& args) {
if (!is_v1_or_v2(c->version()) && if (!is_v1_or_v2(c->version()) &&
(c->version() != Version::GC_V3) && (c->version() != Version::GC_V3) &&
(c->version() != Version::GC_NTE) &&
(c->version() != Version::XB_V3) && (c->version() != Version::XB_V3) &&
(c->version() != Version::BB_V4)) { (c->version() != Version::BB_V4)) {
send_text_message(c, "$C7This command cannot\nbe used on your\ngame version"); 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); send_player_leave_notification(l, c->lobby_client_id);
s->send_lobby_join_notifications(l, c); 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 // TODO: Support extended player info on other versions
auto s = c->require_server_state(); auto s = c->require_server_state();
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) || 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) { if (c->version() == Version::DC_V2) {
auto dc_char = make_shared<PSODCV2CharacterFile>(c->character()->to_dc_v2()); auto dc_char = make_shared<PSODCV2CharacterFile>(c->character()->to_dc_v2());
send_set_extended_player_info.operator()<PSODCV2CharacterFile>(c, dc_char); 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) { } else if (c->version() == Version::GC_V3) {
auto gc_char = make_shared<PSOGCCharacterFile::Character>(c->character()->to_gc()); auto gc_char = make_shared<PSOGCCharacterFile::Character>(c->character()->to_gc());
send_set_extended_player_info.operator()<PSOGCCharacterFile::Character>(c, gc_char); 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; GuildCardPC guild_card;
} __packed_ws__(G_SendGuildCard_PC_6x06, 0xF4); } __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 { struct G_SendGuildCard_GC_6x06 {
G_UnusedHeader header; G_UnusedHeader header;
GuildCardGC guild_card; GuildCardGC guild_card;
+34 -17
View File
@@ -359,24 +359,41 @@ struct GuildCardPC {
operator GuildCardBB() const; operator GuildCardBB() const;
} __packed_ws__(GuildCardPC, 0xF0); } __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 { struct GuildCardGCT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type; using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 00 */ U32T player_tag = 0x00010000; /* NTE:Final */
/* 04 */ U32T guild_card_number = 0; /* 00:00 */ U32T player_tag = 0x00010000;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name; /* 04:04 */ U32T guild_card_number = 0;
/* 20 */ pstring<TextEncoding::MARKED, 0x6C> description; /* 08:08 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 8C */ uint8_t present = 0; /* 20:20 */ pstring<TextEncoding::MARKED, DescriptionLength> description;
/* 8D */ uint8_t language = 0; /* 8C:8C */ uint8_t present = 0;
/* 8E */ uint8_t section_id = 0; /* 8D:8D */ uint8_t language = 0;
/* 8F */ uint8_t char_class = 0; /* 8E:8E */ uint8_t section_id = 0;
/* 90 */ /* 8F:8F */ uint8_t char_class = 0;
/* 90:90 */
operator GuildCardBB() const; operator GuildCardBB() const;
} __packed__; } __packed__;
using GuildCardGC = GuildCardGCT<false>; using GuildCardGCNTE = GuildCardGCT<false, 0x80>;
using GuildCardGCBE = GuildCardGCT<true>; 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(GuildCardGC, 0x90);
check_struct_size(GuildCardGCBE, 0x90); check_struct_size(GuildCardGCBE, 0x90);
@@ -412,9 +429,9 @@ struct GuildCardBB {
operator GuildCardDCNTE() const; operator GuildCardDCNTE() const;
operator GuildCardDC() const; operator GuildCardDC() const;
operator GuildCardPC() const; operator GuildCardPC() const;
template <bool IsBigEndian> template <bool IsBigEndian, size_t DescriptionLength>
operator GuildCardGCT<IsBigEndian>() const { operator GuildCardGCT<IsBigEndian, DescriptionLength>() const {
GuildCardGCT<IsBigEndian> ret; GuildCardGCT<IsBigEndian, DescriptionLength> ret;
ret.player_tag = 0x00010000; ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number.load(); ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language); ret.name.encode(this->name.decode(this->language), this->language);
@@ -428,8 +445,8 @@ struct GuildCardBB {
operator GuildCardXB() const; operator GuildCardXB() const;
} __packed_ws__(GuildCardBB, 0x108); } __packed_ws__(GuildCardBB, 0x108);
template <bool IsBigEndian> template <bool IsBigEndian, size_t DescriptionLength>
GuildCardGCT<IsBigEndian>::operator GuildCardBB() const { GuildCardGCT<IsBigEndian, DescriptionLength>::operator GuildCardBB() const {
GuildCardBB ret; GuildCardBB ret;
ret.guild_card_number = this->guild_card_number.load(); ret.guild_card_number = this->guild_card_number.load();
ret.name.encode(this->name.decode(this->language), this->language); 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: case Version::DC_V2:
bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t<PSODCV2CharacterFile>(data)); bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t<PSODCV2CharacterFile>(data));
break; break;
case Version::GC_NTE:
bb_char = PSOBBCharacterFile::create_from_gc_nte(check_size_t<PSOGCNTECharacterFileCharacter>(data));
break;
case Version::GC_V3: case Version::GC_V3:
bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data)); bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data));
break; 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::DC_V1:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
case Version::BB_V4: case Version::BB_V4:
+72
View File
@@ -517,6 +517,41 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODC
return ret; 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) { shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) {
auto ret = make_shared<PSOBBCharacterFile>(); auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = gc.inventory; ret->inventory = gc.inventory;
@@ -643,6 +678,43 @@ PSODCV2CharacterFile PSOBBCharacterFile::to_dc_v2() const {
return ret; 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 { PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const {
uint8_t language = this->inventory.language; uint8_t language = this->inventory.language;
+29
View File
@@ -412,6 +412,33 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* ECE40 */ /* ECE40 */
} __packed_ws__(PSOPCCharacterFile, 0xECE40); } __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 { struct PSOGCCharacterFile {
/* 00000 */ be_uint32_t checksum = 0; /* 00000 */ be_uint32_t checksum = 0;
struct Character { struct Character {
@@ -646,10 +673,12 @@ struct PSOBBCharacterFile {
const PlayerDispDataBBPreview& preview, const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table); 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_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_gc(const PSOGCCharacterFile::Character& gc);
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb); static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb);
PSODCV2CharacterFile to_dc_v2() const; PSODCV2CharacterFile to_dc_v2() const;
PSOGCNTECharacterFileCharacter to_gc_nte() const;
PSOGCCharacterFile::Character to_gc() const; PSOGCCharacterFile::Character to_gc() const;
PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) 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) { } else if (c->version() == Version::XB_V3) {
version_detect_name = "VersionDetectXB"; version_detect_name = "VersionDetectXB";
} }
if (version_detect_name && if (version_detect_name && specific_version_is_indeterminate(c->config.specific_version)) {
c->config.specific_version == default_specific_version_for_version(c->version(), -1)) {
send_function_call(c, s->function_code_index->name_to_function.at(version_detect_name)); 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 { 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(); 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) { 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 && if (request_extended &&
!c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) && !c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) &&
!c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY) && !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))) {
auto s = c->require_server_state(); auto s = c->require_server_state();
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() { prepare_client_for_patches(c, [wc = weak_ptr<Client>(c)]() {
auto c = wc.lock(); auto c = wc.lock();