diff --git a/CMakeLists.txt b/CMakeLists.txt index c60315bd..5d3bc4c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ add_executable(newserv src/Client.cc src/CommonItemSet.cc src/Compression.cc + src/DCSerialNumbers.cc src/DNSServer.cc src/EnemyType.cc src/Episode3/AssistServer.cc @@ -83,7 +84,6 @@ add_executable(newserv src/PatchFileIndex.cc src/Player.cc src/PlayerSubordinates.cc - src/Product.cc src/ProxyCommands.cc src/ProxyServer.cc src/PSOEncryption.cc diff --git a/TODO.md b/TODO.md index 39067724..63635daa 100644 --- a/TODO.md +++ b/TODO.md @@ -20,6 +20,7 @@ - Make reloading happen on separate threads so compression doesn't block active clients - Try emu.cfg change instead of patch_flycast_memory (https://github.com/fuzziqersoftware/newserv/issues/132) - Try DCv2/PC crossplay +- Implement decrypt/encrypt actions for VMS files and PC save files ## Episode 3 diff --git a/src/Product.cc b/src/DCSerialNumbers.cc similarity index 98% rename from src/Product.cc rename to src/DCSerialNumbers.cc index 5eb18dd6..0ada1767 100644 --- a/src/Product.cc +++ b/src/DCSerialNumbers.cc @@ -1,4 +1,4 @@ -#include "Product.hh" +#include "DCSerialNumbers.hh" #include @@ -1163,23 +1163,23 @@ static char replace_char_reverse(char ch) { static constexpr uint64_t INVALID_PRODUCT = 0xFFFFFFFFFFFFFFFF; -static uint64_t decode_product_str(const string& s) { +static uint64_t decode_dc_serial_number_str(const string& s) { if (s.size() != 8) { return INVALID_PRODUCT; } - uint64_t product = 0; + uint64_t serial_number = 0; for (char ch : s) { char new_ch = replace_char_forward(ch); if (new_ch == '\0') { return INVALID_PRODUCT; } - product = (product << 4) | value_for_hex_char(new_ch); + serial_number = (serial_number << 4) | value_for_hex_char(new_ch); } - return product; + return serial_number; } -static uint32_t decode_product_int(uint32_t v) { +static uint32_t decode_dc_serial_number_int(uint32_t v) { return (replace_nybble_forward(v >> 28) << 28) | (replace_nybble_forward(v >> 24) << 24) | (replace_nybble_forward(v >> 20) << 20) | @@ -1190,7 +1190,7 @@ static uint32_t decode_product_int(uint32_t v) { (replace_nybble_forward(v)); } -static uint32_t encode_product_int(uint32_t v) { +static uint32_t encode_dc_serial_number_int(uint32_t v) { return (replace_nybble_reverse(v >> 28) << 28) | (replace_nybble_reverse(v >> 24) << 24) | (replace_nybble_reverse(v >> 20) << 20) | @@ -1215,9 +1215,9 @@ static pair compute_offset1_and_limit1( } } -bool product_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) { - uint64_t product = decode_product_str(s); - if (product == INVALID_PRODUCT) { +bool dc_serial_number_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) { + uint64_t serial_number = decode_dc_serial_number_str(s); + if (serial_number == INVALID_PRODUCT) { return false; } @@ -1229,7 +1229,7 @@ bool product_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) { 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++) { - if (primes1[offset1] * primes2[offset2] * primes3[offset3] == product) { + if (primes1[offset1] * primes2[offset2] * primes3[offset3] == serial_number) { return true; } } @@ -1238,14 +1238,14 @@ bool product_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) { return false; } -bool decoded_product_is_valid_fast(uint32_t product, uint8_t domain, uint8_t subdomain) { +bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t domain, uint8_t subdomain) { auto [offset1_start, limit1] = compute_offset1_and_limit1(domain, subdomain); if (limit1 == 0) { return false; } for (uint64_t prefix = 0; prefix < 0x000000E800000000; prefix += 0x0000000100000000) { - uint64_t sub0 = product | prefix; + uint64_t sub0 = serial_number | prefix; for (size_t offset1 = offset1_start; offset1 < limit1; offset1++) { if (sub0 % primes1[offset1]) { continue; @@ -1264,19 +1264,19 @@ bool decoded_product_is_valid_fast(uint32_t product, uint8_t domain, uint8_t sub return false; } -bool product_is_valid_fast(const string& s, uint8_t domain, uint8_t subdomain) { - uint64_t product = decode_product_str(s); - if (product == INVALID_PRODUCT) { +bool dc_serial_number_is_valid_fast(const string& s, uint8_t domain, uint8_t subdomain) { + uint64_t serial_number = decode_dc_serial_number_str(s); + if (serial_number == INVALID_PRODUCT) { return false; } - return decoded_product_is_valid_fast(product, domain, subdomain); + return decoded_dc_serial_number_is_valid_fast(serial_number, domain, subdomain); } -bool product_is_valid_fast(uint32_t product, uint8_t domain, uint8_t subdomain) { - return decoded_product_is_valid_fast(decode_product_int(product), domain, subdomain); +bool dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t domain, uint8_t subdomain) { + return decoded_dc_serial_number_is_valid_fast(decode_dc_serial_number_int(serial_number), domain, subdomain); } -string generate_product(uint8_t domain, uint8_t subdomain) { +string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) { size_t offset1, limit1; if (domain == 0) { offset1 = 0x00; @@ -1305,7 +1305,7 @@ string generate_product(uint8_t domain, uint8_t subdomain) { return ret; } -unordered_map generate_all_products(uint8_t domain, uint8_t subdomain) { +unordered_map generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) { vector domains; if (domain == 0xFF) { domains.emplace_back(0x00); @@ -1345,16 +1345,16 @@ unordered_map generate_all_products(uint8_t domain, uint8_t su 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_product_int(value)].push_back(((domain << 2) & 3) | (subdomain & 3)); + ret[encode_dc_serial_number_int(value)].push_back(((domain << 2) & 3) | (subdomain & 3)); } - fprintf(stderr, "... domain=%hhu subdomain=%hhu index2=%zu products=%zu (0x%zX)\n", domain, subdomain, index2, ret.size(), ret.size()); + fprintf(stderr, "... domain=%hhu subdomain=%hhu index2=%zu results=%zu (0x%zX)\n", domain, subdomain, index2, ret.size(), ret.size()); } } } return ret; } -void product_speed_test(uint64_t seed) { +void dc_serial_number_speed_test(uint64_t seed) { uint32_t effective_seed = (seed & 0xFFFFFFFF00000000) ? random_object() : seed; fprintf(stderr, "Product speed test with seed=%08" PRIX32 "\n", effective_seed); PSOV2Encryption crypt(effective_seed); @@ -1366,11 +1366,11 @@ void product_speed_test(uint64_t seed) { string s = string_printf("%08X", crypt.next()); uint64_t start = now(); - bool is_valid_fast = product_is_valid_fast(s, 1, 0xFF); + bool is_valid_fast = dc_serial_number_is_valid_fast(s, 1, 0xFF); time_fast += now() - start; start = now(); - bool is_valid_slow = product_is_valid_slow(s, 1, 0xFF); + bool is_valid_slow = dc_serial_number_is_valid_slow(s, 1, 0xFF); time_slow += now() - start; if (((z & 0xF) == 0) || is_valid_slow || is_valid_fast) { @@ -1381,8 +1381,8 @@ void product_speed_test(uint64_t seed) { } } - fprintf(stderr, "Total time (slow): %" PRId64 " usecs (%" PRIu64 " per product)\n", time_slow, time_slow / count); - fprintf(stderr, "Total time (fast): %" PRId64 " usecs (%" PRIu64 " per product)\n", time_fast, time_fast / count); + fprintf(stderr, "Total time (slow): %" PRId64 " usecs (%" PRIu64 " per serial number)\n", time_slow, time_slow / count); + fprintf(stderr, "Total time (fast): %" PRId64 " usecs (%" PRIu64 " per serial number)\n", time_fast, time_fast / count); fprintf(stderr, "Fast vs. slow speedup: %zux\n", static_cast(time_slow / time_fast)); fprintf(stderr, "Disagreements: %zu\n", num_disagreements); } diff --git a/src/DCSerialNumbers.hh b/src/DCSerialNumbers.hh new file mode 100644 index 00000000..1a619cf2 --- /dev/null +++ b/src/DCSerialNumbers.hh @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include +#include + +// dc_serial_number_is_valid_slow is Sega's implementation; +// dc_serial_number_is_valid_fast produces identical results but is between 3000 +// and 7500 times faster, depending on the compiler's optimization level. +bool dc_serial_number_is_valid_slow( + const std::string& s, uint8_t domain, uint8_t subdomain = 0xFF); +bool dc_serial_number_is_valid_fast( + const std::string& s, uint8_t domain, uint8_t subdomain = 0xFF); +bool dc_serial_number_is_valid_fast( + uint32_t serial_number, uint8_t domain, uint8_t subdomain = 0xFF); +bool decoded_dc_serial_number_is_valid_fast( + uint32_t serial_number, uint8_t domain, uint8_t subdomain = 0xFF); + +std::string generate_dc_serial_number(uint8_t domain, uint8_t subdomain = 0xFF); +std::unordered_map generate_all_dc_serial_numbers(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF); + +void dc_serial_number_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF); diff --git a/src/Main.cc b/src/Main.cc index c2b5227a..dc29d740 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -17,6 +17,7 @@ #include "BMLArchive.hh" #include "CatSession.hh" #include "Compression.hh" +#include "DCSerialNumbers.hh" #include "DNSServer.hh" #include "GSLArchive.hh" #include "GVMEncoder.hh" @@ -24,7 +25,6 @@ #include "Loggers.hh" #include "NetworkAddresses.hh" #include "PSOGCObjectGraph.hh" -#include "Product.hh" #include "ProxyServer.hh" #include "Quest.hh" #include "QuestScript.hh" @@ -244,6 +244,17 @@ The actions are:\n\ convert-itemrt-rel-to-json [INPUT-FILENAME [OUTPUT-FILENAME]]\n\ Convert a REL rare table to a JSON rare item set. The resulting JSON has\n\ the same structure as system/blueburst/rare-table.json.\n\ + generate-dc-serial-number [--domain=DOMAIN] [--subdomain=SUBDOMAIN]\n\ + Generate a PSO DC serial number. DOMAIN should be 0 for DCv1 or 1 for DCv2;\n\ + SUBDOMAIN should be 0 for Japanese, 1 for USA, or 2 for Europe.\n\ + generate-all-dc-serial-numbers\n\ + Generate all possible PSO DC serial numbers.\n\ + inspect-dc-serial-number SERIAL-NUMBER\n\ + Show which domain and subdomain the serial number belongs to. (As with\n\ + generate-dc-serial-number, described above, this will tell you which PSO\n\ + version it is valid for.)\n\ + dc-serial-number-speed-test\n\ + Run a speed test of the two DC serial number validation functions.\n\ \n\ A few options apply to multiple modes described above:\n\ --parse-data\n\ @@ -300,10 +311,10 @@ enum class Behavior { PARSE_OBJECT_GRAPH, REPLAY_LOG, CAT_CLIENT, - GENERATE_PRODUCT, - GENERATE_ALL_PRODUCTS, - INSPECT_PRODUCT, - PRODUCT_SPEED_TEST, + GENERATE_DC_SERIAL_NUMBER, + GENERATE_ALL_DC_SERIAL_NUMBERS, + INSPECT_DC_SERIAL_NUMBER, + DC_SERIAL_NUMBER_SPEED_TEST, }; static bool behavior_takes_input_filename(Behavior b) { @@ -346,7 +357,7 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::PARSE_OBJECT_GRAPH) || (b == Behavior::REPLAY_LOG) || (b == Behavior::CAT_CLIENT) || - (b == Behavior::INSPECT_PRODUCT); + (b == Behavior::INSPECT_DC_SERIAL_NUMBER); } static bool behavior_takes_output_filename(Behavior b) { @@ -604,14 +615,14 @@ int main(int argc, char** argv) { behavior = Behavior::EXTRACT_GSL; } else if (!strcmp(argv[x], "extract-bml")) { behavior = Behavior::EXTRACT_BML; - } else if (!strcmp(argv[x], "generate-product")) { - behavior = Behavior::GENERATE_PRODUCT; - } else if (!strcmp(argv[x], "generate-all-products")) { - behavior = Behavior::GENERATE_ALL_PRODUCTS; - } else if (!strcmp(argv[x], "inspect-product")) { - behavior = Behavior::INSPECT_PRODUCT; - } else if (!strcmp(argv[x], "product-speed-test")) { - behavior = Behavior::PRODUCT_SPEED_TEST; + } else if (!strcmp(argv[x], "generate-dc-serial-number")) { + behavior = Behavior::GENERATE_DC_SERIAL_NUMBER; + } else if (!strcmp(argv[x], "generate-all-dc-serial-numbers")) { + behavior = Behavior::GENERATE_ALL_DC_SERIAL_NUMBERS; + } else if (!strcmp(argv[x], "inspect-dc-serial-number")) { + behavior = Behavior::INSPECT_DC_SERIAL_NUMBER; + } else if (!strcmp(argv[x], "dc-serial-number-speed-test")) { + behavior = Behavior::DC_SERIAL_NUMBER_SPEED_TEST; } else { throw invalid_argument(string_printf("unknown command: %s (try --help)", argv[x])); } @@ -1791,17 +1802,17 @@ int main(int argc, char** argv) { break; } - case Behavior::GENERATE_PRODUCT: { - auto product = generate_product(domain, subdomain); - fprintf(stdout, "%s\n", product.c_str()); + case Behavior::GENERATE_DC_SERIAL_NUMBER: { + string serial_number = generate_dc_serial_number(domain, subdomain); + fprintf(stdout, "%s\n", serial_number.c_str()); break; } - case Behavior::GENERATE_ALL_PRODUCTS: { - auto products = generate_all_products(); - fprintf(stdout, "%zu (0x%zX) products found\n", products.size(), products.size()); - for (const auto& it : products) { - fprintf(stdout, "Valid product: %08" PRIX32, it.first); + case Behavior::GENERATE_ALL_DC_SERIAL_NUMBERS: { + 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((where >> 2) & 3), @@ -1810,22 +1821,22 @@ int main(int argc, char** argv) { fputc('\n', stdout); } - atomic num_valid_products = 0; + atomic num_valid_serial_numbers = 0; mutex output_lock; - auto thread_fn = [&](uint64_t product, size_t) -> bool { + 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 (product_is_valid_fast(product, domain, subdomain)) { - num_valid_products++; + if (dc_serial_number_is_valid_fast(serial_number, domain, subdomain)) { + num_valid_serial_numbers++; lock_guard g(output_lock); - fprintf(stdout, "Valid product: %08" PRIX64 " (domain=%hhu, subdomain=%hhu)\n", product, domain, subdomain); + fprintf(stdout, "Valid serial number: %08" PRIX64 " (domain=%hhu, subdomain=%hhu)\n", serial_number, domain, subdomain); } } } return false; }; auto progress_fn = [&](uint64_t, uint64_t, uint64_t current_value, uint64_t) -> void { - uint64_t num_found = num_valid_products.load(); + uint64_t num_found = num_valid_serial_numbers.load(); fprintf(stderr, "... %08" PRIX64 " %" PRId64 " (0x%" PRIX64 ") found\r", current_value, num_found, num_found); }; @@ -1833,14 +1844,14 @@ int main(int argc, char** argv) { break; } - case Behavior::INSPECT_PRODUCT: { + case Behavior::INSPECT_DC_SERIAL_NUMBER: { if (!input_filename) { - throw invalid_argument("no product given"); + throw invalid_argument("no serial number given"); } size_t num_valid_subdomains = 0; for (uint8_t domain = 0; domain < 3; domain++) { for (uint8_t subdomain = 0; subdomain < 3; subdomain++) { - if (product_is_valid_fast(input_filename, domain, subdomain)) { + if (dc_serial_number_is_valid_fast(input_filename, domain, subdomain)) { fprintf(stdout, "%s is valid in domain %hhu subdomain %hhu\n", input_filename, domain, subdomain); num_valid_subdomains++; } @@ -1852,11 +1863,11 @@ int main(int argc, char** argv) { break; } - case Behavior::PRODUCT_SPEED_TEST: + case Behavior::DC_SERIAL_NUMBER_SPEED_TEST: if (seed.empty()) { - product_speed_test(); + dc_serial_number_speed_test(); } else { - product_speed_test(stoul(seed, nullptr, 16)); + dc_serial_number_speed_test(stoul(seed, nullptr, 16)); } break; diff --git a/src/Product.hh b/src/Product.hh deleted file mode 100644 index 78763113..00000000 --- a/src/Product.hh +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include -#include - -// product_is_valid_slow is Sega's implementation; product_is_valid_fast -// produces identical results but is about 7000 times faster. -bool product_is_valid_slow( - const std::string& s, uint8_t domain, uint8_t subdomain = 0xFF); -bool product_is_valid_fast( - const std::string& s, uint8_t domain, uint8_t subdomain = 0xFF); -bool product_is_valid_fast( - uint32_t product, uint8_t domain, uint8_t subdomain = 0xFF); -bool decoded_product_is_valid_fast( - uint32_t product, uint8_t domain, uint8_t subdomain = 0xFF); - -std::string generate_product(uint8_t domain, uint8_t subdomain = 0xFF); -std::unordered_map generate_all_products(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF); - -void product_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF);