more save file format refinement

This commit is contained in:
Martin Michelsen
2023-04-03 00:27:21 -07:00
parent c4e3eb238f
commit 35845ea49b
2 changed files with 84 additions and 46 deletions
+24 -21
View File
@@ -582,23 +582,26 @@ struct DeckDefinition {
} __attribute__((packed)); // 0x84 bytes in total
struct PlayerConfig {
// The first offsets in the comments in this struct are relative to the start
// of the 61/98 command; the second are relative to the start of the
// Ep3PlayerDataSegment structure in the reverse-engineering project; the
// third are relative to the start of this struct.
// TODO: Fill in the unknown fields here by looking around callsites of
// get_player_data_segment
/* 0728:----:0000 */ parray<uint8_t, 0x154> unknown_a1;
/* 087C:0000:0154 */ uint8_t is_encrypted;
/* 087D:0001:0155 */ uint8_t basis;
/* 087E:0002:0156 */ parray<uint8_t, 2> unused;
/* 0000 */ ptext<char, 12> rank_text; // From B7 command
/* 000C */ parray<uint8_t, 0x1C> unknown_a1;
/* 0028 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
/* 0050 */ parray<be_uint32_t, 10> choice_search_config;
/* 0078 */ parray<be_uint32_t, 0x30> scenario_progress;
/* 0138 */ be_uint16_t unknown_a2;
/* 013A */ be_uint16_t unknown_a3;
/* 013C */ parray<uint8_t, 0x18> unknown_a4;
/* 0154 */ uint8_t is_encrypted;
/* 0155 */ uint8_t basis;
/* 0156 */ parray<uint8_t, 2> unused;
// The following fields (here through the beginning of decks) are encrypted
// using the trivial algorithm, with the basis specified above, if
// is_encrypted is equal to 1.
// It appears the card counts field in this structure is actually 1000 (0x3E8)
// bytes long, even though in every other place the counts array appears it's
// 0x2F0 bytes long. They presumably did this because of the checksum logic.
/* 0880:0004:0158 */ parray<uint8_t, 1000> card_counts;
/* 0158 */ parray<uint8_t, 1000> card_counts;
// These appear to be an attempt at checksumming the card counts array, but
// the algorithm don't cover the entire array and instead reads from later
// parts of this structure. This appears to be due to a copy/paste error in
@@ -607,26 +610,26 @@ struct PlayerConfig {
// [69] and puts the result in card_count_checksums[1], etc. Presumably they
// intended to use 20 as the stride instead of 50, which would have exactly
// covered the entire card_counts array.
/* 0C68:03EC:0540 */ parray<be_uint16_t, 50> card_count_checksums;
/* 0540 */ parray<be_uint16_t, 50> card_count_checksums;
// Yes, these are actually 64-bit integers. They include card IDs and some
// other data, encoded in a way I don't fully understand yet.
/* 0CCC:0450:05A4 */ parray<be_uint64_t, 0x1C2> unknown_a4;
/* 1ADC:1260:13B4 */ parray<uint8_t, 0x80> unknown_a7;
/* 1B5C:12E0:1434 */ parray<DeckDefinition, 25> decks;
/* 2840:1FC4:2118 */ parray<uint8_t, 0x08> unknown_a8;
/* 2848:1FCC:2120 */ be_uint32_t offline_clv_exp; // CLvOff = this / 100
/* 284C:1FD0:2124 */ be_uint32_t online_clv_exp; // CLvOn = this / 100
/* 05A4 */ parray<be_uint64_t, 0x1C2> unknown_a5;
/* 13B4 */ parray<uint8_t, 0x80> unknown_a7;
/* 1434 */ parray<DeckDefinition, 25> decks;
/* 2118 */ parray<uint8_t, 0x08> unknown_a8;
/* 2120 */ be_uint32_t offline_clv_exp; // CLvOff = this / 100
/* 2124 */ be_uint32_t online_clv_exp; // CLvOn = this / 100
struct PlayerReference {
/* 00 */ be_uint32_t guild_card_number;
/* 04 */ ptext<char, 0x18> player_name;
} __attribute__((packed));
// TODO: What do these player references mean? When are entries added to or
// removed from this list?
/* 2850:1FD4:2128 */ parray<PlayerReference, 9> unknown_a9;
/* 294C:20D0:2224 */ parray<uint8_t, 0x50> unknown_a10;
/* 299C:2120:2274 */ ptext<char, 0x10> name;
/* 29AC:2130:2284 */ parray<uint8_t, 0xCC> unknown_a11;
/* 2A78:21FC:2350 */
/* 2128 */ parray<PlayerReference, 9> unknown_a9;
/* 2224 */ parray<uint8_t, 0x50> unknown_a10;
/* 2274 */ ptext<char, 0x10> name;
/* 2284 */ parray<uint8_t, 0xCC> unknown_a11;
/* 2350 */
void decrypt();
void encrypt(uint8_t basis);
+60 -25
View File
@@ -135,8 +135,26 @@ struct PSOGCCharacterFile {
// The signature field holds the value 0xA205B064, which is 2718281828 in
// decimal - approximately e * 10^9. It's unknown why Sega chose this value.
/* 0424 */ be_uint32_t signature;
/* 0428 */ be_uint32_t unknown_a2;
/* 042C */ be_uint32_t unknown_a3;
/* 0428 */ be_uint32_t play_time_seconds;
// This field is a collection of several flags and small values. The known
// fields are:
// ------zA BCDEFG-- HHHIIIJJ KLMNOPQR
// z = Function key setting (BB; 0 = menu shortcuts; 1 = chat shortcuts).
// This bit is unused by PSO GC.
// A = Keyboard controls (BB; 0 = on; 1 = off). Note that A is also used
// by PSO GC, but its function is currently unknown.
// G = Choice search setting (0 = enabled; 1 = disabled)
// H = Player lobby labels (0 = name; 1 = name, language, and level;
// 2 = W/D counts; 3 = challenge rank; 4 = nothing)
// I = Idle disconnect time (0 = 15 mins; 1 = 30 mins; 2 = 45 mins;
// 3 = 60 mins; 4 or 5: immediately; 6: 16 seconds; 7: 32 seconds).
// Obviously the behaviors for 4-7 are unintended; this is the result
// of a missing bounds check.
// J = Message speed (0 = slow; 1 = normal; 2 = fast; 3 = very fast)
// P = Cursor position (0 = saved; 1 = non-saved)
// Q = Button config (0 = normal; 1 = L/R reversed)
// R = Map direction (0 = non-fixed; 1 = fixed)
/* 042C */ be_uint32_t option_flags;
/* 0430 */ be_uint32_t save_count;
/* 0434 */ parray<uint8_t, 0x230> unknown_a4;
/* 0664 */ PlayerBank bank;
@@ -163,37 +181,54 @@ struct PSOGCCharacterFile {
struct PSOGCEp3CharacterFile {
/* 00000 */ be_uint32_t checksum; // crc32 of this field (as 0) through end of struct
struct Character {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 041C */ be_uint32_t unknown_a1;
/* 0420 */ be_uint32_t creation_timestamp;
/* 0424 */ be_uint32_t signature; // Same value as for Episodes 1&2 (above)
/* 0428 */ be_uint32_t unknown_a2;
/* 042C */ be_uint32_t unknown_a3;
/* 0430 */ be_uint32_t save_count;
/* 0434 */ parray<uint8_t, 0x1C> unknown_a4;
/* 0450 */ parray<uint8_t, 0x10> unknown_a5;
// 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:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ be_uint32_t unknown_a1;
/* 0420:0004 */ be_uint32_t creation_timestamp;
/* 0424:0008 */ be_uint32_t signature; // Same value as for Episodes 1&2 (above)
/* 0428:000C */ be_uint32_t play_time_seconds;
// See the comment in PSOGCCharacterFile::Character about what the bits in
// this field mean.
/* 042C:0010 */ be_uint32_t option_flags;
/* 0430:0014 */ be_uint32_t save_count;
/* 0434:0018 */ parray<uint8_t, 0x1C> unknown_a2;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
// seq_vars is an array of 1024 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 */ parray<uint8_t, 0x400> seq_vars;
/* 0860 */ parray<uint8_t, 0x6C> unknown_a6;
/* 08CC */ GuildCardV3 guild_card;
/* 095C */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0D7C */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 140C */ parray<uint8_t, 0xAC> unknown_a7;
/* 14B8 */ ptext<char, 0xAC> info_board;
/* 1564 */ parray<uint8_t, 0xF4> unknown_a8;
/* 1658 */ Episode3::PlayerConfig ep3_config;
/* 39A8 */ be_uint32_t unknown_a9;
/* 39AC */ be_uint32_t unknown_a10;
/* 39B0 */ be_uint32_t unknown_a11;
/* 39B4 */
/* 0460:0044 */ parray<uint8_t, 0x400> seq_vars;
/* 0860:0444 */ be_uint32_t unknown_a4;
/* 0864:0448 */ be_uint32_t unknown_a5;
/* 0868:044C */ be_uint32_t unknown_a6;
/* 086C:0450 */ parray<uint8_t, 0x60> unknown_a7;
/* 08CC:04B0 */ GuildCardV3 guild_card;
/* 095C:0540 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0D7C:0960 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 140C:0FF0 */ ptext<char, 0xAC> auto_reply;
/* 14B8:109C */ ptext<char, 0xAC> info_board;
/* 1564:1148 */ be_uint16_t win_count;
/* 1566:114A */ be_uint16_t lose_count;
/* 1568:114C */ parray<be_uint16_t, 5> unknown_a8;
/* 1572:1156 */ parray<uint8_t, 2> unused;
/* 1574:1158 */ parray<be_uint32_t, 2> unknown_a9;
/* 157C:1160 */ parray<uint8_t, 0xDC> unknown_a10;
/* 1658:123C */ Episode3::PlayerConfig ep3_config;
/* 39A8:358C */ be_uint32_t unknown_a11;
/* 39AC:3590 */ be_uint32_t unknown_a12;
/* 39B0:3594 */ be_uint32_t unknown_a13;
/* 39B4:3598 */
} __attribute__((packed));
/* 00004 */ parray<Character, 7> characters;
/* 193F0 */ ptext<char, 0x10> serial_number; // As %08X (not decimal)
/* 19400 */ ptext<char, 0x10> access_key;
/* 19410 */ ptext<char, 0x10> password;
// In Episode 3, this field still exists, but is unused since BGM test was
// removed from the options menu in favor of the jukebox. The jukebox is
// accessible online only, and which songs are available there is controlled
// by the B7 command sent by the server instead.
/* 19420 */ be_uint64_t bgm_test_songs_unlocked;
/* 19428 */ be_uint32_t save_count;
// This is an array of 1000 bits, represented here as 128 bytes, the last few