#pragma once #include #include #include #include #include #include #include "PSOEncryption.hh" #include "Text.hh" #include "Player.hh" struct ShuffleTables { uint8_t forward_table[0x100]; uint8_t reverse_table[0x100]; ShuffleTables(PSOV2Encryption& crypt); static uint32_t pseudorand(PSOV2Encryption& crypt, uint32_t prev); void shuffle(void* vdest, const void* vsrc, size_t size, bool reverse) const; }; struct PSOGCIFileHeader { /* 0000 */ parray game_id; // 'GPOE', 'GPSP', etc. /* 0004 */ parray developer_id; // '8P' for Sega // There is a structure for this part of the header, but we don't use it /* 0006 */ parray remaining_gci_header; // game_name is e.g. "PSO EPISODE I & II" or "PSO EPISODE III" /* 0040 */ ptext game_name; /* 005C */ be_uint32_t embedded_seed; // Used in some of Ralf's quest packs /* 0060 */ ptext file_name; /* 0080 */ parray banner; /* 1880 */ parray icon; // data_size specifies the number of bytes remaining in the file. In all cases // except for the system file, this data is encrypted. /* 2080 */ be_uint32_t data_size; // To compute checksum, set checksum to zero, then compute the CRC32 of all // fields in this struct starting with gci_header.game_name. (Yes, including // the checksum field, which is temporarily zero.) See checksum_correct below. /* 2084 */ be_uint32_t checksum; bool checksum_correct() const; void check() const; bool is_ep12() const; bool is_ep3() const; } __attribute__((packed)); struct PSOGCSystemFile { /* 0000 */ be_uint32_t checksum; /* 0004 */ be_uint16_t unknown_a1; /* 0006 */ uint8_t unknown_a2; /* 0007 */ uint8_t language; /* 0008 */ be_uint32_t unknown_a3; /* 000C */ be_uint16_t unknown_a4; /* 000E */ be_uint16_t unknown_a5; /* 0010 */ parray unknown_a6; /* 0110 */ parray unknown_a7; /* 0118 */ be_uint32_t creation_internet_time; // Character file round1 seed /* 011C */ } __attribute__((packed)); struct PSOGCEp3SystemFile { /* 0000 */ PSOGCSystemFile base; /* 011C */ int8_t unknown_a1; /* 011D */ parray unknown_a2; /* 0128 */ be_uint32_t unknown_a3; /* 012C */ } __attribute__((packed)); struct PSOGCCharacterFile { /* 00000 */ be_uint32_t checksum; /* 00004 */ parray unknown_a1; // TODO /* 11568 */ be_uint32_t round2_seed; /* 1156C */ } __attribute__((packed)); 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 save_token; // Sent in 96 command /* 0424 */ parray unknown_a2; /* 0430 */ be_uint32_t save_count; // Sent in 96 command /* 0434 */ parray unknown_a3; /* 08CC */ GuildCardV3 guild_card; struct SymbolChatEntry { /* 00 */ be_uint32_t present; /* 04 */ ptext name; /* 1C */ be_uint16_t unused; /* 1E */ uint8_t flags; /* 1F */ uint8_t face_spec; struct CornerObject { uint8_t type; uint8_t flags_color; } __attribute__((packed)); /* 20 */ parray corner_objects; struct FacePart { uint8_t type; uint8_t x; uint8_t y; uint8_t flags; } __attribute__((packed)); /* 28 */ parray face_parts; /* 58 */ } __attribute__((packed)); /* 095C */ parray symbol_chats; struct ChatShortcut { /* 00 */ be_uint32_t present_type; /* 04 */ parray definition; /* 54 */ } __attribute__((packed)); /* 0D7C */ parray chat_shortcuts; /* 140C */ parray unknown_a4; /* 14B8 */ ptext info_board; /* 1564 */ parray unknown_a5; /* 1658 */ Episode3::PlayerConfig ep3_config; /* 39A8 */ be_uint32_t unknown_a7; /* 39AC */ be_uint32_t unknown_a8; /* 39B0 */ be_uint32_t unknown_a9; /* 39B4 */ } __attribute__((packed)); /* 00004 */ parray characters; /* 193F0 */ ptext serial_number; // As %08X (not decimal) /* 19400 */ ptext access_key; /* 19410 */ ptext password; /* 19420 */ be_uint32_t unknown_a1; /* 19424 */ be_uint32_t unknown_a2; /* 19428 */ be_uint32_t unknown_a3; /* 1942C */ parray unknown_a4; /* 194AC */ be_uint32_t round2_seed; /* 194B0 */ } __attribute__((packed)); struct PSOGCGuildCardFile { /* 0000 */ be_uint32_t checksum; /* 0004 */ parray unknown_a1; /* E288 */ be_uint32_t round2_seed; /* E28C */ } __attribute__((packed)); template std::string decrypt_gci_or_vms_v2_data_section( const void* data_section, size_t size, uint32_t round1_seed) { std::string decrypted(size, '\0'); PSOV2Encryption shuf_crypt(round1_seed); ShuffleTables shuf(shuf_crypt); shuf.shuffle(decrypted.data(), data_section, size, true); size_t orig_size = decrypted.size(); decrypted.resize((decrypted.size() + 3) & (~3)); PSOV2Encryption round1_crypt(round1_seed); round1_crypt.encrypt_minus_t(decrypted.data(), decrypted.size()); decrypted.resize(orig_size); return decrypted; } template std::string encrypt_gci_or_vms_v2_data_section( const void* data_section, size_t size, uint32_t round1_seed) { std::string encrypted(reinterpret_cast(data_section), size); encrypted.resize((encrypted.size() + 3) & (~3)); PSOV2Encryption crypt(round1_seed); crypt.encrypt_minus_t(encrypted.data(), encrypted.size()); std::string ret(size, '\0'); PSOV2Encryption shuf_crypt(round1_seed); ShuffleTables shuf(shuf_crypt); shuf.shuffle(ret.data(), encrypted.data(), size, false); return ret; } template StructT decrypt_gci_fixed_size_file_data_section( const void* data_section, size_t size, uint32_t round1_seed) { std::string decrypted = decrypt_gci_or_vms_v2_data_section( data_section, size, round1_seed); if (decrypted.size() < sizeof(StructT)) { throw std::runtime_error("file too small for structure"); } StructT ret = *reinterpret_cast(decrypted.data()); PSOV2Encryption round2_crypt(ret.round2_seed); round2_crypt.encrypt_big_endian(&ret, offsetof(StructT, round2_seed)); uint32_t expected_crc = ret.checksum; ret.checksum = 0; uint32_t actual_crc = crc32(&ret, sizeof(ret)); ret.checksum = expected_crc; if (expected_crc != actual_crc) { throw std::runtime_error(string_printf( "incorrect decrypted data section checksum: expected %08" PRIX32 "; received %08" PRIX32, expected_crc, actual_crc)); } return ret; } template std::string encrypt_gci_fixed_size_file_data_section( const StructT& s, uint32_t round1_seed) { StructT encrypted = s; encrypted.checksum = 0; encrypted.round2_seed = random_object(); encrypted.checksum = crc32(&encrypted, sizeof(encrypted)); PSOV2Encryption round2_crypt(encrypted.round2_seed); round2_crypt.encrypt_big_endian(&encrypted, offsetof(StructT, round2_seed)); return encrypt_gci_or_vms_v2_data_section( &encrypted, sizeof(StructT), round1_seed); }