describe DC save file formats; add decrypt/encrypt actions

This commit is contained in:
Martin Michelsen
2024-11-03 21:24:48 -08:00
parent ac20d0c7d4
commit 0522b539c4
22 changed files with 544 additions and 214 deletions
+2 -2
View File
@@ -1728,8 +1728,8 @@ 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);
auto dc_char = make_shared<PSODCV2CharacterFile::Character>(c->character()->to_dc_v2());
send_set_extended_player_info.operator()<PSODCV2CharacterFile::Character>(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);
+81 -48
View File
@@ -2,6 +2,7 @@
#include <stdint.h>
#include <mutex>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
@@ -24,6 +25,7 @@ static const uint32_t primes1[] = {
0x313, 0x31D, 0x329, 0x32B, 0x335, 0x337, 0x33B, 0x33D, 0x347, 0x355, 0x359,
0x35B, 0x35F, 0x36D, 0x371, 0x373, 0x377, 0x38B, 0x38F, 0x397, 0x3A1, 0x3A9,
0x3AD, 0x3B3, 0x3B9, 0x3C7, 0x3CB, 0x3D1, 0x3D7, 0x3DF, 0x3E5};
static const uint32_t primes2[] = {
0x3F1, 0x3F5, 0x3FB, 0x3FD, 0x407, 0x409, 0x40F, 0x419, 0x41B, 0x425, 0x427,
0x42D, 0x43F, 0x443, 0x445, 0x449, 0x44F, 0x455, 0x45D, 0x463, 0x469, 0x47F,
@@ -135,6 +137,8 @@ static const uint32_t primes2[] = {
0x2627, 0x2629, 0x2635, 0x263B, 0x263F, 0x264B, 0x2653, 0x2659, 0x2665,
0x2669, 0x266F, 0x267B, 0x2681, 0x2683, 0x268F, 0x269B, 0x269F, 0x26AD,
0x26B3, 0x26C3, 0x26C9, 0x26CB, 0x26D5, 0x26DD, 0x26EF, 0x26F5};
static constexpr size_t num_primes2 = sizeof(primes2) / sizeof(primes2[0]);
static const uint32_t primes3[] = {
0x2717, 0x2719, 0x2735, 0x2737, 0x274D, 0x2753, 0x2755, 0x275F, 0x276B,
0x276D, 0x2773, 0x2777, 0x277F, 0x2795, 0x279B, 0x279D, 0x27A7, 0x27AF,
@@ -1107,15 +1111,19 @@ static const uint32_t primes3[] = {
0x18569, 0x1857B, 0x1857D, 0x18581, 0x18587, 0x18589, 0x18595, 0x185B1,
0x185B7, 0x185CB, 0x185D1, 0x185E1, 0x185E9, 0x185EF, 0x185F5, 0x185F9,
0x185FF, 0x18613, 0x1861F};
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
static bool check_prime3(uint64_t prime3) {
static vector<bool> primes3_set;
static mutex primes3_init_mutex;
if (primes3_set.empty()) {
size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
primes3_set.resize(primes3_set_size, false);
for (size_t z = 0; z < num_primes3; z++) {
primes3_set[primes3[z] - primes3[0]] = true;
lock_guard g(primes3_init_mutex);
if (primes3_set.empty()) {
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
primes3_set.resize(primes3_set_size, false);
for (size_t z = 0; z < num_primes3; z++) {
primes3_set[primes3[z] - primes3[0]] = true;
}
}
}
uint64_t offset = prime3 - primes3[0];
@@ -1227,8 +1235,8 @@ bool dc_serial_number_is_valid_slow(const string& s, uint8_t domain, uint8_t sub
}
for (; offset1 < limit1; offset1++) {
for (size_t offset2 = 0; offset2 < sizeof(primes2) / sizeof(primes2[0]); offset2++) {
for (size_t offset3 = 0; offset3 < sizeof(primes3) / sizeof(primes3[0]); offset3++) {
for (size_t offset2 = 0; offset2 < num_primes2; offset2++) {
for (size_t offset3 = 0; offset3 < num_primes3; offset3++) {
if (primes1[offset1] * primes2[offset2] * primes3[offset3] == serial_number) {
return true;
}
@@ -1251,7 +1259,7 @@ bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t doma
continue;
}
uint64_t sub1 = sub0 / primes1[offset1];
for (size_t offset2 = 0; offset2 < sizeof(primes2) / sizeof(primes2[0]); offset2++) {
for (size_t offset2 = 0; offset2 < num_primes2; offset2++) {
if (sub1 % primes2[offset2]) {
continue;
}
@@ -1293,8 +1301,8 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
size_t det1 = (subdomain == 0xFF) ? phosg::random_object<uint32_t>() : subdomain;
size_t index1 = offset1 + (det1 % (limit1 - offset1));
size_t index2 = phosg::random_object<uint32_t>() % (sizeof(primes2) / sizeof(primes2[0]));
size_t index3 = phosg::random_object<uint32_t>() % (sizeof(primes3) / sizeof(primes3[0]));
size_t index2 = phosg::random_object<uint32_t>() % num_primes2;
size_t index3 = phosg::random_object<uint32_t>() % num_primes3;
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
string s = phosg::string_printf("%08X", value);
@@ -1306,54 +1314,79 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
}
unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
vector<uint8_t> domains;
if (domain == 0xFF) {
domains.emplace_back(0x00);
domains.emplace_back(0x01);
domains.emplace_back(0x02);
} else {
domains.emplace_back(domain);
DCSerialNumberIterator iter;
if (domain < 3) {
iter.domain = domain;
iter.end_domain = domain + 1;
} else if (domain != 0xFF) {
throw runtime_error("invalid domain");
}
vector<uint8_t> subdomains;
if (subdomain == 0xFF) {
subdomains.emplace_back(0x00);
subdomains.emplace_back(0x01);
subdomains.emplace_back(0x02);
} else {
subdomains.emplace_back(subdomain);
if (subdomain < 3) {
iter.subdomain = subdomain;
iter.start_subdomain = subdomain;
iter.end_subdomain = subdomain + 1;
} else if (subdomain != 0xFF) {
throw runtime_error("invalid subdomain");
}
uint32_t serial_number;
unordered_map<uint32_t, string> ret;
for (uint8_t domain : domains) {
size_t offset1, limit1;
if (domain == 0) {
offset1 = 0x00;
limit1 = 0x03;
} else if (domain == 1) {
offset1 = 0x1E;
limit1 = 0x21;
} else if (domain == 2) {
offset1 = 0x3C;
limit1 = 0x3F;
} else {
throw runtime_error("invalid domain");
}
for (uint8_t subdomain : subdomains) {
size_t index1 = offset1 + (subdomain % (limit1 - offset1));
for (size_t index2 = 0; index2 < sizeof(primes2) / sizeof(primes2[0]); index2++) {
for (size_t index3 = 0; index3 < sizeof(primes3) / sizeof(primes3[0]); index3++) {
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
ret[encode_dc_serial_number_int(value)].push_back(((domain << 2) & 3) | (subdomain & 3));
}
fprintf(stderr, "... domain=%hhu subdomain=%hhu index2=%zu results=%zu (0x%zX)\n", domain, subdomain, index2, ret.size(), ret.size());
}
while ((serial_number = iter.next()) != 0) {
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
if (iter.index3 == 0) {
fprintf(stderr, "... (it) domain=%hhu subdomain=%hhu index2=%hu results=%zu (0x%zX)\n", iter.domain, iter.subdomain, iter.index2, ret.size(), ret.size());
}
}
return ret;
}
uint32_t DCSerialNumberIterator::next() {
if (!this->started) {
this->started = true;
} else if (!this->complete) {
this->index3++;
if (this->index3 >= num_primes3) {
this->index3 = 0;
this->index2++;
if (this->index2 >= num_primes2) {
this->index2 = 0;
this->subdomain++;
if (this->subdomain >= this->end_subdomain) {
this->subdomain = this->start_subdomain;
this->domain++;
if (this->domain >= this->end_domain) {
this->serial_number = 0;
this->complete = true;
}
}
}
}
}
if (!this->complete) {
size_t index1 = (30 * this->domain) + (this->subdomain % 3);
return encode_dc_serial_number_int(primes1[index1] * primes2[this->index2] * primes3[this->index3]);
} else {
return 0;
}
}
size_t DCSerialNumberIterator::total_count() const {
return (this->end_domain - this->start_domain) * (this->end_subdomain - this->start_subdomain) * num_primes2 * num_primes3;
}
size_t DCSerialNumberIterator::progress() const {
size_t domains_done = this->domain - this->start_domain;
size_t subdomains_per_domain = this->end_subdomain - this->start_subdomain;
size_t subdomains_done = this->subdomain - this->start_subdomain;
return (
(domains_done * subdomains_per_domain * num_primes2 * num_primes3) +
(subdomains_done * num_primes2 * num_primes3) +
(this->index2 * num_primes3) +
this->index3);
}
void dc_serial_number_speed_test(uint64_t seed) {
uint32_t effective_seed = (seed & 0xFFFFFFFF00000000) ? phosg::random_object<uint32_t>() : seed;
fprintf(stderr, "Product speed test with seed=%08" PRIX32 "\n", effective_seed);
+19
View File
@@ -20,6 +20,25 @@ bool decoded_dc_serial_number_is_valid_fast(
std::string generate_dc_serial_number(uint8_t domain, uint8_t subdomain = 0xFF);
std::unordered_map<uint32_t, std::string> generate_all_dc_serial_numbers(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF);
struct DCSerialNumberIterator {
bool started = false;
bool complete = false;
uint8_t domain = 0;
uint8_t start_domain = 0;
uint8_t end_domain = 3;
uint8_t subdomain = 0;
uint8_t start_subdomain = 0;
uint8_t end_subdomain = 3;
uint16_t index2 = 0;
uint16_t index3 = 0;
uint32_t serial_number = 0;
uint32_t next();
size_t total_count() const;
size_t progress() const;
};
void dc_serial_number_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF);
struct EncryptedDCv2Executables {
+212 -30
View File
@@ -610,8 +610,6 @@ Action a_parse_pc_v2_registry(
serial_number, serial_number, access_data.c_str(), email_data.c_str());
});
// ./newserv generate-pc-v2-registry --serial-number=386248990 --access-key=14575600 --email=mjem@wildblue.net
Action a_generate_pc_v2_registry(
"generate-pc-v2-registry", "\
generate-pc-v2-registry <OPTIONS> [OUTPUT-FILENAME]\n\
@@ -674,6 +672,172 @@ Action a_decrypt_challenge_data(
write_output_data(args, data.data(), data.size(), "dec");
});
static void a_encrypt_decrypt_vms_save_fn(phosg::Arguments& args) {
bool is_decrypt = (args.get<string>(0) == "decrypt-vms-save");
bool skip_checksum = args.get<bool>("skip-checksum");
string serial_number_str = args.get<string>("serial-number");
int64_t override_round2_seed = args.get<int64_t>("round2-seed", -1, phosg::Arguments::IntFormat::HEX);
int64_t round1_seed = serial_number_str.empty() ? -1 : stoul(serial_number_str, nullptr, 16);
auto data = read_input_data(args);
phosg::StringReader r(data);
const auto& header = r.get<PSOVMSFileHeader>();
header.check();
r.skip(header.icon_data_size());
size_t data_start_offset = r.where();
auto process_file = [&]<typename StructT, bool UseIterator, size_t ChecksumLength = sizeof(StructT)>() {
if (is_decrypt) {
const void* data_section = r.getv(header.data_size);
if (round1_seed < 0) {
size_t num_threads = args.get<size_t>("threads", 0);
if (num_threads == 0) {
num_threads = thread::hardware_concurrency();
}
mutex output_lock;
if (UseIterator) {
DCSerialNumberIterator iter;
mutex iter_lock;
atomic<bool> seed_found = false;
auto thread_fn = [&]() -> void {
for (;;) {
uint32_t serial_number;
{
lock_guard g(iter_lock);
serial_number = iter.next();
}
if (serial_number == 0) {
return;
}
try {
auto decrypted = decrypt_fixed_size_data_section_t<StructT, false, ChecksumLength>(
data_section, sizeof(StructT), serial_number, skip_checksum, override_round2_seed);
seed_found = true;
{
lock_guard g(iter_lock);
iter.complete = true;
}
lock_guard g(output_lock);
fprintf(stderr, "\nFound serial number: %08" PRIX32 "\n", serial_number);
*reinterpret_cast<StructT*>(data.data() + data_start_offset) = decrypted;
} catch (const runtime_error&) {
}
}
};
vector<thread> threads;
while (threads.size() < num_threads) {
threads.emplace_back(thread_fn);
}
for (;;) {
usleep(1000000);
lock_guard g(iter_lock);
size_t progress = iter.progress();
size_t total_count = iter.total_count();
float progress_percent = static_cast<float>(progress * 100) / total_count;
fprintf(stderr, "... %zu/%zu (%g%%, domain %02hhX, subdomain %02hhX, index2 %04hX, index3 %04hX)\r",
progress, total_count, progress_percent, iter.domain, iter.subdomain, iter.index2, iter.index3);
if (iter.complete) {
break;
}
}
for (auto& th : threads) {
th.join();
}
if (!seed_found) {
throw runtime_error("no seed found");
}
} else {
uint64_t seed = phosg::parallel_range_blocks<uint64_t>([&](uint64_t serial_number, size_t) -> bool {
try {
auto decrypted = decrypt_fixed_size_data_section_t<StructT, false, ChecksumLength>(
data_section, sizeof(StructT), serial_number, skip_checksum, override_round2_seed);
lock_guard g(output_lock);
fprintf(stderr, "\nFound serial number: %08" PRIX64 "\n", serial_number);
*reinterpret_cast<StructT*>(data.data() + data_start_offset) = decrypted;
return true;
} catch (const runtime_error&) {
return false;
}
},
0, 0x100000000, 0x1000, num_threads);
if (seed >= 0x100000000) {
throw runtime_error("no seed found");
}
}
} else {
auto decrypted = decrypt_fixed_size_data_section_t<StructT, false, ChecksumLength>(
data_section, sizeof(StructT), 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, false, ChecksumLength>(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());
}
};
bool is_v2 = header.is_v2();
if (!is_v2 && (header.data_size == sizeof(PSODCNTECharacterFile))) {
fprintf(stderr, "File type: DC NTE character\n");
process_file.template operator()<PSODCNTECharacterFile, false>();
} else if (!is_v2 && (header.data_size == sizeof(PSODCNTEGuildCardFile))) {
fprintf(stderr, "File type: DC NTE Guild Card list\n");
throw runtime_error("DC NTE Guild Card files are not encrypted");
} else if (!is_v2 && (header.data_size == sizeof(PSODC112000CharacterFile))) {
fprintf(stderr, "File type: DC 11/2000 character\n");
process_file.template operator()<PSODC112000CharacterFile, false>();
} else if (!is_v2 && (header.data_size == sizeof(PSODC112000GuildCardFile))) {
fprintf(stderr, "File type: DC 11/2000 Guild Card list\n");
throw runtime_error("DC 11/2000 Guild Card files are not encrypted");
} else if (!is_v2 && (header.data_size == sizeof(PSODCV1CharacterFile))) {
fprintf(stderr, "File type: DC v1 character\n");
process_file.template operator()<PSODCV1CharacterFile, true>();
} else if (is_v2 && (header.data_size == sizeof(PSODCV2CharacterFile))) {
fprintf(stderr, "File type: DC v2 character\n");
process_file.template operator()<PSODCV2CharacterFile, true>();
} else if (header.data_size == sizeof(PSODCV1V2GuildCardFile)) {
// There appears to be a copy/paste error here: the game uses the character
// file size when checksumming the Guild Card file, so we must do the same
if (!is_v2) {
fprintf(stderr, "File type: DC v1 Guild Card list\n");
static_assert(sizeof(PSODCV1CharacterFile) <= sizeof(PSODCV1V2GuildCardFile::EncryptedSection));
process_file.template operator()<PSODCV1V2GuildCardFile::EncryptedSection, true, sizeof(PSODCV1CharacterFile)>();
} else {
fprintf(stderr, "File type: DC v2 Guild Card list\n");
static_assert(sizeof(PSODCV2CharacterFile) <= sizeof(PSODCV1V2GuildCardFile::EncryptedSection));
process_file.template operator()<PSODCV1V2GuildCardFile::EncryptedSection, true, sizeof(PSODCV2CharacterFile)>();
}
} else {
throw runtime_error("unrecognized save type");
}
write_output_data(args, data.data(), data.size(), is_decrypt ? "vmsd" : "vms");
}
Action a_decrypt_vms_save("decrypt-vms-save", nullptr, a_encrypt_decrypt_vms_save_fn);
Action a_encrypt_vms_save("encrypt-vms-save", "\
encrypt-gci-save --seed=SEED INPUT-FILENAME [OUTPUT-FILENAME]\n\
decrypt-gci-save [--seed=SEED] INPUT-FILENAME [OUTPUT-FILENAME]\n\
Encrypt or decrypt a character or Guild Card file in VMS format. If\n\
encrypting, the checksum is also recomputed and stored in the encrypted\n\
file. --seed is the encryption seed (serial number) specified as a 32-bit\n\
hexadecimal value.\n",
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");
@@ -996,7 +1160,7 @@ Action a_salvage_gci(
auto process_file = [&]<typename StructT>() {
vector<multimap<size_t, uint32_t>> top_seeds_by_thread(
num_threads ? num_threads : thread::hardware_concurrency());
phosg::parallel_range<uint64_t>(
phosg::parallel_range_blocks<uint64_t>(
[&](uint64_t seed, size_t thread_num) -> bool {
size_t zero_count;
if (round2) {
@@ -1026,9 +1190,7 @@ Action a_salvage_gci(
}
return false;
},
0,
0x100000000,
num_threads);
0, 0x100000000, 0x1000, num_threads);
multimap<size_t, uint32_t> top_seeds;
for (const auto& thread_top_seeds : top_seeds_by_thread) {
@@ -1108,7 +1270,7 @@ Action a_find_decryption_seed(
return true;
};
uint64_t seed = phosg::parallel_range<uint64_t>([&](uint64_t seed, size_t) -> bool {
uint64_t seed = phosg::parallel_range_blocks<uint64_t>([&](uint64_t seed, size_t) -> bool {
string be_decrypt_buf = ciphertext.substr(0, max_plaintext_size);
string le_decrypt_buf = ciphertext.substr(0, max_plaintext_size);
if (uses_v3_encryption(version)) {
@@ -1137,7 +1299,7 @@ Action a_find_decryption_seed(
}
return false;
},
0, 0x100000000, num_threads);
0, 0x100000000, 0x1000, num_threads);
if (seed < 0x100000000) {
phosg::log_info("Found seed %08" PRIX64, seed);
@@ -2500,7 +2662,7 @@ Action a_find_rare_enemy_seeds(
return false;
};
phosg::parallel_range<uint64_t>(thread_fn, 0, 0x100000000, num_threads, nullptr);
phosg::parallel_range_blocks<uint64_t>(thread_fn, 0, 0x100000000, 0x1000, num_threads, nullptr);
});
Action a_load_maps_test(
@@ -2639,45 +2801,65 @@ Action a_generate_dc_serial_number(
fprintf(stdout, "%s\n", serial_number.c_str());
});
Action a_generate_all_dc_serial_numbers(
"generate-all-dc-serial-numbers", "\
generate-all-dc-serial-numbers\n\
Generate all possible PSO DC serial numbers.\n",
"dc-serial-number-generator-test", nullptr,
+[](phosg::Arguments& args) {
size_t num_threads = args.get<size_t>("threads", 0);
auto serial_numbers = generate_all_dc_serial_numbers();
fprintf(stdout, "%zu (0x%zX) serial numbers found\n", serial_numbers.size(), serial_numbers.size());
for (const auto& it : serial_numbers) {
fprintf(stdout, "Valid serial number: %08" PRIX32, it.first);
for (uint8_t where : it.second) {
fprintf(stdout, " (domain=%hhu, subdomain=%hhu)",
static_cast<uint8_t>((where >> 2) & 3),
static_cast<uint8_t>(where & 3));
vector<unordered_set<uint32_t>> serial_numbers;
serial_numbers.resize(9);
DCSerialNumberIterator iter;
uint32_t serial_number;
size_t num_serial_numbers = 0;
while ((serial_number = iter.next()) != 0) {
serial_numbers[iter.domain * 3 + iter.subdomain].emplace(serial_number);
if (((++num_serial_numbers) % 0x10000) == 0) {
fprintf(stderr, "... %08zX (domain=%02hhX, subdomain=%02hhX, index2=%04hX, index3=%04hX) counts=[%zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu]\n",
num_serial_numbers, iter.domain, iter.subdomain, iter.index2, iter.index3,
serial_numbers[0].size(), serial_numbers[1].size(), serial_numbers[2].size(),
serial_numbers[3].size(), serial_numbers[4].size(), serial_numbers[5].size(),
serial_numbers[6].size(), serial_numbers[7].size(), serial_numbers[8].size());
}
fputc('\n', stdout);
}
atomic<uint64_t> num_valid_serial_numbers = 0;
array<atomic<size_t>, 9> found_counts = {0, 0, 0, 0, 0, 0, 0, 0, 0};
atomic<uint64_t> num_mismatches = 0;
mutex output_lock;
auto thread_fn = [&](uint64_t serial_number, size_t) -> bool {
for (uint8_t domain = 0; domain < 3; domain++) {
for (uint8_t subdomain = 0; subdomain < 3; subdomain++) {
if (dc_serial_number_is_valid_fast(serial_number, domain, subdomain)) {
num_valid_serial_numbers++;
bool is_valid = dc_serial_number_is_valid_fast(serial_number, domain, subdomain);
bool was_iterated = serial_numbers[domain * 3 + subdomain].count(serial_number);
if (is_valid != was_iterated) {
lock_guard g(output_lock);
fprintf(stdout, "Valid serial number: %08" PRIX64 " (domain=%hhu, subdomain=%hhu)\n", serial_number, domain, subdomain);
fprintf(stdout, "Mismatch at %08" PRIX64 " (domain=%hhu, subdomain=%hhu): is_valid=%s, was_iterated=%s\n",
serial_number, domain, subdomain, is_valid ? "true" : "false", was_iterated ? "true" : "false");
} else if (is_valid && was_iterated) {
found_counts[domain * 3 + subdomain]++;
}
}
}
return false;
};
auto progress_fn = [&](uint64_t, uint64_t, uint64_t current_value, uint64_t) -> void {
uint64_t num_found = num_valid_serial_numbers.load();
fprintf(stderr, "... %08" PRIX64 " %" PRId64 " (0x%" PRIX64 ") found\r",
current_value, num_found, num_found);
fprintf(stderr, "... %08" PRIX64 " %" PRId64 " mismatches; counts: [%zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu]\r", current_value, num_mismatches.load(),
found_counts[0].load(), serial_numbers[0].size(),
found_counts[1].load(), serial_numbers[1].size(),
found_counts[2].load(), serial_numbers[2].size(),
found_counts[3].load(), serial_numbers[3].size(),
found_counts[4].load(), serial_numbers[4].size(),
found_counts[5].load(), serial_numbers[5].size(),
found_counts[6].load(), serial_numbers[6].size(),
found_counts[7].load(), serial_numbers[7].size(),
found_counts[8].load(), serial_numbers[8].size());
};
phosg::parallel_range<uint64_t>(thread_fn, 0, 0x100000000, num_threads, progress_fn);
phosg::parallel_range_blocks<uint64_t>(thread_fn, 0, 0x100000000, 0x1000, num_threads, progress_fn);
if (num_mismatches > 0) {
throw logic_error("mismatches occurred during test");
}
});
Action a_inspect_dc_serial_number(
"inspect-dc-serial-number", "\
inspect-dc-serial-number SERIAL-NUMBER\n\
@@ -2797,7 +2979,7 @@ Action a_replay_ep3_battle_commands(
run_replay(base_seed, 0);
} else {
size_t num_threads = args.get<size_t>("threads", 0);
phosg::parallel_range<int64_t>(run_replay, 0, 0x100000000, num_threads);
phosg::parallel_range_blocks<int64_t>(run_replay, 0, 0x100000000, 0x1000, num_threads);
}
});
+2 -2
View File
@@ -166,7 +166,7 @@ string find_seed_and_decrypt_download_quest_data_section(
const void* data_section, size_t size, bool skip_checksum, bool is_ep3_trial, size_t num_threads) {
mutex result_lock;
string result;
uint64_t result_seed = phosg::parallel_range<uint64_t>([&](uint64_t seed, size_t) {
uint64_t result_seed = phosg::parallel_range_blocks<uint64_t>([&](uint64_t seed, size_t) {
try {
string ret = decrypt_download_quest_data_section<BE>(
data_section, size, seed, skip_checksum, is_ep3_trial);
@@ -177,7 +177,7 @@ string find_seed_and_decrypt_download_quest_data_section(
return false;
}
},
0, 0x100000000, num_threads);
0, 0x100000000, 0x1000, num_threads);
if (!result.empty() && (result_seed < 0x100000000)) {
static_game_data_log.info("Found seed %08" PRIX64, result_seed);
+1 -1
View File
@@ -3416,7 +3416,7 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
shared_ptr<PSOBBCharacterFile> bb_char;
switch (c->version()) {
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::Character>(data));
break;
case Version::GC_NTE:
bb_char = PSOBBCharacterFile::create_from_gc_nte(check_size_t<PSOGCNTECharacterFileCharacter>(data));
+13 -3
View File
@@ -143,6 +143,16 @@ bool PSOVMSFileHeader::checksum_correct() const {
return (crc == this->crc);
}
void PSOVMSFileHeader::check() const {
if (!this->checksum_correct()) {
throw runtime_error("VMS file unencrypted header checksum is incorrect");
}
}
bool PSOVMSFileHeader::is_v2() const {
return !memcmp(this->short_desc.data, "PSOV2", 5);
}
bool PSOGCIFileHeader::checksum_correct() const {
uint32_t cs = phosg::crc32(&this->game_name, this->game_name.bytes());
cs = phosg::crc32(&this->embedded_seed, sizeof(this->embedded_seed), cs);
@@ -543,7 +553,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile& dc) {
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile::Character& dc) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = dc.inventory;
ret->inventory.decode_from_client(Version::DC_V2);
@@ -792,10 +802,10 @@ void save_psochar(
phosg::fwritex(f.get(), empty_membership);
}
PSODCV2CharacterFile PSOBBCharacterFile::to_dc_v2() const {
PSODCV2CharacterFile::Character PSOBBCharacterFile::to_dc_v2() const {
uint8_t language = this->inventory.language;
PSODCV2CharacterFile ret;
PSODCV2CharacterFile::Character ret;
ret.inventory = this->inventory;
// We don't need to do the v1-compatible encoding (hence it is OK to pass
// nullptr here) but we do need to encode mag stats in the v2 format
+185 -124
View File
@@ -36,6 +36,12 @@ struct PSOVMSFileHeader {
/* 0080 */ // parray<parray<uint8_t, 0x200>, num_icons> icon;
bool checksum_correct() const;
void check() const;
inline size_t icon_data_size() const {
return this->num_icons * 0x200;
}
bool is_v2() const;
} __packed_ws__(PSOVMSFileHeader, 0x80);
struct PSOGCIFileHeader {
@@ -292,118 +298,139 @@ struct PSOBBBaseSystemFile : PSOBBMinimalSystemFile {
// Character files
struct PSODCNTECharacterFile {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint16_t save_count_since_last_inventory_erasure = 1;
/* 0432:0016 */ le_uint16_t inventory_erasure_count = 0;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
// TODO: Figure out how quest flags work; it's obviously different from 0x80
// bytes per difficulty like in v1. Is it just 2048 flags shared across all
// difficulties, instead of 1024 in each difficulty?
/* 0460:0044 */ parray<uint8_t, 0x100> quest_flags;
/* 0560:0144 */ le_uint16_t bank_meseta;
/* 0562:0146 */ le_uint16_t num_bank_items;
/* 0564:0148 */ parray<ItemData, 60> bank_items;
/* 0A14:05F8 */ GuildCardDCNTE guild_card;
/* 0A8F:0673 */ uint8_t unknown_s1; // Probably actually unused
/* 0A90:0674 */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 0AA0:0684 */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 0AB0:0694 */
} __packed_ws__(PSODCNTECharacterFile, 0xAB0);
/* 0000 */ le_uint32_t checksum = 0;
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint16_t save_count_since_last_inventory_erasure = 1;
/* 0432:0016 */ le_uint16_t inventory_erasure_count = 0;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
// TODO: Figure out how quest flags work; it's obviously different from 0x80
// bytes per difficulty like in v1. Is it just 2048 flags shared across all
// difficulties, instead of 1024 in each difficulty?
/* 0460:0044 */ parray<uint8_t, 0x100> quest_flags;
/* 0560:0144 */ le_uint16_t bank_meseta;
/* 0562:0146 */ le_uint16_t num_bank_items;
/* 0564:0148 */ parray<ItemData, 60> bank_items;
/* 0A14:05F8 */ GuildCardDCNTE guild_card;
/* 0A8F:0673 */ uint8_t unknown_s1; // Probably actually unused
/* 0A90:0674 */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 0AA0:0684 */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 0AB0:0694 */
} __packed_ws__(Character, 0xAB0);
/* 0004 */ Character character;
/* 0AB4 */ le_uint32_t round2_seed = 0;
/* 0AB8 */
} __packed_ws__(PSODCNTECharacterFile, 0xAB8);
struct PSODC112000CharacterFile {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint16_t save_count_since_last_inventory_erasure = 1;
/* 0432:0016 */ le_uint16_t inventory_erasure_count = 0;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
// TODO: Figure out how quest flags work; it's obviously different from 0x80
// bytes per difficulty like in v1. Is it just 2048 flags shared across all
// difficulties, instead of 1024 in each difficulty?
/* 0460:0044 */ parray<uint8_t, 0x100> quest_flags;
/* 0560:0144 */ le_uint16_t bank_meseta;
/* 0562:0146 */ le_uint16_t num_bank_items;
/* 0564:0148 */ parray<ItemData, 60> bank_items;
/* 0A14:05F8 */ GuildCardDC guild_card;
/* 0A91:0675 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0A94:0678 */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 0EB4:0A98 */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 13B4:0F98 */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 13C4:0FA8 */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 13D4:0FB8 */
} __packed_ws__(PSODC112000CharacterFile, 0x13D4);
/* 0000 */ le_uint32_t checksum = 0;
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint16_t save_count_since_last_inventory_erasure = 1;
/* 0432:0016 */ le_uint16_t inventory_erasure_count = 0;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
// TODO: Figure out how quest flags work; it's obviously different from 0x80
// bytes per difficulty like in v1. Is it just 2048 flags shared across all
// difficulties, instead of 1024 in each difficulty?
/* 0460:0044 */ parray<uint8_t, 0x100> quest_flags;
/* 0560:0144 */ le_uint16_t bank_meseta;
/* 0562:0146 */ le_uint16_t num_bank_items;
/* 0564:0148 */ parray<ItemData, 60> bank_items;
/* 0A14:05F8 */ GuildCardDC guild_card;
/* 0A91:0675 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0A94:0678 */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 0EB4:0A98 */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 13B4:0F98 */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 13C4:0FA8 */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 13D4:0FB8 */
} __packed_ws__(Character, 0x13D4);
/* 0004 */ Character character;
/* 13D8 */ le_uint32_t round2_seed = 0;
} __packed_ws__(PSODC112000CharacterFile, 0x13DC);
struct PSODCV1CharacterFile {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint32_t save_count = 1;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
/* 0460:0044 */ QuestFlagsV1 quest_flags;
/* 05E0:01C4 */ PlayerBank60 bank;
/* 0B88:076C */ GuildCardDC guild_card;
/* 0C05:07E9 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0C08:07EC */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 1028:0C0C */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 1528:110C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 1538:111C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 1548:112C */
} __packed_ws__(PSODCV1CharacterFile, 0x1548);
/* 0000 */ le_uint32_t checksum = 0;
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_uint32_t save_count = 1;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
/* 0460:0044 */ QuestFlagsV1 quest_flags;
/* 05E0:01C4 */ PlayerBank60 bank;
/* 0B88:076C */ GuildCardDC guild_card;
/* 0C05:07E9 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0C08:07EC */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 1028:0C0C */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 1528:110C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 1538:111C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 1548:112C */
} __packed_ws__(Character, 0x1548);
/* 0004 */ Character character;
/* 154C */ le_uint32_t round2_seed = 0;
} __packed_ws__(PSODCV1CharacterFile, 0x1550);
struct PSODCV2CharacterFile {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_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 */ PlayerBank60 bank;
/* 0C08:07EC */ GuildCardDC guild_card;
/* 0C85:0869 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0C88:086C */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 10A8:0C8C */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 15A8:118C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 15B8:119C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 15C8:11AC */ PlayerRecordsBattle battle_records;
/* 15E0:11C4 */ PlayerRecordsChallengeDC challenge_records;
/* 1680:1264 */ parray<le_uint16_t, 20> tech_menu_shortcut_entries;
// The Choice Search config is stored here as 32-bit integers, even though
// it's represented with 16-bit integers in the various commands that send it
// to and from the server. The order of the entries here is the same (that
// is, the first two of these ints are entries[0], the second two are
// entries[1], etc.).
/* 16A8:128C */ parray<le_uint32_t, 10> choice_search_config;
/* 16D0:12B4 */ parray<uint8_t, 4> unknown_a2;
/* 16D4:12B8 */ pstring<TextEncoding::ASCII, 0x10> v2_serial_number;
/* 16E4:12C8 */ pstring<TextEncoding::ASCII, 0x10> v2_access_key;
/* 16F4:12D8 */
} __packed_ws__(PSODCV2CharacterFile, 0x16F4);
/* 0000 */ le_uint32_t checksum = 0;
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
/* 0428:000C */ le_uint32_t play_time_seconds = 0;
/* 042C:0010 */ le_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ le_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 */ PlayerBank60 bank;
/* 0C08:07EC */ GuildCardDC guild_card;
/* 0C85:0869 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0C88:086C */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 10A8:0C8C */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 15A8:118C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 15B8:119C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 15C8:11AC */ PlayerRecordsBattle battle_records;
/* 15E0:11C4 */ PlayerRecordsChallengeDC challenge_records;
/* 1680:1264 */ parray<le_uint16_t, 20> tech_menu_shortcut_entries;
// The Choice Search config is stored here as 32-bit integers, even though
// it's represented with 16-bit integers in the various commands that send it
// to and from the server. The order of the entries here is the same (that
// is, the first two of these ints are entries[0], the second two are
// entries[1], etc.).
/* 16A8:128C */ parray<le_uint32_t, 10> choice_search_config;
/* 16D0:12B4 */ parray<uint8_t, 4> unknown_a2;
/* 16D4:12B8 */ pstring<TextEncoding::ASCII, 0x10> v2_serial_number;
/* 16E4:12C8 */ pstring<TextEncoding::ASCII, 0x10> v2_access_key;
/* 16F4:12D8 */
} __packed_ws__(Character, 0x16F4);
/* 0004 */ Character character;
/* 16F0 */ le_uint32_t round2_seed = 0;
} __packed_ws__(PSODCV2CharacterFile, 0x16FC);
struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
@@ -517,7 +544,7 @@ struct PSOGCCharacterFile {
// ------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
// A = Keyboard controls (BB; 0 = on; 1 = off). This field 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;
@@ -555,7 +582,7 @@ struct PSOGCCharacterFile {
/* 2794:2378 */ be_uint32_t unknown_f8 = 0;
/* 2798:237C */
} __packed_ws__(Character, 0x2798);
/* 00004 */ parray<Character, 7> characters;
/* 00004 */ parray<Character, 7> characters; // 0-3: main chars, 4-6: temps
/* 1152C */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
/* 1153C */ pstring<TextEncoding::ASCII, 0x10> access_key;
/* 1154C */ pstring<TextEncoding::ASCII, 0x10> password;
@@ -760,13 +787,13 @@ struct PSOBBCharacterFile {
uint8_t language,
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_dc_v2(const PSODCV2CharacterFile::Character& 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_ep3(const PSOGCEp3CharacterFile::Character& ep3);
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb);
PSODCV2CharacterFile to_dc_v2() const;
PSODCV2CharacterFile::Character to_dc_v2() const;
PSOGCNTECharacterFileCharacter to_gc_nte() const;
PSOGCCharacterFile::Character to_gc() const;
PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const;
@@ -811,11 +838,45 @@ void save_psochar(
////////////////////////////////////////////////////////////////////////////////
// Guild Card files
struct PSODCV2GuildCardFile {
// TODO: Fill this out.
/* 0000 */ parray<uint8_t, 0x330C> unknown_a1;
/* 330C */
} __packed_ws__(PSODCV2GuildCardFile, 0x330C);
struct PSODCNTEGuildCardFile {
// Note: DC NTE does not encrypt the Guild Card file
struct Entry {
/* 0000 */ GuildCardDCNTE guild_card;
/* 007B */ uint8_t unknown_a1 = 0; // Probably actually unused
/* 007C */
} __packed_ws__(Entry, 0x7C);
/* 0000 */ parray<Entry, 100> entries;
/* 3070 */
} __packed_ws__(PSODCNTEGuildCardFile, 0x3070);
struct PSODCGuildCardFileEntry {
/* 0000 */ GuildCardDC guild_card;
/* 007D */ parray<uint8_t, 3> unknown_a1 = 0; // Probably actually unused
/* 0080 */
} __packed_ws__(PSODCGuildCardFileEntry, 0x80);
struct PSODC112000GuildCardFile {
// Note: 11/2000 does not encrypt the Guild Card file
/* 0000 */ parray<PSODCGuildCardFileEntry, 100> entries;
/* 3200 */
} __packed_ws__(PSODC112000GuildCardFile, 0x3200);
struct PSODCV1V2GuildCardFile {
struct EncryptedSection {
/* 0000 */ le_uint32_t checksum = 0;
/* 0004 */ parray<PSODCGuildCardFileEntry, 100> entries;
/* 3204 */ le_int16_t music_volume = 0;
/* 3206 */ int8_t sound_volume = 0;
/* 3207 */ uint8_t language = 1;
/* 3208 */ le_uint32_t server_time_delta_frames = 540000; // 648000 on DCv1
/* 320C */ le_uint32_t creation_timestamp = 0;
/* 3210 */ le_uint32_t round2_seed = 0;
/* 3214 */
} __packed_ws__(EncryptedSection, 0x3214);
/* 0000 */ EncryptedSection encrypted_section;
/* 3214 */ parray<uint8_t, 0x100> event_flags;
/* 3314 */
} __packed_ws__(PSODCV1V2GuildCardFile, 0x3314);
struct PSOPCGuildCardFile { // PSO______GUD
/* 0000 */ le_uint32_t checksum = 0;
@@ -979,14 +1040,14 @@ std::string decrypt_fixed_size_data_section_s(
size_t size,
uint32_t round1_seed,
bool skip_checksum = false,
uint64_t override_round2_seed = 0xFFFFFFFFFFFFFFFF) {
int64_t override_round2_seed = -1) {
if (size < 2 * sizeof(U32T<BE>)) {
throw std::runtime_error("data size is too small");
}
std::string decrypted = decrypt_data_section<BE>(data_section, size, round1_seed);
uint32_t round2_seed = override_round2_seed < 0x100000000
? static_cast<uint32_t>(override_round2_seed)
uint32_t round2_seed = (override_round2_seed >= 0)
? override_round2_seed
: reinterpret_cast<const U32T<BE>*>(decrypted.data() + decrypted.size() - sizeof(U32T<BE>))->load();
PSOV2Encryption round2_crypt(round2_seed);
if (BE) {
@@ -1011,13 +1072,13 @@ std::string decrypt_fixed_size_data_section_s(
return decrypted;
}
template <typename StructT, bool BE>
template <typename StructT, bool BE, size_t ChecksumLength = sizeof(StructT)>
StructT decrypt_fixed_size_data_section_t(
const void* data_section,
size_t size,
uint32_t round1_seed,
bool skip_checksum = false,
uint64_t override_round2_seed = 0xFFFFFFFFFFFFFFFF) {
int64_t override_round2_seed = -1) {
std::string decrypted = decrypt_data_section<BE>(data_section, size, round1_seed);
if (decrypted.size() < sizeof(StructT)) {
@@ -1025,7 +1086,7 @@ StructT decrypt_fixed_size_data_section_t(
}
StructT ret = *reinterpret_cast<const StructT*>(decrypted.data());
PSOV2Encryption round2_crypt(override_round2_seed < 0x100000000 ? override_round2_seed : ret.round2_seed.load());
PSOV2Encryption round2_crypt((override_round2_seed >= 0) ? override_round2_seed : ret.round2_seed.load());
if (BE) {
round2_crypt.encrypt_big_endian(&ret, offsetof(StructT, round2_seed));
} else {
@@ -1035,7 +1096,7 @@ StructT decrypt_fixed_size_data_section_t(
if (!skip_checksum) {
uint32_t expected_crc = ret.checksum;
ret.checksum = 0;
uint32_t actual_crc = phosg::crc32(&ret, sizeof(ret));
uint32_t actual_crc = phosg::crc32(&ret, ChecksumLength);
ret.checksum = expected_crc;
if (expected_crc != actual_crc) {
throw std::runtime_error(phosg::string_printf(
@@ -1070,12 +1131,12 @@ std::string encrypt_fixed_size_data_section_s(const void* data, size_t size, uin
return encrypt_data_section<BE>(encrypted.data(), encrypted.size(), round1_seed);
}
template <typename StructT, bool BE>
template <typename StructT, bool BE, size_t ChecksumLength = sizeof(StructT)>
std::string encrypt_fixed_size_data_section_t(const StructT& s, uint32_t round1_seed) {
StructT encrypted = s;
encrypted.checksum = 0;
encrypted.round2_seed = phosg::random_object<uint32_t>();
encrypted.checksum = phosg::crc32(&encrypted, sizeof(encrypted));
encrypted.checksum = phosg::crc32(&encrypted, ChecksumLength);
PSOV2Encryption round2_crypt(encrypted.round2_seed);
if (BE) {