diff --git a/Source/Core/Core/ARDecrypt.cpp b/Source/Core/Core/ARDecrypt.cpp index 98f49cfc411..b94b36de5a4 100644 --- a/Source/Core/Core/ARDecrypt.cpp +++ b/Source/Core/Core/ARDecrypt.cpp @@ -7,11 +7,13 @@ #include "Core/ARDecrypt.h" -#include #include #include +#include #include +#include #include +#include #include #ifdef _WIN32 @@ -209,45 +211,43 @@ constexpr Seeds genseeds = [] { return seeds; }(); -static void getcode(const u32* src, u32* addr, u32* val) +static std::pair GetCode(const u32* src) { - *addr = Common::swap32(src[0]); - *val = Common::swap32(src[1]); + return {Common::swap32(src[0]), Common::swap32(src[1])}; } -static void setcode(u32* dst, u32 addr, u32 val) +static void SetCode(u32* dst, u32 addr, u32 val) { dst[0] = Common::swap32(addr); dst[1] = Common::swap32(val); } -static u16 gencrc16(const u32* codes, u16 size) +// This looks like a nibble-wise CRC-16/KERMIT. +static constexpr u16 GetCRC16(std::span codes) { u16 ret = 0; - if (size > 0) + for (u32 code : codes) { - for (u8 tmp = 0; tmp < size; ++tmp) + for (u32 i = 0; i != 4; ++i) { - for (int i = 0; i < 4; ++i) - { - u8 tmp2 = ((codes[tmp] >> (i << 3)) ^ ret); - ret = ((crctable0[(tmp2 >> 4) & 0x0F] ^ crctable1[tmp2 & 0x0F]) ^ (ret >> 8)); - } + const u8 tmp = ((code >> (i << 3)) ^ ret); + ret = ((crctable0[(tmp >> 4) & 0x0F] ^ crctable1[tmp & 0x0F]) ^ (ret >> 8)); } } + return ret; } -static u8 verifycode(const u32* codes, u16 size) +static constexpr u8 VerifyCode(std::span codes) { - u16 tmp = gencrc16(codes, size); + const u16 tmp = GetCRC16(codes); return (((tmp >> 12) ^ (tmp >> 8) ^ (tmp >> 4) ^ tmp) & 0x0F); } -static void unscramble1(u32* addr, u32* val) +static void Unscramble1(u32* addr, u32* val) { - u32 tmp; + u32 tmp = 0; *val = std::rotl(*val, 4); @@ -272,9 +272,9 @@ static void unscramble1(u32* addr, u32* val) *val ^= tmp; } -static void unscramble2(u32* addr, u32* val) +static void Unscramble2(u32* addr, u32* val) { - u32 tmp; + u32 tmp = 0; *val = std::rotr(*val, 1); @@ -299,18 +299,15 @@ static void unscramble2(u32* addr, u32* val) *addr = std::rotr((*addr ^ tmp), 4); } -static void decryptcode(const u32* seeds, u32* code) +static void DecryptCode(const Seeds& seeds, u32* code) { - u32 addr, val; - u32 tmp, tmp2; - int i = 0; + auto [addr, val] = GetCode(code); + Unscramble1(&addr, &val); - getcode(code, &addr, &val); - unscramble1(&addr, &val); - while (i < 32) + for (u32 i = 0; i < 32;) { - tmp = (std::rotr(val, 4) ^ seeds[i++]); - tmp2 = (val ^ seeds[i++]); + u32 tmp = (std::rotr(val, 4) ^ seeds[i++]); + u32 tmp2 = (val ^ seeds[i++]); addr ^= (table6[tmp & 0x3F] ^ table4[(tmp >> 8) & 0x3F] ^ table2[(tmp >> 16) & 0x3F] ^ table0[(tmp >> 24) & 0x3F] ^ table7[tmp2 & 0x3F] ^ table5[(tmp2 >> 8) & 0x3F] ^ table3[(tmp2 >> 16) & 0x3F] ^ table1[(tmp2 >> 24) & 0x3F]); @@ -321,16 +318,16 @@ static void decryptcode(const u32* seeds, u32* code) table0[(tmp >> 24) & 0x3F] ^ table7[tmp2 & 0x3F] ^ table5[(tmp2 >> 8) & 0x3F] ^ table3[(tmp2 >> 16) & 0x3F] ^ table1[(tmp2 >> 24) & 0x3F]); } - unscramble2(&addr, &val); - setcode(code, val, addr); + Unscramble2(&addr, &val); + SetCode(code, val, addr); } -static bool getbitstring(u32* ctrl, u32* out, u8 len) +static bool GetBitString(u32* ctrl, u32* out, u8 len) { u32 tmp = (ctrl[0] + (ctrl[1] << 2)); *out = 0; - while (len--) + while (len-- != 0) { if (ctrl[2] > 0x1F) { @@ -348,155 +345,139 @@ static bool getbitstring(u32* ctrl, u32* out, u8 len) return true; } -static bool batchdecrypt(u32* codes, u16 size) +static std::optional BatchDecrypt(std::span codes) { - u32* ptr = codes; - std::array tmparray{}; + const auto size = u32(codes.size()); + + assert((size & 1) == 0); + assert(size != 0); + + for (u32 i = 0; i < size; i += 2) + DecryptCode(genseeds, codes.data() + i); + + const u32 tmp = codes[0]; + codes[0] &= 0x0FFFFFFF; + if ((tmp >> 28) != VerifyCode(codes)) + return std::nullopt; + + std::array tmparray = { + codes[0], + 0, + 4, // Skip crc + size, + }; + std::array tmparray2{}; - - // Not required - // if (size & 1) return 0; - // if (!size) return 0; - - u32 tmp = (size >> 1); - while (tmp--) - { - decryptcode(genseeds.data(), ptr); - ptr += 2; - } - - tmparray[0] = *codes; - tmparray[1] = 0; - tmparray[2] = 4; // Skip crc - tmparray[3] = size; - getbitstring(tmparray.data(), &tmparray2[1], 11); // Game id - getbitstring(tmparray.data(), &tmparray2[2], 17); // Code id - getbitstring(tmparray.data(), &tmparray2[3], 1); // Master code - getbitstring(tmparray.data(), &tmparray2[4], 1); // Unknown - getbitstring(tmparray.data(), &tmparray2[5], 2); // Region + GetBitString(tmparray.data(), &tmparray2[1], 11); // Game id + GetBitString(tmparray.data(), &tmparray2[2], 17); // Code id + GetBitString(tmparray.data(), &tmparray2[3], 1); // Master code + GetBitString(tmparray.data(), &tmparray2[4], 1); // Unknown + GetBitString(tmparray.data(), &tmparray2[5], 2); // Region // Grab gameid and region from the last decrypted code // TODO: Maybe check this against Dolphin's GameID? - "code is for wrong game" type msg - // gameid = tmparray2[1]; - // region = tmparray2[5]; - - tmp = codes[0]; - codes[0] &= 0x0FFFFFFF; - if ((tmp >> 28) != verifycode(codes, size)) - { - return false; - } - - return true; + return GameIDAndRegion{tmparray2[1], tmparray2[5]}; // Unfinished (so says Parasyte :p ) } -static int GetVal(const char* flt, char chr) +static u32 GetVal(char chr) { - int ret = (int)(strchr(flt, chr) - flt); + const auto ret = u32(strchr(filter, Common::ToUpper(chr)) - filter); switch (ret) { case 32: // 'I' case 33: // 'L' - ret = 1; + return 1; break; case 34: // 'O' - ret = 0; + return 0; break; case 35: // 'S' - ret = 5; + return 5; break; + default: + return ret; } - return ret; } -static int alphatobin(u32* dst, const std::vector& alpha, int size) +// Returns a vector of converted lines or an index where conversion failed. +static std::expected, std::size_t> +ConvertAlphaToBinary(const std::span& alpha) { - int j = 0; - int ret = 0; - int org = size + 1; - u32 bin[2]; - u8 parity; + std::vector result; + result.reserve(alpha.size() * 2); - for (; size; --size) + std::size_t current_index = 0; + for (const auto& line : alpha) { - bin[0] = 0; - for (int i = 0; i < 6; i++) - { - bin[0] |= (GetVal(filter, alpha[j >> 1][i]) << (((5 - i) * 5) + 2)); - } - bin[0] |= (GetVal(filter, alpha[j >> 1][6]) >> 3); - dst[j++] = bin[0]; + assert(line.size() == 13); - bin[1] = 0; - for (int i = 0; i < 6; i++) + u32 value_a = GetVal(line[6]) >> 3; + u32 value_b = GetVal(line[12]) >> 1; + for (u32 i = 0; i != 6; ++i) { - bin[1] |= (GetVal(filter, alpha[j >> 1][i + 6]) << (((5 - i) * 5) + 4)); + value_a |= (GetVal(line[i]) << (((5 - i) * 5) + 2)); + value_b |= (GetVal(line[i + 6]) << (((5 - i) * 5) + 4)); } - bin[1] |= (GetVal(filter, alpha[j >> 1][12]) >> 1); - dst[j++] = bin[1]; + result.emplace_back(value_a); + result.emplace_back(value_b); - // verify parity bit - int k = 0; - parity = 0; - for (int i = 0; i < 64; i++) - { - if (i == 32) - { - k++; - } - parity ^= (bin[k] >> (i - (k << 5))); - } - if ((parity & 1) != (GetVal(filter, alpha[(j - 2) >> 1][12]) & 1)) - { - ret = (org - size); - } + const u32 parity = std::popcount(value_a ^ value_b); + if ((parity & 1) != (GetVal(line[12]) & 1)) + return std::unexpected{current_index}; + + ++current_index; } - return ret; + return std::move(result); } -void DecryptARCode(std::vector vCodes, std::vector* ops) +std::optional DecryptARCode(std::span encrypted_lines, + std::vector* result) { - std::array uCodes{}; + if (encrypted_lines.empty()) + return std::nullopt; - for (std::string& s : vCodes) + auto conversion_result = ConvertAlphaToBinary(encrypted_lines); + if (!conversion_result.has_value()) { - Common::ToUpper(&s); - } - - const u32 ret = alphatobin(uCodes.data(), vCodes, (int)vCodes.size()); - if (ret) - { - // Return value is index + 1, 0 being the success flag value. PanicAlertFmtT( "Action Replay Code Decryption Error:\nParity Check Failed\n\nCulprit Code:\n{0}", - vCodes[ret - 1]); + encrypted_lines[conversion_result.error()]); + return std::nullopt; } - else if (!batchdecrypt(uCodes.data(), (u16)vCodes.size() << 1)) - { - // Commented out since we just send the code anyways and hope for the best XD - // PanicAlertFmt("Action Replay Code Decryption Error:\nCRC Check Failed\n\n" - // "First Code in Block (should be verification code):\n{}", - // vCodes[0]); - for (size_t i = 0; i < (vCodes.size() << 1); i += 2) + auto& codes = *conversion_result; + const auto decrypted_result = BatchDecrypt(codes); + if (!decrypted_result.has_value()) + { + ERROR_LOG_FMT(ACTIONREPLAY, + "Decryption Error: CRC Check Failed. " + "First Code in Block (should be verification code): {}", + encrypted_lines[0]); + + for (size_t i = 0; i < codes.size(); i += 2) { - ops->emplace_back(uCodes[i], uCodes[i + 1]); - // PanicAlertFmt("Decrypted AR Code without verification code:\n{:08X} {:08X}", uCodes[i], - // uCodes[i + 1]); + result->emplace_back(codes[i], codes[i + 1]); + WARN_LOG_FMT(ACTIONREPLAY, "Decrypted AR Code without verification code: {:08X} {:08X}", + codes[i], codes[i + 1]); } } else { + INFO_LOG_FMT(ACTIONREPLAY, "Decrypted AR Code: GameID:{:08X} Region:{}", + decrypted_result->game_id, decrypted_result->region); + // Skip passing the verification code back - for (size_t i = 2; i < (vCodes.size() << 1); i += 2) + for (size_t i = 2; i < codes.size(); i += 2) { - ops->emplace_back(uCodes[i], uCodes[i + 1]); - // PanicAlertFmt("Decrypted AR Code:\n{:08X} {:08X}", uCodes[i], uCodes[i+1]); + result->emplace_back(codes[i], codes[i + 1]); + DEBUG_LOG_FMT(ACTIONREPLAY, "Decrypted AR Code: {:08X} {:08X}", codes[i], codes[i + 1]); } } + + return decrypted_result; } } // namespace ActionReplay diff --git a/Source/Core/Core/ARDecrypt.h b/Source/Core/Core/ARDecrypt.h index d74e46dc27a..1364ae88814 100644 --- a/Source/Core/Core/ARDecrypt.h +++ b/Source/Core/Core/ARDecrypt.h @@ -3,14 +3,24 @@ #pragma once +#include +#include #include #include -#include "Common/CommonTypes.h" #include "Core/ActionReplay.h" namespace ActionReplay { -void DecryptARCode(std::vector vCodes, std::vector* ops); + +struct GameIDAndRegion +{ + // FYI: The "game id" here doesn't seem to match what we are familiar with. + u32 game_id; + u32 region; +}; + +std::optional DecryptARCode(std::span encrypted_lines, + std::vector* result); } // namespace ActionReplay