add xbox disk file formats
This commit is contained in:
@@ -754,6 +754,7 @@ The data formats that newserv can convert to/from are:
|
|||||||
| PSO DC save file (.vms) | `encrypt-vms-save` | `decrypt-vms-save` |
|
| PSO DC save file (.vms) | `encrypt-vms-save` | `decrypt-vms-save` |
|
||||||
| PSO PC save file | `encrypt-pc-save` | `decrypt-pc-save` |
|
| PSO PC save file | `encrypt-pc-save` | `decrypt-pc-save` |
|
||||||
| PSO GC save file (.gci) | `encrypt-gci-save` | `decrypt-gci-save` |
|
| PSO GC save file (.gci) | `encrypt-gci-save` | `decrypt-gci-save` |
|
||||||
|
| PSO Xbox save file | None | `decrypt-xbox-save` |
|
||||||
| PSO GC snapshot file | None | `decode-gci-snapshot` |
|
| PSO GC snapshot file | None | `decode-gci-snapshot` |
|
||||||
| Quest script (.bin) | `assemble-quest-script` | `disassemble-quest-script` |
|
| Quest script (.bin) | `assemble-quest-script` | `disassemble-quest-script` |
|
||||||
| Quest map (.dat) | None | `disassemble-quest-map` |
|
| Quest map (.dat) | None | `disassemble-quest-map` |
|
||||||
|
|||||||
+1
-1
@@ -1646,7 +1646,7 @@ ChatCommandDefinition cc_loadchar(
|
|||||||
if (!a.c->login || !a.c->login->xb_license) {
|
if (!a.c->login || !a.c->login->xb_license) {
|
||||||
throw runtime_error("XB client is not logged in");
|
throw runtime_error("XB client is not logged in");
|
||||||
}
|
}
|
||||||
PSOXBCharacterFileCharacter xb_char = *a.c->character();
|
PSOXBCharacterFile::Character xb_char = *a.c->character();
|
||||||
xb_char.guild_card.xb_user_id_high = (a.c->login->xb_license->user_id >> 32) & 0xFFFFFFFF;
|
xb_char.guild_card.xb_user_id_high = (a.c->login->xb_license->user_id >> 32) & 0xFFFFFFFF;
|
||||||
xb_char.guild_card.xb_user_id_low = a.c->login->xb_license->user_id & 0xFFFFFFFF;
|
xb_char.guild_card.xb_user_id_low = a.c->login->xb_license->user_id & 0xFFFFFFFF;
|
||||||
co_await send_set_extended_player_info(xb_char);
|
co_await send_set_extended_player_info(xb_char);
|
||||||
|
|||||||
@@ -7429,7 +7429,7 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 {
|
|||||||
// Requested with the GetExtendedPlayerInfo patch. Format depends on version:
|
// Requested with the GetExtendedPlayerInfo patch. Format depends on version:
|
||||||
// DC v2: PSODCV2CharacterFile
|
// DC v2: PSODCV2CharacterFile
|
||||||
// GC v3: PSOGCCharacterFile::Character
|
// GC v3: PSOGCCharacterFile::Character
|
||||||
// XB v3: PSOXBCharacterFileCharacter
|
// XB v3: PSOXBCharacterFile::Character
|
||||||
|
|
||||||
// 6xE4: Increment enemy damage threshold
|
// 6xE4: Increment enemy damage threshold
|
||||||
// This command increments or decrements the minimum amount of damage an enemy
|
// This command increments or decrements the minimum amount of damage an enemy
|
||||||
|
|||||||
+131
-79
@@ -859,85 +859,6 @@ Action a_encrypt_vms_save("encrypt-vms-save", "\
|
|||||||
hexadecimal value.\n",
|
hexadecimal value.\n",
|
||||||
a_encrypt_decrypt_vms_save_fn);
|
a_encrypt_decrypt_vms_save_fn);
|
||||||
|
|
||||||
static void a_encrypt_decrypt_gci_save_fn(phosg::Arguments& args) {
|
|
||||||
bool is_decrypt = (args.get<string>(0) == "decrypt-gci-save");
|
|
||||||
bool skip_checksum = args.get<bool>("skip-checksum");
|
|
||||||
string seed = args.get<string>("seed");
|
|
||||||
string system_filename = args.get<string>("sys");
|
|
||||||
int64_t override_round2_seed = args.get<int64_t>("round2-seed", -1, phosg::Arguments::IntFormat::HEX);
|
|
||||||
|
|
||||||
uint32_t round1_seed;
|
|
||||||
if (!system_filename.empty()) {
|
|
||||||
string system_data = phosg::load_file(system_filename);
|
|
||||||
phosg::StringReader r(system_data);
|
|
||||||
const auto& header = r.get<PSOGCIFileHeader>();
|
|
||||||
header.check();
|
|
||||||
const auto& system = r.get<PSOGCSystemFile>();
|
|
||||||
round1_seed = system.creation_timestamp;
|
|
||||||
} else if (!seed.empty()) {
|
|
||||||
round1_seed = stoul(seed, nullptr, 16);
|
|
||||||
} else {
|
|
||||||
throw runtime_error("either --sys or --seed must be given");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data = read_input_data(args);
|
|
||||||
phosg::StringReader r(data);
|
|
||||||
const auto& header = r.get<PSOGCIFileHeader>();
|
|
||||||
header.check();
|
|
||||||
|
|
||||||
size_t data_start_offset = r.where();
|
|
||||||
|
|
||||||
auto process_file = [&]<typename StructT>() {
|
|
||||||
if (is_decrypt) {
|
|
||||||
const void* data_section = r.getv(header.data_size);
|
|
||||||
auto decrypted = decrypt_fixed_size_data_section_t<StructT, true>(
|
|
||||||
data_section, header.data_size, round1_seed, skip_checksum, override_round2_seed);
|
|
||||||
*reinterpret_cast<StructT*>(data.data() + data_start_offset) = decrypted;
|
|
||||||
} else {
|
|
||||||
const auto& s = r.get<StructT>();
|
|
||||||
auto encrypted = encrypt_fixed_size_data_section_t<StructT, true>(s, round1_seed);
|
|
||||||
if (data_start_offset + encrypted.size() > data.size()) {
|
|
||||||
throw runtime_error("encrypted result exceeds file size");
|
|
||||||
}
|
|
||||||
memcpy(data.data() + data_start_offset, encrypted.data(), encrypted.size());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (header.data_size == sizeof(PSOGCGuildCardFile)) {
|
|
||||||
process_file.template operator()<PSOGCGuildCardFile>();
|
|
||||||
} else if (header.is_ep12() && (header.data_size == sizeof(PSOGCCharacterFile))) {
|
|
||||||
process_file.template operator()<PSOGCCharacterFile>();
|
|
||||||
} else if (header.is_ep3() && (header.data_size == sizeof(PSOGCEp3CharacterFile))) {
|
|
||||||
auto* charfile = reinterpret_cast<PSOGCEp3CharacterFile*>(data.data() + data_start_offset);
|
|
||||||
if (!is_decrypt) {
|
|
||||||
for (size_t z = 0; z < charfile->characters.size(); z++) {
|
|
||||||
charfile->characters[z].ep3_config.encrypt(phosg::random_object<uint8_t>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process_file.template operator()<PSOGCEp3CharacterFile>();
|
|
||||||
if (is_decrypt) {
|
|
||||||
for (size_t z = 0; z < charfile->characters.size(); z++) {
|
|
||||||
charfile->characters[z].ep3_config.decrypt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw runtime_error("unrecognized save type");
|
|
||||||
}
|
|
||||||
|
|
||||||
write_output_data(args, data.data(), data.size(), is_decrypt ? "gcid" : "gci");
|
|
||||||
}
|
|
||||||
|
|
||||||
Action a_decrypt_gci_save("decrypt-gci-save", nullptr, a_encrypt_decrypt_gci_save_fn);
|
|
||||||
Action a_encrypt_gci_save("encrypt-gci-save", "\
|
|
||||||
encrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
|
||||||
decrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
|
||||||
Encrypt or decrypt a character or Guild Card file in GCI format. If\n\
|
|
||||||
encrypting, the checksum is also recomputed and stored in the encrypted\n\
|
|
||||||
file. CRYPT-OPTION is required; it can be either --sys=SYSTEM-FILENAME\n\
|
|
||||||
(specifying the name of the corresponding PSO_SYSTEM .gci file) or\n\
|
|
||||||
--seed=ROUND1-SEED (specified as a 32-bit hexadecimal number).\n",
|
|
||||||
a_encrypt_decrypt_gci_save_fn);
|
|
||||||
|
|
||||||
static void a_encrypt_decrypt_pc_save_fn(phosg::Arguments& args) {
|
static void a_encrypt_decrypt_pc_save_fn(phosg::Arguments& args) {
|
||||||
bool is_decrypt = (args.get<string>(0) == "decrypt-pc-save");
|
bool is_decrypt = (args.get<string>(0) == "decrypt-pc-save");
|
||||||
bool skip_checksum = args.get<bool>("skip-checksum");
|
bool skip_checksum = args.get<bool>("skip-checksum");
|
||||||
@@ -1034,6 +955,137 @@ static void a_encrypt_decrypt_save_data_fn(phosg::Arguments& args) {
|
|||||||
write_output_data(args, output_data.data(), output_data.size(), "dec");
|
write_output_data(args, output_data.data(), output_data.size(), "dec");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void a_encrypt_decrypt_gci_save_fn(phosg::Arguments& args) {
|
||||||
|
bool is_decrypt = (args.get<string>(0) == "decrypt-gci-save");
|
||||||
|
bool skip_checksum = args.get<bool>("skip-checksum");
|
||||||
|
string seed = args.get<string>("seed");
|
||||||
|
string system_filename = args.get<string>("sys");
|
||||||
|
int64_t override_round2_seed = args.get<int64_t>("round2-seed", -1, phosg::Arguments::IntFormat::HEX);
|
||||||
|
|
||||||
|
uint32_t round1_seed;
|
||||||
|
if (!system_filename.empty()) {
|
||||||
|
string system_data = phosg::load_file(system_filename);
|
||||||
|
phosg::StringReader r(system_data);
|
||||||
|
const auto& header = r.get<PSOGCIFileHeader>();
|
||||||
|
header.check();
|
||||||
|
const auto& system = r.get<PSOGCSystemFile>();
|
||||||
|
round1_seed = system.creation_timestamp;
|
||||||
|
} else if (!seed.empty()) {
|
||||||
|
round1_seed = stoul(seed, nullptr, 16);
|
||||||
|
} else {
|
||||||
|
throw runtime_error("either --sys or --seed must be given");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = read_input_data(args);
|
||||||
|
phosg::StringReader r(data);
|
||||||
|
const auto& header = r.get<PSOGCIFileHeader>();
|
||||||
|
header.check();
|
||||||
|
|
||||||
|
size_t data_start_offset = r.where();
|
||||||
|
|
||||||
|
auto process_file = [&]<typename StructT>() {
|
||||||
|
if (is_decrypt) {
|
||||||
|
const void* data_section = r.getv(header.data_size);
|
||||||
|
auto decrypted = decrypt_fixed_size_data_section_t<StructT, true>(
|
||||||
|
data_section, header.data_size, round1_seed, skip_checksum, override_round2_seed);
|
||||||
|
*reinterpret_cast<StructT*>(data.data() + data_start_offset) = decrypted;
|
||||||
|
} else {
|
||||||
|
const auto& s = r.get<StructT>();
|
||||||
|
auto encrypted = encrypt_fixed_size_data_section_t<StructT, true>(s, round1_seed);
|
||||||
|
if (data_start_offset + encrypted.size() > data.size()) {
|
||||||
|
throw runtime_error("encrypted result exceeds file size");
|
||||||
|
}
|
||||||
|
memcpy(data.data() + data_start_offset, encrypted.data(), encrypted.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (header.data_size == sizeof(PSOGCGuildCardFile)) {
|
||||||
|
process_file.template operator()<PSOGCGuildCardFile>();
|
||||||
|
} else if (header.is_ep12() && (header.data_size == sizeof(PSOGCCharacterFile))) {
|
||||||
|
process_file.template operator()<PSOGCCharacterFile>();
|
||||||
|
} else if (header.is_ep3() && (header.data_size == sizeof(PSOGCEp3CharacterFile))) {
|
||||||
|
auto* charfile = reinterpret_cast<PSOGCEp3CharacterFile*>(data.data() + data_start_offset);
|
||||||
|
if (!is_decrypt) {
|
||||||
|
for (size_t z = 0; z < charfile->characters.size(); z++) {
|
||||||
|
charfile->characters[z].ep3_config.encrypt(phosg::random_object<uint8_t>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process_file.template operator()<PSOGCEp3CharacterFile>();
|
||||||
|
if (is_decrypt) {
|
||||||
|
for (size_t z = 0; z < charfile->characters.size(); z++) {
|
||||||
|
charfile->characters[z].ep3_config.decrypt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw runtime_error("unrecognized save type");
|
||||||
|
}
|
||||||
|
|
||||||
|
write_output_data(args, data.data(), data.size(), is_decrypt ? "gcid" : "gci");
|
||||||
|
}
|
||||||
|
|
||||||
|
Action a_decrypt_gci_save("decrypt-gci-save", nullptr, a_encrypt_decrypt_gci_save_fn);
|
||||||
|
Action a_encrypt_gci_save("encrypt-gci-save", "\
|
||||||
|
encrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||||
|
decrypt-gci-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||||
|
Encrypt or decrypt a character or Guild Card file in GCI format. If\n\
|
||||||
|
encrypting, the checksum is also recomputed and stored in the encrypted\n\
|
||||||
|
file. CRYPT-OPTION is required; it can be either --sys=SYSTEM-FILENAME\n\
|
||||||
|
(specifying the name of the corresponding PSO_SYSTEM .gci file) or\n\
|
||||||
|
--seed=ROUND1-SEED (specified as a 32-bit hexadecimal number).\n",
|
||||||
|
a_encrypt_decrypt_gci_save_fn);
|
||||||
|
|
||||||
|
Action a_decrypt_xbox_save(
|
||||||
|
"decrypt-xbox-save", "\
|
||||||
|
decrypt-xbox-save CRYPT-OPTION INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||||
|
Decrypt a character or Guild Card file in Xbox format. CRYPT-OPTION is\n\
|
||||||
|
required; it can be either --sys=SYSTEM-FILENAME (specifying the name of\n\
|
||||||
|
the corresponding PSO_SYSTEM file) or --seed=ROUND1-SEED (specified as a\n\
|
||||||
|
32-bit hexadecimal number).\n",
|
||||||
|
+[](phosg::Arguments& args) {
|
||||||
|
bool skip_checksum = args.get<bool>("skip-checksum");
|
||||||
|
string seed = args.get<string>("seed");
|
||||||
|
string system_filename = args.get<string>("sys");
|
||||||
|
int64_t override_round2_seed = args.get<int64_t>("round2-seed", -1, phosg::Arguments::IntFormat::HEX);
|
||||||
|
|
||||||
|
uint32_t round1_seed;
|
||||||
|
if (!system_filename.empty()) {
|
||||||
|
string system_data = phosg::load_file(system_filename);
|
||||||
|
phosg::StringReader r(system_data);
|
||||||
|
const auto& header = r.get<PSOXBFileHeader>();
|
||||||
|
header.check();
|
||||||
|
const auto& system = r.get<PSOXBSystemFile>();
|
||||||
|
round1_seed = system.creation_timestamp;
|
||||||
|
} else if (!seed.empty()) {
|
||||||
|
round1_seed = stoul(seed, nullptr, 16);
|
||||||
|
} else {
|
||||||
|
throw runtime_error("either --sys or --seed must be given");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = read_input_data(args);
|
||||||
|
phosg::StringReader r(data);
|
||||||
|
const auto& header = r.get<PSOXBFileHeader>();
|
||||||
|
header.check();
|
||||||
|
|
||||||
|
size_t data_start_offset = r.where();
|
||||||
|
|
||||||
|
auto process_file = [&]<typename StructT>() {
|
||||||
|
const void* data_section = r.getv(header.data_size);
|
||||||
|
auto decrypted = decrypt_fixed_size_data_section_t<StructT, false>(
|
||||||
|
data_section, header.data_size, round1_seed, skip_checksum, override_round2_seed);
|
||||||
|
*reinterpret_cast<StructT*>(data.data() + data_start_offset) = decrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (header.data_size == sizeof(PSOXBGuildCardFile)) {
|
||||||
|
process_file.template operator()<PSOXBGuildCardFile>();
|
||||||
|
} else if (header.data_size == sizeof(PSOXBCharacterFile)) {
|
||||||
|
process_file.template operator()<PSOXBCharacterFile>();
|
||||||
|
} else {
|
||||||
|
throw runtime_error("unrecognized save type");
|
||||||
|
}
|
||||||
|
|
||||||
|
write_output_data(args, data.data(), data.size(), "dec");
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: Write usage text for these actions
|
// TODO: Write usage text for these actions
|
||||||
Action a_decrypt_save_data("decrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn);
|
Action a_decrypt_save_data("decrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn);
|
||||||
Action a_encrypt_save_data("encrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn);
|
Action a_encrypt_save_data("encrypt-save-data", nullptr, a_encrypt_decrypt_save_data_fn);
|
||||||
|
|||||||
@@ -3533,7 +3533,7 @@ static asio::awaitable<void> on_30(shared_ptr<Client> c, Channel::Message& msg)
|
|||||||
ch = PSOBBCharacterFile::create_from_file(msg.check_size_t<PSOGCCharacterFile::Character>());
|
ch = PSOBBCharacterFile::create_from_file(msg.check_size_t<PSOGCCharacterFile::Character>());
|
||||||
break;
|
break;
|
||||||
case Version::XB_V3:
|
case Version::XB_V3:
|
||||||
ch = PSOBBCharacterFile::create_from_file(msg.check_size_t<PSOXBCharacterFileCharacter>());
|
ch = PSOBBCharacterFile::create_from_file(msg.check_size_t<PSOXBCharacterFile::Character>());
|
||||||
break;
|
break;
|
||||||
case Version::GC_EP3_NTE:
|
case Version::GC_EP3_NTE:
|
||||||
case Version::GC_EP3:
|
case Version::GC_EP3:
|
||||||
|
|||||||
+19
-3
@@ -312,6 +312,22 @@ PSOGCEp3CharacterFile::Character::operator PSOGCEp3NTECharacter() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PSOXBFileHeader::checksum_correct() const {
|
||||||
|
uint32_t cs = phosg::crc32(&this->game_name, this->game_name.bytes());
|
||||||
|
cs = phosg::crc32(&this->file_name, this->file_name.bytes(), cs);
|
||||||
|
cs = phosg::crc32(&this->banner, this->banner.bytes(), cs);
|
||||||
|
cs = phosg::crc32(&this->icon, this->icon.bytes(), cs);
|
||||||
|
cs = phosg::crc32(&this->data_size, sizeof(this->data_size), cs);
|
||||||
|
cs = phosg::crc32("\0\0\0\0", 4, cs); // this->checksum (treated as zero)
|
||||||
|
return (cs == this->checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PSOXBFileHeader::check() const {
|
||||||
|
if (!this->checksum_correct()) {
|
||||||
|
throw runtime_error("Xbox file intermediate header checksum is incorrect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PSOBBGuildCardFile::Entry::clear() {
|
void PSOBBGuildCardFile::Entry::clear() {
|
||||||
this->data.clear();
|
this->data.clear();
|
||||||
this->unknown_a1.clear(0);
|
this->unknown_a1.clear(0);
|
||||||
@@ -830,7 +846,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCE
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFileCharacter& src) {
|
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
auto ret = PSOBBCharacterFile::create_from_config(
|
||||||
src.guild_card.guild_card_number,
|
src.guild_card.guild_card_number,
|
||||||
src.inventory.language,
|
src.inventory.language,
|
||||||
@@ -1101,10 +1117,10 @@ PSOBBCharacterFile::operator PSOGCCharacterFile::Character() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSOBBCharacterFile::operator PSOXBCharacterFileCharacter() const {
|
PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
|
||||||
uint8_t language = this->inventory.language;
|
uint8_t language = this->inventory.language;
|
||||||
|
|
||||||
PSOXBCharacterFileCharacter ret;
|
PSOXBCharacterFile::Character ret;
|
||||||
ret.inventory = this->inventory;
|
ret.inventory = this->inventory;
|
||||||
ret.inventory.encode_for_client(Version::XB_V3, nullptr);
|
ret.inventory.encode_for_client(Version::XB_V3, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
||||||
|
|||||||
+83
-4
@@ -91,6 +91,31 @@ struct PSOGCIFileHeader {
|
|||||||
bool is_nte() const;
|
bool is_nte() const;
|
||||||
} __packed_ws__(PSOGCIFileHeader, 0x2088);
|
} __packed_ws__(PSOGCIFileHeader, 0x2088);
|
||||||
|
|
||||||
|
struct PSOXBFileHeader {
|
||||||
|
// The signature is computed by doing the following:
|
||||||
|
// // TODO: Should flags be 0 or 1? It looks like it should be 0 for
|
||||||
|
// // character files, but not sure about this
|
||||||
|
// auto handle = XCalculateSignatureBegin(flags);
|
||||||
|
// XCalculateSignatureUpdate(
|
||||||
|
// handle,
|
||||||
|
// &header.source_size,
|
||||||
|
// total_size - offsetof(PSOXBFileHeader, source_size));
|
||||||
|
// XCalculateSignatureEnd(handle, header.signature);
|
||||||
|
/* 0000 */ parray<uint8_t, 0x14> signature;
|
||||||
|
/* 0014 */ le_uint32_t source_size = 0; // == total file size - 0x4000
|
||||||
|
/* 0018 */ parray<uint8_t, 0x3FE8> unused; // Always blank (zeroes)
|
||||||
|
/* 4000 */ pstring<TextEncoding::MARKED, 0x20> game_name;
|
||||||
|
/* 4020 */ pstring<TextEncoding::MARKED, 0x20> file_name;
|
||||||
|
/* 4040 */ parray<uint8_t, 0x1800> banner; // Always blank (zeroes)
|
||||||
|
/* 5840 */ parray<uint8_t, 0x800> icon; // Always blank (zeroes)
|
||||||
|
/* 6040 */ le_uint32_t data_size = 0;
|
||||||
|
/* 6044 */ le_uint32_t checksum = 0; // Starts at game_name
|
||||||
|
/* 6048 */
|
||||||
|
|
||||||
|
bool checksum_correct() const;
|
||||||
|
void check() const;
|
||||||
|
} __packed_ws__(PSOXBFileHeader, 0x6048);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Subordinate structures
|
// Subordinate structures
|
||||||
|
|
||||||
@@ -280,6 +305,35 @@ struct PSOGCEp3SystemFile {
|
|||||||
/* 012C */
|
/* 012C */
|
||||||
} __packed_ws__(PSOGCEp3SystemFile, 0x12C);
|
} __packed_ws__(PSOGCEp3SystemFile, 0x12C);
|
||||||
|
|
||||||
|
struct PSOXBSystemFile {
|
||||||
|
/* 0000 */ le_uint32_t checksum = 0;
|
||||||
|
/* 0004 */ le_int16_t music_volume = -50;
|
||||||
|
/* 0006 */ int8_t sound_volume = 0;
|
||||||
|
/* 0007 */ uint8_t language = 0;
|
||||||
|
/* 0008 */ be_int32_t server_time_delta_frames = 200;
|
||||||
|
/* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off
|
||||||
|
/* 000E */ be_uint16_t surround_sound_enabled = 0;
|
||||||
|
/* 0010 */ parray<uint8_t, 0x0100> event_flags;
|
||||||
|
/* 0110 */ parray<uint8_t, 8> unknown_a1;
|
||||||
|
struct UserEntry {
|
||||||
|
/* 00 */ le_uint32_t xb_user_id_high = 0;
|
||||||
|
/* 04 */ le_uint32_t xb_user_id_low = 0;
|
||||||
|
/* 08 */ le_uint32_t unknown_a2;
|
||||||
|
/* 0C */ le_uint32_t last_write_year;
|
||||||
|
/* 10 */ le_uint32_t last_write_month;
|
||||||
|
/* 14 */ le_uint32_t last_write_day;
|
||||||
|
/* 18 */ le_uint32_t last_write_hour;
|
||||||
|
/* 1C */ le_uint32_t last_write_minute;
|
||||||
|
/* 20 */ le_uint32_t last_write_second;
|
||||||
|
/* 24 */ le_uint32_t flags = 1; // 1 = not present
|
||||||
|
/* 28 */ pstring<TextEncoding::ASCII, 0x10> gamertag;
|
||||||
|
/* 38 */
|
||||||
|
} __packed_ws__(UserEntry, 0x38);
|
||||||
|
/* 0118 */ parray<UserEntry, 4> users;
|
||||||
|
/* 01F8 */ le_uint32_t creation_timestamp = 0;
|
||||||
|
/* 01FC */
|
||||||
|
} __packed_ws__(PSOXBSystemFile, 0x1FC);
|
||||||
|
|
||||||
struct PSOBBMinimalSystemFile {
|
struct PSOBBMinimalSystemFile {
|
||||||
/* 0000 */ be_uint32_t checksum = 0;
|
/* 0000 */ be_uint32_t checksum = 0;
|
||||||
/* 0004 */ be_int16_t music_volume = 0;
|
/* 0004 */ be_int16_t music_volume = 0;
|
||||||
@@ -717,7 +771,8 @@ struct PSOGCEp3CharacterFile {
|
|||||||
/* 194B0 */
|
/* 194B0 */
|
||||||
} __packed_ws__(PSOGCEp3CharacterFile, 0x194B0);
|
} __packed_ws__(PSOGCEp3CharacterFile, 0x194B0);
|
||||||
|
|
||||||
struct PSOXBCharacterFileCharacter {
|
struct PSOXBCharacterFile {
|
||||||
|
struct Character {
|
||||||
// This structure is internally split into two by the game. The offsets here
|
// 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
|
// are relative to the start of this structure (first column), and relative
|
||||||
// to the start of the second internal structure (second column).
|
// to the start of the second internal structure (second column).
|
||||||
@@ -758,7 +813,23 @@ struct PSOXBCharacterFileCharacter {
|
|||||||
} __packed_ws__(UnknownA8Entry, 0x30);
|
} __packed_ws__(UnknownA8Entry, 0x30);
|
||||||
/* 27D8:23BC */ parray<UnknownA8Entry, 5> unknown_a8;
|
/* 27D8:23BC */ parray<UnknownA8Entry, 5> unknown_a8;
|
||||||
/* 28C8:24AC */
|
/* 28C8:24AC */
|
||||||
} __packed_ws__(PSOXBCharacterFileCharacter, 0x28C8);
|
} __packed_ws__(Character, 0x28C8);
|
||||||
|
|
||||||
|
struct CharEntry {
|
||||||
|
Character character;
|
||||||
|
parray<uint8_t, 0x18> unknown_a1;
|
||||||
|
} __packed_ws__(CharEntry, 0x28E0);
|
||||||
|
|
||||||
|
/* 00000 */ le_uint32_t checksum = 0;
|
||||||
|
/* 00004 */ parray<CharEntry, 15> characters;
|
||||||
|
/* 26524 */ pstring<TextEncoding::ASCII, 0x10> serial_number;
|
||||||
|
/* 26534 */ pstring<TextEncoding::ASCII, 0x10> access_key;
|
||||||
|
/* 26544 */ pstring<TextEncoding::ASCII, 0x10> password;
|
||||||
|
/* 26554 */ be_uint64_t bgm_test_songs_unlocked = 0;
|
||||||
|
/* 2655C */ le_uint32_t save_count = 1;
|
||||||
|
/* 26560 */ le_uint32_t round2_seed = 0;
|
||||||
|
/* 26564 */
|
||||||
|
} __packed_ws__(PSOXBCharacterFile, 0x26564);
|
||||||
|
|
||||||
struct PSOBBCharacterFile {
|
struct PSOBBCharacterFile {
|
||||||
// Most fields have the same meanings as in PSOGCCharacterFile::Character.
|
// Most fields have the same meanings as in PSOGCCharacterFile::Character.
|
||||||
@@ -816,7 +887,7 @@ struct PSOBBCharacterFile {
|
|||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCNTECharacterFileCharacter& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCNTECharacterFileCharacter& src);
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCCharacterFile::Character& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCCharacterFile::Character& src);
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCEp3CharacterFile::Character& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCEp3CharacterFile::Character& src);
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOXBCharacterFileCharacter& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOXBCharacterFile::Character& src);
|
||||||
|
|
||||||
PSODCNTECharacterFile::Character as_dc_nte(uint64_t hardware_id) const;
|
PSODCNTECharacterFile::Character as_dc_nte(uint64_t hardware_id) const;
|
||||||
PSODC112000CharacterFile::Character as_11_2000(uint64_t hardware_id) const;
|
PSODC112000CharacterFile::Character as_11_2000(uint64_t hardware_id) const;
|
||||||
@@ -825,7 +896,7 @@ struct PSOBBCharacterFile {
|
|||||||
operator PSOGCNTECharacterFileCharacter() const;
|
operator PSOGCNTECharacterFileCharacter() const;
|
||||||
operator PSOGCCharacterFile::Character() const;
|
operator PSOGCCharacterFile::Character() const;
|
||||||
operator PSOGCEp3CharacterFile::Character() const;
|
operator PSOGCEp3CharacterFile::Character() const;
|
||||||
operator PSOXBCharacterFileCharacter() const;
|
operator PSOXBCharacterFile::Character() const;
|
||||||
|
|
||||||
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
|
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
|
||||||
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
|
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
|
||||||
@@ -947,6 +1018,14 @@ struct PSOGCGuildCardFile {
|
|||||||
/* E28C */
|
/* E28C */
|
||||||
} __packed_ws__(PSOGCGuildCardFile, 0xE28C);
|
} __packed_ws__(PSOGCGuildCardFile, 0xE28C);
|
||||||
|
|
||||||
|
struct PSOXBGuildCardFile {
|
||||||
|
/* 00000 */ le_uint32_t checksum = 0;
|
||||||
|
/* 00004 */ parray<GuildCardXB, 100> entries;
|
||||||
|
/* 0D934 */ parray<GuildCardXB, 0x1C> blocked_senders;
|
||||||
|
/* 11604 */ le_uint32_t creation_timestamp = 0;
|
||||||
|
/* 11608 */ le_uint32_t round2_seed = 0;
|
||||||
|
} __packed_ws__(PSOXBGuildCardFile, 0x1160C);
|
||||||
|
|
||||||
struct PSOBBGuildCardFile {
|
struct PSOBBGuildCardFile {
|
||||||
struct Entry {
|
struct Entry {
|
||||||
/* 0000 */ GuildCardBB data;
|
/* 0000 */ GuildCardBB data;
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ get_data_ptr:
|
|||||||
data:
|
data:
|
||||||
.data <VERS 0x0062D844 0x0062DDE4 0x0063591C 0x00632E04 0x0063269C 0x00632E04 0x0063319C> # char_file_part1
|
.data <VERS 0x0062D844 0x0062DDE4 0x0063591C 0x00632E04 0x0063269C 0x00632E04 0x0063319C> # char_file_part1
|
||||||
.data <VERS 0x0062D8E8 0x0062DE88 0x006359C0 0x00632EA8 0x00632740 0x00632EA8 0x00633240> # char_file_part2
|
.data <VERS 0x0062D8E8 0x0062DE88 0x006359C0 0x00632EA8 0x00632740 0x00632EA8 0x00633240> # char_file_part2
|
||||||
# Server adds a PSOXBCharacterFileCharacter here
|
# Server adds a PSOXBCharacterFile::Character here
|
||||||
|
|||||||
Reference in New Issue
Block a user