From 73a68911e86de57622cc4b1e7c77b6f0919cf6fa Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 9 Jul 2023 21:04:36 -0700 Subject: [PATCH] document challenge mode time encoding --- src/PSOEncryption.cc | 33 +++++++++++++++++++++++++++++++++ src/PSOEncryption.hh | 3 +++ src/Player.hh | 16 ++++++++-------- src/QuestScript.cc | 2 +- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/PSOEncryption.cc b/src/PSOEncryption.cc index 92c5a191..d6cc2b03 100644 --- a/src/PSOEncryption.cc +++ b/src/PSOEncryption.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -886,6 +887,38 @@ void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis) { } } +static uint8_t count_one_bits(uint16_t v) { + uint8_t ret = 0; + while (v) { + v &= (v - 1); + ret++; + } + return ret; +} + +uint32_t encrypt_challenge_time(uint16_t value) { + vector available_bits({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); + + uint16_t mask = 0; + uint8_t num_one_bits = (random_object() % 9) + 4; // Range [4, 12] + for (; num_one_bits; num_one_bits--) { + uint8_t index = random_object() % available_bits.size(); + auto it = available_bits.begin() + index; + mask |= (1 << *it); + available_bits.erase(it); + } + + return (mask << 16) | (value ^ mask); +} + +uint16_t decrypt_challenge_time(uint32_t value) { + uint16_t mask = (value >> 0x10); + uint8_t mask_one_bits = count_one_bits(mask); + return ((mask_one_bits < 4) || (mask_one_bits > 12)) + ? 0xFFFF + : ((mask ^ value) & 0xFFFF); +} + template StringT crypt_challenge_rank_text(const void* src, size_t count) { if (count == 0) { diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index cd14fb43..66141f89 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -241,6 +241,9 @@ private: void decrypt_trivial_gci_data(void* data, size_t size, uint8_t basis); +uint32_t encrypt_challenge_time(uint16_t value); +uint16_t decrypt_challenge_time(uint32_t value); + std::string decrypt_challenge_rank_text(const char* data, size_t count); std::string decrypt_challenge_rank_text(const std::string& data); std::string encrypt_challenge_rank_text(const char* data, size_t count); diff --git a/src/Player.hh b/src/Player.hh index 4f5bc0f3..966b4390 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -333,13 +333,13 @@ struct PlayerRecordsDCPC_Challenge { /* 00 */ le_uint16_t title_color = 0x7FFF; /* 02 */ parray unknown_u0; /* 04 */ ptext rank_title; // Encrypted; see decrypt_challenge_rank_text - /* 10 */ parray times_ep1_online; // TODO: This might be offline times + /* 10 */ parray times_ep1_online; // Encrypted; see decrypt_challenge_time. TODO: This might be offline times /* 34 */ le_uint16_t unknown_g3 = 0; /* 36 */ le_uint16_t grave_deaths = 0; /* 38 */ parray grave_coords_time; /* 4C */ ptext grave_team; /* 60 */ ptext grave_message; - /* 78 */ parray times_ep1_offline; // TODO: This might be online times + /* 78 */ parray times_ep1_offline; // Encrypted; see decrypt_challenge_time. TODO: This might be online times /* 9C */ parray unknown_l4; /* A0 */ } __attribute__((packed)); @@ -359,9 +359,9 @@ struct PlayerRecordsV3_Challenge { // of save file structure /* 0000:001C */ U16T title_color = 0x7FFF; // XRGB1555 /* 0002:001E */ parray unknown_u0; - /* 0004:0020 */ parray times_ep1_online; - /* 0028:0044 */ parray times_ep2_online; - /* 003C:0058 */ parray times_ep1_offline; + /* 0004:0020 */ parray times_ep1_online; // Encrypted; see decrypt_challenge_time + /* 0028:0044 */ parray times_ep2_online; // Encrypted; see decrypt_challenge_time + /* 003C:0058 */ parray times_ep1_offline; // Encrypted; see decrypt_challenge_time /* 0060:007C */ parray unknown_g3; /* 0064:0080 */ U16T grave_deaths = 0; /* 0066:0082 */ parray unknown_u4; @@ -378,9 +378,9 @@ struct PlayerRecordsV3_Challenge { struct PlayerRecordsBB_Challenge { /* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555 /* 0002 */ parray unknown_u0; - /* 0004 */ parray times_ep1_online; - /* 0028 */ parray times_ep2_online; - /* 003C */ parray times_ep1_offline; + /* 0004 */ parray times_ep1_online; // Encrypted; see decrypt_challenge_time + /* 0028 */ parray times_ep2_online; // Encrypted; see decrypt_challenge_time + /* 003C */ parray times_ep1_offline; // Encrypted; see decrypt_challenge_time /* 0060 */ parray unknown_g3; /* 0064 */ le_uint16_t grave_deaths = 0; /* 0066 */ parray unknown_u4; diff --git a/src/QuestScript.cc b/src/QuestScript.cc index edfc0a81..8b5d415f 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -725,7 +725,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF8B5, "write4", {REG, REG}, {}, V2, V2}, {0xF8B5, "write4", {}, {INT32, REG}, V3, V4}, {0xF8B6, "check_for_hacking", {REG}, {}, V2, V2}, // Returns a bitmask of 5 different types of detectable hacking. But it only works on DCv2 - it crashes on all other versions. - {0xF8B7, nullptr, {REG}, {}, V2, V4}, // TODO (DX) - Challenge mode. Appears to be timing-related; regA is expected to be in [60, 3600]. Encodes the value with some strange masking key, even though it's never sent over the network and is only decoded locally. + {0xF8B7, nullptr, {REG}, {}, V2, V4}, // TODO (DX) - Challenge mode. Appears to be timing-related; regA is expected to be in [60, 3600]. Encodes the value with encrypt_challenge_time even though it's never sent over the network and is only decrypted locally. {0xF8B8, "disable_retry_menu", {}, {}, V2, V4}, {0xF8B9, "chl_recovery", {}, {}, V2, V4}, {0xF8BA, "load_guild_card_file_creation_time_to_flag_buf", {}, {}, V2, V4},