rename and document DC serial number functions

This commit is contained in:
Martin Michelsen
2023-10-05 22:41:30 -07:00
parent 532bcab0b6
commit cdb3943d9f
6 changed files with 98 additions and 85 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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
+28 -28
View File
@@ -1,4 +1,4 @@
#include "Product.hh"
#include "DCSerialNumbers.hh"
#include <stdint.h>
@@ -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<size_t, size_t> 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<uint32_t, string> generate_all_products(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);
@@ -1345,16 +1345,16 @@ unordered_map<uint32_t, string> 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<uint32_t>() : 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<size_t>(time_slow / time_fast));
fprintf(stderr, "Disagreements: %zu\n", num_disagreements);
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
#include <string>
#include <unordered_map>
// 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<uint32_t, std::string> generate_all_dc_serial_numbers(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF);
void dc_serial_number_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF);
+45 -34
View File
@@ -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<uint8_t>((where >> 2) & 3),
@@ -1810,22 +1821,22 @@ int main(int argc, char** argv) {
fputc('\n', stdout);
}
atomic<uint64_t> num_valid_products = 0;
atomic<uint64_t> 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;
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include <stdint.h>
#include <string>
#include <unordered_map>
// 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<uint32_t, std::string> generate_all_products(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF);
void product_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF);