From c95b158e4ef31398ebba1bae772688d7195f2cb3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 20 Apr 2024 10:08:45 -0700 Subject: [PATCH] add decrypt/encrypt for simple DCv2 executable encryption --- src/DCSerialNumbers.cc | 32 +++++++++++++++++++++++++++++ src/DCSerialNumbers.hh | 2 ++ src/Main.cc | 46 +++++++++++++++++++++++++++++++----------- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/DCSerialNumbers.cc b/src/DCSerialNumbers.cc index 4610cb59..c0fa5ee4 100644 --- a/src/DCSerialNumbers.cc +++ b/src/DCSerialNumbers.cc @@ -1425,3 +1425,35 @@ EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const ret.indexes.at(fixup_steps_offset) = 0; return ret; } + +std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_key) { + if (data.size() & 3) { + throw runtime_error("size is not a multiple of 4"); + } + + StringReader r(data); + if (mask_key < 0) { + unordered_map key_freq; + while (!r.eof()) { + key_freq[r.get_u32l()] += 1; + } + size_t max_v = 0; + for (const auto& it : key_freq) { + if (it.second > max_v) { + max_v = it.second; + mask_key = it.first; + } + } + if (mask_key < 0) { + throw runtime_error("cannot determine mask key"); + } + log_info("Determined %08" PRIX64 " to be the most likely mask key", mask_key); + r.go(0); + } + + StringWriter w; + while (!r.eof()) { + w.put_u32l(r.get_u32l() ^ mask_key); + } + return std::move(w.str()); +} diff --git a/src/DCSerialNumbers.hh b/src/DCSerialNumbers.hh index 4bbef752..07b94a47 100644 --- a/src/DCSerialNumbers.hh +++ b/src/DCSerialNumbers.hh @@ -32,3 +32,5 @@ std::string decrypt_dp_address_jpn( const std::string& iwashi_sea_data, const std::string& katsuo_sea_data); EncryptedDCv2Executables encrypt_dp_address_jpn(const std::string& executable, const std::string& indexes); + +std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t seed = -1); diff --git a/src/Main.cc b/src/Main.cc index c347c543..41954665 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -757,35 +757,57 @@ Action a_encrypt_save_data("encrypt-save-data", nullptr, a_encrypt_decrypt_save_ Action a_decrypt_dcv2_executable( "decrypt-dcv2-executable", "\ decrypt-dcv2-executable --executable=EXEC --indexes=INDEXES --values=VALUES\n\ + decrypt-dcv2-executable --executable=EXEC --simple [--seed=SEED]\n\ Decrypt a PSO DC v2 executable file. EXEC should be the path to the\n\ executable (DP_ADDRESS.JPN), INDEXES should be the path to the index fixup\n\ table (KATSUO.SEA), and VALUES should be the path to the value fixup table\n\ - (IWASHI.SEA). The output is written to EXEC.dec.\n", + (IWASHI.SEA). The output is written to EXEC.dec.\n\ + If --simple is given, uses the simpler encryption method used in some\n\ + community modifications of the game. In this case, --seed is not required;\n\ + if not given, finds the seed automatically, and prints it to stderr so you\n\ + will be able to use it when re-encrypting.", +[](Arguments& args) { string executable_filename = args.get("executable", true); - string values_filename = args.get("values", true); - string indexes_filename = args.get("indexes", true); string executable_data = load_file(executable_filename); - string values_data = load_file(values_filename); - string indexes_data = load_file(indexes_filename); - string decrypted = decrypt_dp_address_jpn(executable_data, values_data, indexes_data); + string decrypted; + if (args.get("simple")) { + string seed_str = args.get("seed"); + int64_t seed = seed_str.empty() ? -1 : stoull(seed_str, nullptr, 16); + decrypted = crypt_dp_address_jpn_simple(executable_data, seed); + } else { + string values_filename = args.get("values", true); + string indexes_filename = args.get("indexes", true); + string values_data = load_file(values_filename); + string indexes_data = load_file(indexes_filename); + decrypted = decrypt_dp_address_jpn(executable_data, values_data, indexes_data); + } save_file(executable_filename + ".dec", decrypted); }); Action a_encrypt_dcv2_executable( "encrypt-dcv2-executable", "\ decrypt-dcv2-executable --executable=EXEC --indexes=INDEXES\n\ + decrypt-dcv2-executable --executable=EXEC --simple --seed=SEED\n\ Encrypt a PSO DC v2 executable file. EXEC should be the path to the\n\ executable (DP_ADDRESS.JPN) and INDEXES should be the path to the index\n\ fixup table (KATSUO.SEA). The output is written to EXEC.enc and\n\ - INDEXES.enc.", + INDEXES.enc.\n\ + If --simple is given, uses the simpler encryption method used in some\n\ + community modifications of the game. In this case, --seed is required.", +[](Arguments& args) { string executable_filename = args.get("executable", true); - string indexes_filename = args.get("indexes", true); string executable_data = load_file(executable_filename); - string indexes_data = load_file(indexes_filename); - auto encrypted = encrypt_dp_address_jpn(executable_data, indexes_data); - save_file(executable_filename + ".enc", encrypted.executable); - save_file(indexes_filename + ".enc", encrypted.indexes); + string encrypted_executable; + if (args.get("simple")) { + int64_t seed = stoull(args.get("seed", true), nullptr, 16); + encrypted_executable = crypt_dp_address_jpn_simple(executable_data, seed); + } else { + string indexes_filename = args.get("indexes", true); + string indexes_data = load_file(indexes_filename); + auto encrypted = encrypt_dp_address_jpn(executable_data, indexes_data); + save_file(indexes_filename + ".enc", encrypted.indexes); + encrypted_executable = std::move(encrypted.executable); + } + save_file(executable_filename + ".enc", encrypted_executable); }); Action a_decode_gci_snapshot(