From 2e5bc0cb14d79a4626e09ed2fee7e24fc2d25d87 Mon Sep 17 00:00:00 2001 From: Mary Date: Mon, 19 Jun 2023 23:13:20 +0200 Subject: [PATCH 1/8] feat: crypto: Add basic AES keyslot manager We loads keys from AppData/Alber/sysdata/aes_keys.txt. NOTE: We do differ from other emulators by not hardcoding the generator key, it's the user responsibility to provide it in aes_keys.txt. --- CMakeLists.txt | 5 +- include/crypto/aes_engine.hpp | 167 +++++++++++++++++++++++++++++++++ include/emulator.hpp | 2 + include/helpers.hpp | 15 +++ src/core/crypto/aes_engine.cpp | 84 +++++++++++++++++ src/emulator.cpp | 9 +- 6 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 include/crypto/aes_engine.hpp create mode 100644 src/core/crypto/aes_engine.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 74c2d429..4a70e4b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp ) +set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp src/core/kernel/memory_management.cpp src/core/kernel/ports.cpp src/core/kernel/events.cpp src/core/kernel/threads.cpp @@ -119,6 +120,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/system_models.hpp include/services/dlp_srvr.hpp include/result/result.hpp include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp + include/crypto/aes_engine.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp @@ -130,6 +132,7 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp source_group("Header Files\\Core" FILES ${HEADER_FILES}) source_group("Source Files\\Core" FILES ${SOURCE_FILES}) +source_group("Source Files\\Core\\Crypto" FILES ${CRYPTO_SOURCE_FILES}) source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES}) source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES}) source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES}) @@ -138,7 +141,7 @@ source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES}) source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES}) source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) -add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} +add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp new file mode 100644 index 00000000..19408962 --- /dev/null +++ b/include/crypto/aes_engine.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "helpers.hpp" + +namespace Crypto { + constexpr std::size_t AesKeySize = 0x10; + + using AESKey = std::array; + + template + static std::array rolArray(const std::array& value, std::size_t bits) { + const auto bitWidth = N * UINT8_WIDTH; + + bits %= bitWidth; + + const auto byteShift = bits / UINT8_WIDTH; + const auto bitShift = bits % UINT8_WIDTH; + + std::array result; + + for (std::size_t i = 0; i < N; i++) { + result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (UINT8_WIDTH - bitShift))) & UINT8_MAX; + } + + return result; + } + + template + static std::array addArray(const std::array& a, const std::array& b) { + std::array result; + std::size_t sum = 0; + std::size_t carry = 0; + + for (std::int64_t i = N - 1; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> UINT8_WIDTH; + result[i] = static_cast(sum & UINT8_MAX); + } + + return result; + } + + template + static std::array xorArray(const std::array& a, const std::array& b) { + std::array result; + + for (std::size_t i = 0; i < N; i++) { + result[i] = a[i] ^ b[i]; + } + + return result; + } + + static std::optional createKeyFromHex(const std::string& hex) { + if (hex.size() < 32) { + return {}; + } + + AESKey rawKey; + for (std::size_t i = 0; i < rawKey.size(); i++) { + rawKey[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return rawKey; + } + + struct AESKeySlot { + std::optional keyX; + std::optional keyY; + std::optional normalKey; + }; + + enum KeySlotId : std::size_t { + NCCHKey0 = 0x2C, + NCCHKey1 = 0x25, + NCCHKey2 = 0x18, + NCCHKey3 = 0x1B, + }; + + class AESEngine { + private: + constexpr static std::size_t AesKeySlotCount = 0x40; + + std::optional m_generator; + std::array m_slots; + + constexpr void updateNormalKey(std::size_t slotId) { + if (m_generator.has_value() && hasKeyX(slotId) && hasKeyY(slotId)) { + auto &keySlot = m_slots.at(slotId); + AESKey keyX = keySlot.keyX.value(); + AESKey keyY = keySlot.keyY.value(); + + keySlot.normalKey = rolArray(addArray(xorArray(rolArray(keyX, 2), keyY), m_generator.value()), 87); + } + } + + public: + AESEngine() {} + + void loadKeys(const std::filesystem::path& path); + + constexpr bool hasKeyX(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).keyX.has_value(); + } + + constexpr AESKey getKeyX(std::size_t slotId) { + return m_slots.at(slotId).keyX.value_or(AESKey{}); + } + + constexpr void setKeyX(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).keyX = key; + + updateNormalKey(slotId); + } + } + + constexpr bool hasKeyY(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).keyY.has_value(); + } + + constexpr AESKey getKeyY(std::size_t slotId) { + return m_slots.at(slotId).keyY.value_or(AESKey{}); + } + + constexpr void setKeyY(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).keyY = key; + + updateNormalKey(slotId); + } + } + + constexpr bool hasNormalKey(std::size_t slotId) { + if (slotId >= AesKeySlotCount) { + return false; + } + + return m_slots.at(slotId).normalKey.has_value(); + } + + constexpr AESKey getNormalKey(std::size_t slotId) { + return m_slots.at(slotId).normalKey.value_or(AESKey{}); + } + + constexpr void setNormalKey(std::size_t slotId, const AESKey &key) { + if (slotId < AesKeySlotCount) { + m_slots.at(slotId).normalKey = key; + } + } + }; +} \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index 46aa0ade..fe9d0466 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -6,6 +6,7 @@ #include #include "cpu.hpp" +#include "crypto/aes_engine.hpp" #include "io_file.hpp" #include "memory.hpp" #include "opengl.hpp" @@ -20,6 +21,7 @@ class Emulator { GPU gpu; Memory memory; Kernel kernel; + Crypto::AESEngine aesEngine; SDL_Window* window; SDL_GLContext glContext; diff --git a/include/helpers.hpp b/include/helpers.hpp index 853e487b..e110a8b6 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -164,6 +166,19 @@ namespace Helpers { return std::bit_cast(from); } #endif + + static std::vector split(const std::string& s, const char c) { + std::istringstream tmp(s); + std::vector result(1); + + while (std::getline(tmp, *result.rbegin(), c)) { + result.emplace_back(); + } + + // Remove temporary slot + result.pop_back(); + return result; + } }; // namespace Helpers // UDLs for memory size values diff --git a/src/core/crypto/aes_engine.cpp b/src/core/crypto/aes_engine.cpp new file mode 100644 index 00000000..53694d02 --- /dev/null +++ b/src/core/crypto/aes_engine.cpp @@ -0,0 +1,84 @@ +#include +#include + +#include "crypto/aes_engine.hpp" +#include "helpers.hpp" + +namespace Crypto { + void AESEngine::loadKeys(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::in); + + if (file.fail()) { + Helpers::warn("keys: Couldn't read key file: %s", path.c_str()); + return; + } + + while (!file.eof()) { + std::string line; + + std::getline(file, line); + + // Skip obvious invalid lines + if (line.empty() || line.starts_with("#")) { + continue; + } + + const auto parts = Helpers::split(line, '='); + + if (parts.size() != 2) { + Helpers::warn("keys: Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + const std::string& rawKeyHex = parts[1]; + + std::size_t slotId; + char keyType; + + bool is_generator = name == "generator"; + + if (!is_generator && std::sscanf(name.c_str(), "slot0x%zXKey%c", &slotId, &keyType) != 2) { + Helpers::warn("keys: Ignoring unknown key %s", name.c_str()); + continue; + } + + auto key = createKeyFromHex(rawKeyHex); + + if (!key.has_value()) { + Helpers::warn("keys: Failed to parse raw key %s", rawKeyHex.c_str()); + continue; + } + + if (is_generator) { + m_generator = key; + continue; + } + + if (slotId >= AesKeySlotCount) { + Helpers::warn("keys: Invalid key slot id %u", slotId); + continue; + } + + switch (keyType) { + case 'X': + setKeyX(slotId, key.value()); + break; + case 'Y': + setKeyY(slotId, key.value()); + break; + case 'N': + setNormalKey(slotId, key.value()); + break; + default: + Helpers::warn("keys: Invalid key type %c", keyType); + break; + } + } + + // As the generator key can be set at any time, force update all normal keys. + for (std::size_t i = 0; i < AesKeySlotCount; i++) { + updateNormalKey(i); + } + } +}; \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..92a9fb30 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -135,8 +135,15 @@ bool Emulator::loadROM(const std::filesystem::path& path) { // Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in // %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart char* appData = SDL_GetPrefPath(nullptr, "Alber"); - const std::filesystem::path dataPath = std::filesystem::path(appData) / path.filename().stem(); + const std::filesystem::path appDataPath = std::filesystem::path(appData); + const std::filesystem::path dataPath = appDataPath / path.filename().stem(); + const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt"; IOFile::setAppDataDir(dataPath); + + if (std::filesystem::exists(aesKeysPath)) { + aesEngine.loadKeys(aesKeysPath); + } + SDL_free(appData); kernel.initializeFS(); From 86dd7f54f5657f834d83d83c8665a549be7f7ff2 Mon Sep 17 00:00:00 2001 From: Mary Date: Mon, 19 Jun 2023 23:13:38 +0200 Subject: [PATCH 2/8] feat: Add support for encrypted NCSD Missing version 1 support and seeded crypto but that would be enough for now. --- CMakeLists.txt | 4 +- include/loader/ncch.hpp | 21 +- include/memory.hpp | 3 +- src/core/fs/archive_ncch.cpp | 9 +- src/core/fs/archive_self_ncch.cpp | 13 +- src/core/loader/ncch.cpp | 375 +++++++++++++++++++++--------- src/core/loader/ncsd.cpp | 16 +- src/emulator.cpp | 2 +- 8 files changed, 305 insertions(+), 138 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a70e4b4..4250172b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,9 +143,9 @@ source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) -target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad) +target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad cryptopp) if(GPU_DEBUG_INFO) target_compile_definitions(Alber PRIVATE GPU_DEBUG_INFO=1) -endif() \ No newline at end of file +endif() diff --git a/include/loader/ncch.hpp b/include/loader/ncch.hpp index 2d05602d..95856e8c 100644 --- a/include/loader/ncch.hpp +++ b/include/loader/ncch.hpp @@ -1,14 +1,22 @@ #pragma once #include +#include #include #include "io_file.hpp" #include "helpers.hpp" +#include "crypto/aes_engine.hpp" struct NCCH { + struct EncryptionInfo { + Crypto::AESKey normalKey; + Crypto::AESKey initialCounter; + }; + struct FSInfo { // Info on the ExeFS/RomFS u64 offset = 0; u64 size = 0; u64 hashRegionSize = 0; + std::optional encryptionInfo; }; // Descriptions for .text, .data and .rodata sections @@ -34,6 +42,8 @@ struct NCCH { bool mountRomFS = false; bool encrypted = false; bool fixedCryptoKey = false; + bool seedCrypto = false; + u8 secondaryKeySlot = 0; static constexpr u64 mediaUnit = 0x200; u64 size = 0; // Size of NCCH converted to bytes @@ -41,6 +51,7 @@ struct NCCH { u32 bssSize = 0; u32 exheaderSize = 0; + FSInfo exheaderInfo; FSInfo exeFS; FSInfo romFS; CodeSetInfo text, data, rodata; @@ -50,10 +61,9 @@ struct NCCH { // Contains of the cart's save data std::vector saveData; - // Header: 0x200 + 0x800 byte NCCH header + exheadr // Returns true on success, false on failure // Partition index/offset/size must have been set before this - bool loadFromHeader(u8* header, IOFile& file); + bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info); bool hasExtendedHeader() { return exheaderSize != 0; } bool hasExeFS() { return exeFS.size != 0; } @@ -61,7 +71,8 @@ struct NCCH { bool hasCode() { return codeFile.size() != 0; } bool hasSaveData() { return saveData.size() != 0; } -private: - std::array primaryKey = {}; // For exheader, ExeFS header and icons - std::array secondaryKey = {}; // For RomFS and some files in ExeFS + std::pair getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); + std::pair getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); + + std::pair readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size); }; \ No newline at end of file diff --git a/include/memory.hpp b/include/memory.hpp index 33b18ca5..3d68866e 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "crypto/aes_engine.hpp" #include "helpers.hpp" #include "handles.hpp" #include "loader/ncsd.hpp" @@ -146,7 +147,7 @@ public: void* getReadPointer(u32 address); void* getWritePointer(u32 address); std::optional loadELF(std::ifstream& file); - std::optional loadNCSD(const std::filesystem::path& path); + std::optional loadNCSD(Crypto::AESEngine &aesEngine, const std::filesystem::path& path); u8 read8(u32 vaddr); u16 read16(u32 vaddr); diff --git a/src/core/fs/archive_ncch.cpp b/src/core/fs/archive_ncch.cpp index 0c635c21..3a3330b8 100644 --- a/src/core/fs/archive_ncch.cpp +++ b/src/core/fs/archive_ncch.cpp @@ -133,6 +133,8 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size auto cxi = mem.getCXI(); IOFile& ioFile = mem.CXIFile; + NCCH::FSInfo fsInfo; + // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc switch (type) { case PathType::RomFS: { @@ -142,9 +144,8 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size Helpers::panic("Tried to read from NCCH with too big of an offset"); } - if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) { - Helpers::panic("Failed to seek while reading from RomFS"); - } + fsInfo = cxi->romFS; + offset += 0x1000; break; } @@ -153,7 +154,7 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size } std::unique_ptr data(new u8[size]); - auto [success, bytesRead] = ioFile.readBytes(&data[0], size); + auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size); if (!success) { Helpers::panic("Failed to read from NCCH archive"); diff --git a/src/core/fs/archive_self_ncch.cpp b/src/core/fs/archive_self_ncch.cpp index 2146c578..fa141b03 100644 --- a/src/core/fs/archive_self_ncch.cpp +++ b/src/core/fs/archive_self_ncch.cpp @@ -71,6 +71,8 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 auto cxi = mem.getCXI(); IOFile& ioFile = mem.CXIFile; + NCCH::FSInfo fsInfo; + // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc switch (type) { case PathType::RomFS: { @@ -80,9 +82,8 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); } - if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) { - Helpers::panic("Failed to seek while reading from RomFS"); - } + fsInfo = cxi->romFS; + offset += 0x1000; break; } @@ -93,9 +94,7 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); } - if (!ioFile.seek(cxi->fileOffset + exeFSOffset + offset)) { // TODO: Not sure if this needs the + 0x1000 - Helpers::panic("Failed to seek while reading from ExeFS"); - } + fsInfo = cxi->exeFS; break; } @@ -104,7 +103,7 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 } std::unique_ptr data(new u8[size]); - auto [success, bytesRead] = ioFile.readBytes(&data[0], size); + auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size); if (!success) { Helpers::panic("Failed to read from SelfNCCH archive"); diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c3dd8d2e..196a29d4 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -1,142 +1,303 @@ +#include +#include #include #include #include "loader/lz77.hpp" #include "loader/ncch.hpp" #include "memory.hpp" -bool NCCH::loadFromHeader(u8* header, IOFile& file) { - if (header[0x100] != 'N' || header[0x101] != 'C' || header[0x102] != 'C' || header[0x103] != 'H') { - printf("Invalid header on NCCH\n"); +#include + +bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info) { + // 0x200 bytes for the NCCH header + constexpr u64 headerSize = 0x200; + u8 header[headerSize]; + + auto [success, bytes] = readFromFile(file, info, header, 0, headerSize); + if (!success || bytes != headerSize) { + printf("Failed to read NCCH header\n"); return false; } + + if (header[0x100] != 'N' || header[0x101] != 'C' || header[0x102] != 'C' || header[0x103] != 'H') { + printf("Invalid header on NCCH\n"); + return false; + } - codeFile.clear(); - saveData.clear(); + codeFile.clear(); + saveData.clear(); - size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break - exheaderSize = *(u32*)&header[0x180]; + size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break + exheaderSize = *(u32*)&header[0x180]; - const u64 programID = *(u64*)&header[0x118]; + const u64 programID = *(u64*)&header[0x118]; - // Read NCCH flags - isNew3DS = header[0x188 + 4] == 2; - fixedCryptoKey = (header[0x188 + 7] & 0x1) == 0x1; - mountRomFS = (header[0x188 + 7] & 0x2) != 0x2; - encrypted = (header[0x188 + 7] & 0x4) != 0x4; - - // Read ExeFS and RomFS info - exeFS.offset = u64(*(u32*)&header[0x1A0]) * mediaUnit; - exeFS.size = u64(*(u32*)&header[0x1A4]) * mediaUnit; - exeFS.hashRegionSize = u64(*(u32*)&header[0x1A8]) * mediaUnit; + // Read NCCH flags + secondaryKeySlot = header[0x188 + 3]; + isNew3DS = header[0x188 + 4] == 2; + fixedCryptoKey = (header[0x188 + 7] & 0x1) == 0x1; + mountRomFS = (header[0x188 + 7] & 0x2) != 0x2; + encrypted = (header[0x188 + 7] & 0x4) != 0x4; + seedCrypto = (header[0x188 + 7] & 0x20) == 0x20; - romFS.offset = u64(*(u32*)&header[0x1B0]) * mediaUnit; - romFS.size = u64(*(u32*)&header[0x1B4]) * mediaUnit; - romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit; + // Read exheader, ExeFS and RomFS info + exheaderInfo.offset = info.offset + 0x200; + exheaderInfo.size = exheaderSize; + exheaderInfo.hashRegionSize = 0; - if (fixedCryptoKey) { - Helpers::panic("Fixed crypto keys for NCCH"); - } + exeFS.offset = info.offset + u64(*(u32*)&header[0x1A0]) * mediaUnit; + exeFS.size = u64(*(u32*)&header[0x1A4]) * mediaUnit; + exeFS.hashRegionSize = u64(*(u32*)&header[0x1A8]) * mediaUnit; - if (exheaderSize != 0) { - const u8* exheader = &header[0x200]; // Extended NCCH header - const u64 jumpID = *(u64*)&exheader[0x1C0 + 0x8]; + romFS.offset = info.offset + u64(*(u32*)&header[0x1B0]) * mediaUnit; + romFS.size = u64(*(u32*)&header[0x1B4]) * mediaUnit; + romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit; - // It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header - // This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra - if (u32(programID) == u32(jumpID) && encrypted) { - printf("NCSD is supposedly ecrypted but not actually encrypted\n"); - encrypted = false; - } else if (encrypted) { - Helpers::panic("Encrypted NCSD file"); - } + if (encrypted) { + Crypto::AESKey primaryKeyY; + Crypto::AESKey secondaryKeyY; + std::memcpy(primaryKeyY.data(), header, primaryKeyY.size()); - const u64 saveDataSize = *(u64*)&exheader[0x1C0 + 0x0]; // Size of save data in bytes - saveData.resize(saveDataSize, 0xff); + if (!seedCrypto) { + secondaryKeyY = primaryKeyY; + } else { + Helpers::panic("Seed crypto is not supported"); + return false; + } - compressCode = (exheader[0xD] & 1) != 0; - stackSize = *(u32*)&exheader[0x1C]; - bssSize = *(u32*)&exheader[0x3C]; + auto primaryResult = getPrimaryKey(aesEngine, primaryKeyY); - text.extract(&exheader[0x10]); - rodata.extract(&exheader[0x20]); - data.extract(&exheader[0x30]); - } + if (!primaryResult.first) { + Helpers::panic("getPrimaryKey failed!"); + return false; + } - printf("Stack size: %08X\nBSS size: %08X\n", stackSize, bssSize); + Crypto::AESKey primaryKey = primaryResult.second; - // Read ExeFS - if (hasExeFS()) { - u64 exeFSOffset = fileOffset + exeFS.offset; // Offset of ExeFS in the file = exeFS offset + ncch offset - printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset, exeFS.size, exeFSOffset); - constexpr size_t exeFSHeaderSize = 0x200; + auto secondaryResult = getSecondaryKey(aesEngine, secondaryKeyY); - u8 exeFSHeader[exeFSHeaderSize]; + if (!secondaryResult.first) { + Helpers::panic("getSecondaryKey failed!"); + return false; + } - file.seek(exeFSOffset); - auto [success, bytes] = file.readBytes(exeFSHeader, exeFSHeaderSize); - if (!success || bytes != exeFSHeaderSize) { - printf("Failed to parse ExeFS header\n"); - return false; - } + Crypto::AESKey secondaryKey = secondaryResult.second; - // ExeFS format allows up to 10 files - for (int i = 0; i < 10; i++) { - u8* fileInfo = &exeFSHeader[i * 16]; + EncryptionInfo encryptionInfoTmp; + encryptionInfoTmp.normalKey = primaryKey; + encryptionInfoTmp.initialCounter.fill(0); - char name[9]; - std::memcpy(name, fileInfo, 8); // Get file name as a string - name[8] = '\0'; // Add null terminator to it just in case there's none + for (std::size_t i = 1; i <= sizeof(std::uint64_t) - 1; i++) { + encryptionInfoTmp.initialCounter[i] = header[0x108 + sizeof(std::uint64_t) - 1 - i]; + } + encryptionInfoTmp.initialCounter[8] = 1; + exheaderInfo.encryptionInfo = encryptionInfoTmp; - u32 fileOffset = *(u32*)&fileInfo[0x8]; - u32 fileSize = *(u32*)&fileInfo[0xC]; + encryptionInfoTmp.initialCounter[8] = 2; + exeFS.encryptionInfo = encryptionInfoTmp; - if (fileSize != 0) { - printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", i, name, fileSize, fileOffset); - } + encryptionInfoTmp.normalKey = secondaryKey; + encryptionInfoTmp.initialCounter[8] = 3; + romFS.encryptionInfo = encryptionInfoTmp; + } - if (std::strcmp(name, ".code") == 0) { - if (hasCode()) { - Helpers::panic("Second code file in a single NCCH partition. What should this do?\n"); - } + if (exheaderSize != 0) { + u8 exheader[exheaderSize]; - if (compressCode) { - std::vector tmp; - tmp.resize(fileSize); + auto [success, bytes] = readFromFile(file, info, exheader, 0x200, exheaderSize); + if (!success || bytes != exheaderSize) { + printf("Failed to read Extended NCCH header\n"); + return false; + } - // A file offset of 0 means our file is located right after the ExeFS header - // So in the ROM, files are located at (file offset + exeFS offset + exeFS header size) - file.seek(exeFSOffset + exeFSHeaderSize + fileOffset); - file.readBytes(tmp.data(), fileSize); - - // Decompress .code file from the tmp vector to the "code" vector - if (!CartLZ77::decompress(codeFile, tmp)) { - printf("Failed to decompress .code file\n"); - return false; - } - } else { - codeFile.resize(fileSize); - file.seek(exeFSOffset + exeFSHeaderSize + fileOffset); - file.readBytes(codeFile.data(), fileSize); - } - } - } - } + const u64 jumpID = *(u64*)&exheader[0x1C0 + 0x8]; - if (hasRomFS()) { - printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size); - } + // It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header + // This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra + if (u32(programID) == u32(jumpID) && encrypted) { + printf("NCSD is supposedly ecrypted but not actually encrypted\n"); + encrypted = false; + } + // If it's truely encrypted, we need to read section again. + if (encrypted) { + auto [success, bytes] = readFromFile(file, exheaderInfo, exheader, 0, exheaderSize); + if (!success || bytes != exheaderSize) { + printf("Failed to read Extended NCCH header\n"); + return false; + } + } - if (stackSize != 0 && stackSize != VirtualAddrs::DefaultStackSize) { - Helpers::warn("Requested stack size is %08X bytes. Temporarily emulated as 0x4000 until adjustable sizes are added\n", stackSize); - } + const u64 saveDataSize = *(u64*)&exheader[0x1C0 + 0x0]; // Size of save data in bytes + saveData.resize(saveDataSize, 0xff); - if (encrypted) { - if (hasExeFS()) - Helpers::panic("Encrypted NCCH partition with ExeFS"); - else - printf("Encrypted NCCH partition. Hopefully not required because it doesn't have an ExeFS. Skipped\n"); - } + compressCode = (exheader[0xD] & 1) != 0; + stackSize = *(u32*)&exheader[0x1C]; + bssSize = *(u32*)&exheader[0x3C]; - initialized = true; - return true; + text.extract(&exheader[0x10]); + rodata.extract(&exheader[0x20]); + data.extract(&exheader[0x30]); + } + + printf("Stack size: %08X\nBSS size: %08X\n", stackSize, bssSize); + + // Read ExeFS + if (hasExeFS()) { + u64 exeFSOffset = fileOffset + exeFS.offset; // Offset of ExeFS in the file = exeFS offset + ncch offset + printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset, exeFS.size, exeFSOffset); + constexpr size_t exeFSHeaderSize = 0x200; + + u8 exeFSHeader[exeFSHeaderSize]; + + auto [success, bytes] = readFromFile(file, exeFS, exeFSHeader, 0, exeFSHeaderSize); + if (!success || bytes != exeFSHeaderSize) { + printf("Failed to parse ExeFS header\n"); + return false; + } + + // ExeFS format allows up to 10 files + for (int i = 0; i < 10; i++) { + u8* fileInfo = &exeFSHeader[i * 16]; + + char name[9]; + std::memcpy(name, fileInfo, 8); // Get file name as a string + name[8] = '\0'; // Add null terminator to it just in case there's none + + u32 fileOffset = *(u32*)&fileInfo[0x8]; + u32 fileSize = *(u32*)&fileInfo[0xC]; + + if (fileSize != 0) { + printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", i, name, fileSize, fileOffset); + } + + if (std::strcmp(name, ".code") == 0) { + if (hasCode()) { + Helpers::panic("Second code file in a single NCCH partition. What should this do?\n"); + } + + if (compressCode) { + std::vector tmp; + tmp.resize(fileSize); + + // A file offset of 0 means our file is located right after the ExeFS header + // So in the ROM, files are located at (file offset + exeFS offset + exeFS header size) + readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize); + + // Decompress .code file from the tmp vector to the "code" vector + if (!CartLZ77::decompress(codeFile, tmp)) { + printf("Failed to decompress .code file\n"); + return false; + } + } else { + codeFile.resize(fileSize); + readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize); + } + } + } + } + + if (hasRomFS()) { + printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size); + } + + if (stackSize != 0 && stackSize != VirtualAddrs::DefaultStackSize) { + Helpers::warn("Requested stack size is %08X bytes. Temporarily emulated as 0x4000 until adjustable sizes are added\n", stackSize); + } + + initialized = true; + return true; +} + +std::pair NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) { + Crypto::AESKey result; + + if (encrypted) { + if (fixedCryptoKey) { + return {true, result}; + } + + aesEngine.setKeyY(Crypto::KeySlotId::NCCHKey0, keyY); + + if (!aesEngine.hasNormalKey(Crypto::KeySlotId::NCCHKey0)) { + return {false, result}; + } + + result = aesEngine.getNormalKey(Crypto::KeySlotId::NCCHKey0); + } + + return {true, result}; +} + +std::pair NCCH::getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) { + Crypto::AESKey result; + + if (encrypted) { + + if (fixedCryptoKey) { + return {true, result}; + } + + Crypto::KeySlotId keySlotId; + + switch (secondaryKeySlot) { + case 0: + keySlotId = Crypto::KeySlotId::NCCHKey0; + break; + case 1: + keySlotId = Crypto::KeySlotId::NCCHKey1; + break; + case 10: + keySlotId = Crypto::KeySlotId::NCCHKey2; + break; + case 11: + keySlotId = Crypto::KeySlotId::NCCHKey3; + break; + default: + return {false, result}; + } + + if (!aesEngine.hasKeyX(keySlotId)) { + return {false, result}; + } + + aesEngine.setKeyY(keySlotId, keyY); + + if (!aesEngine.hasNormalKey(keySlotId)) { + return {false, result}; + } + + result = aesEngine.getNormalKey(keySlotId); + } + + return {true, result}; +} + +std::pair NCCH::readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size) { + if (size == 0) { + return { true, 0 }; + } + + std::size_t readMaxSize = std::min(size, static_cast(info.size) - offset); + + file.seek(info.offset + offset); + auto [success, bytes] = file.readBytes(dst, readMaxSize); + + if (!success) { + return { success, bytes}; + } + + if (success && info.encryptionInfo.has_value()) { + auto& encryptionInfo = info.encryptionInfo.value(); + + CryptoPP::CTR_Mode::Decryption d(encryptionInfo.normalKey.data(), encryptionInfo.normalKey.size(), encryptionInfo.initialCounter.data()); + + if (offset > 0) { + d.Seek(offset); + } + + CryptoPP::byte* data = reinterpret_cast(dst); + d.ProcessData(data, data, bytes); + } + + return { success, bytes}; } \ No newline at end of file diff --git a/src/core/loader/ncsd.cpp b/src/core/loader/ncsd.cpp index bf93f490..1166b3fe 100644 --- a/src/core/loader/ncsd.cpp +++ b/src/core/loader/ncsd.cpp @@ -3,7 +3,7 @@ #include "loader/ncsd.hpp" #include "memory.hpp" -std::optional Memory::loadNCSD(const std::filesystem::path& path) { +std::optional Memory::loadNCSD(Crypto::AESEngine &aesEngine, const std::filesystem::path& path) { NCSD ncsd; if (!ncsd.file.open(path, "rb")) return std::nullopt; @@ -51,18 +51,12 @@ std::optional Memory::loadNCSD(const std::filesystem::path& path) { ncch.fileOffset = partition.offset; if (partition.length != 0) { // Initialize the NCCH of each partition - ncsd.file.seek(partition.offset); + NCCH::FSInfo ncchFsInfo; - // 0x200 bytes for the NCCH header and another 0x800 for the exheader - constexpr u64 headerSize = 0x200 + 0x800; - u8 ncchHeader[headerSize]; - std::tie(success, bytes) = ncsd.file.readBytes(ncchHeader, headerSize); - if (!success || bytes != headerSize) { - printf("Failed to read NCCH header\n"); - return std::nullopt; - } + ncchFsInfo.offset = partition.offset; + ncchFsInfo.size = partition.length; - if (!ncch.loadFromHeader(ncchHeader, ncsd.file)) { + if (!ncch.loadFromHeader(aesEngine, ncsd.file, ncchFsInfo)) { printf("Invalid NCCH partition\n"); return std::nullopt; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 92a9fb30..f37e8825 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -161,7 +161,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) { bool Emulator::loadNCSD(const std::filesystem::path& path) { romType = ROMType::NCSD; - std::optional opt = memory.loadNCSD(path); + std::optional opt = memory.loadNCSD(aesEngine, path); if (!opt.has_value()) { return false; From 6bdc754e86f07644f81a654e9ae75af62571eee9 Mon Sep 17 00:00:00 2001 From: Mary Date: Mon, 19 Jun 2023 23:35:08 +0200 Subject: [PATCH 3/8] fix: Use CHAR_BIT instead of UINT8_WIDTH Fix building on Windows --- include/crypto/aes_engine.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp index 19408962..9c92feba 100644 --- a/include/crypto/aes_engine.hpp +++ b/include/crypto/aes_engine.hpp @@ -16,17 +16,17 @@ namespace Crypto { template static std::array rolArray(const std::array& value, std::size_t bits) { - const auto bitWidth = N * UINT8_WIDTH; + const auto bitWidth = N * CHAR_BIT; bits %= bitWidth; - const auto byteShift = bits / UINT8_WIDTH; - const auto bitShift = bits % UINT8_WIDTH; + const auto byteShift = bits / CHAR_BIT; + const auto bitShift = bits % CHAR_BIT; std::array result; for (std::size_t i = 0; i < N; i++) { - result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (UINT8_WIDTH - bitShift))) & UINT8_MAX; + result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (CHAR_BIT - bitShift))) & UINT8_MAX; } return result; @@ -40,7 +40,7 @@ namespace Crypto { for (std::int64_t i = N - 1; i >= 0; i--) { sum = a[i] + b[i] + carry; - carry = sum >> UINT8_WIDTH; + carry = sum >> CHAR_BIT; result[i] = static_cast(sum & UINT8_MAX); } From ef5329a0d436b722efe3fc47cc1f116b3a9f1344 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 20 Jun 2023 08:35:48 +0200 Subject: [PATCH 4/8] fix: do not allocate exheader on the stack as it has a variable length Fix build on Windows hopefully. --- src/core/loader/ncch.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 196a29d4..fb121fa5 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -102,9 +102,9 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn } if (exheaderSize != 0) { - u8 exheader[exheaderSize]; + std::unique_ptr exheader(new u8[exheaderSize]); - auto [success, bytes] = readFromFile(file, info, exheader, 0x200, exheaderSize); + auto [success, bytes] = readFromFile(file, info, &exheader[0], 0x200, exheaderSize); if (!success || bytes != exheaderSize) { printf("Failed to read Extended NCCH header\n"); return false; @@ -120,7 +120,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn } // If it's truely encrypted, we need to read section again. if (encrypted) { - auto [success, bytes] = readFromFile(file, exheaderInfo, exheader, 0, exheaderSize); + auto [success, bytes] = readFromFile(file, exheaderInfo, &exheader[0], 0, exheaderSize); if (!success || bytes != exheaderSize) { printf("Failed to read Extended NCCH header\n"); return false; From 0494ca0064fed7dfb2f44827436f03ea283f2676 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 20 Jun 2023 08:31:31 +0200 Subject: [PATCH 5/8] Address comments --- include/crypto/aes_engine.hpp | 6 +++--- src/core/loader/ncch.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp index 9c92feba..0bb579c5 100644 --- a/include/crypto/aes_engine.hpp +++ b/include/crypto/aes_engine.hpp @@ -72,9 +72,9 @@ namespace Crypto { } struct AESKeySlot { - std::optional keyX; - std::optional keyY; - std::optional normalKey; + std::optional keyX{std::nullopt}; + std::optional keyY{std::nullopt}; + std::optional normalKey{std::nullopt}; }; enum KeySlotId : std::size_t { diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index fb121fa5..1a4178fe 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -118,7 +118,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn printf("NCSD is supposedly ecrypted but not actually encrypted\n"); encrypted = false; } - // If it's truely encrypted, we need to read section again. + // If it's truly encrypted, we need to read section again. if (encrypted) { auto [success, bytes] = readFromFile(file, exheaderInfo, &exheader[0], 0, exheaderSize); if (!success || bytes != exheaderSize) { From 3cf84276705299370556af96ba3d9d619863bac4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 27 Jun 2023 01:12:17 +0300 Subject: [PATCH 6/8] [Crypto] Slightly more robust error handling --- include/crypto/aes_engine.hpp | 38 ++++++++++++++++------------------ src/core/crypto/aes_engine.cpp | 17 +++++++-------- src/core/loader/ncch.cpp | 8 +++++++ src/emulator.cpp | 10 +++++---- src/main.cpp | 2 +- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp index 0bb579c5..3cb75bac 100644 --- a/include/crypto/aes_engine.hpp +++ b/include/crypto/aes_engine.hpp @@ -11,11 +11,10 @@ namespace Crypto { constexpr std::size_t AesKeySize = 0x10; + using AESKey = std::array; - using AESKey = std::array; - - template - static std::array rolArray(const std::array& value, std::size_t bits) { + template + static std::array rolArray(const std::array& value, std::size_t bits) { const auto bitWidth = N * CHAR_BIT; bits %= bitWidth; @@ -23,7 +22,7 @@ namespace Crypto { const auto byteShift = bits / CHAR_BIT; const auto bitShift = bits % CHAR_BIT; - std::array result; + std::array result; for (std::size_t i = 0; i < N; i++) { result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (CHAR_BIT - bitShift))) & UINT8_MAX; @@ -32,24 +31,24 @@ namespace Crypto { return result; } - template - static std::array addArray(const std::array& a, const std::array& b) { - std::array result; + template + static std::array addArray(const std::array& a, const std::array& b) { + std::array result; std::size_t sum = 0; std::size_t carry = 0; for (std::int64_t i = N - 1; i >= 0; i--) { sum = a[i] + b[i] + carry; carry = sum >> CHAR_BIT; - result[i] = static_cast(sum & UINT8_MAX); + result[i] = static_cast(sum & UINT8_MAX); } return result; } - template - static std::array xorArray(const std::array& a, const std::array& b) { - std::array result; + template + static std::array xorArray(const std::array& a, const std::array& b) { + std::array result; for (std::size_t i = 0; i < N; i++) { result[i] = a[i] ^ b[i]; @@ -65,16 +64,16 @@ namespace Crypto { AESKey rawKey; for (std::size_t i = 0; i < rawKey.size(); i++) { - rawKey[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + rawKey[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); } return rawKey; } struct AESKeySlot { - std::optional keyX{std::nullopt}; - std::optional keyY{std::nullopt}; - std::optional normalKey{std::nullopt}; + std::optional keyX = std::nullopt; + std::optional keyY = std::nullopt; + std::optional normalKey = std::nullopt; }; enum KeySlotId : std::size_t { @@ -88,8 +87,9 @@ namespace Crypto { private: constexpr static std::size_t AesKeySlotCount = 0x40; - std::optional m_generator; + std::optional m_generator = std::nullopt; std::array m_slots; + bool keysLoaded = false; constexpr void updateNormalKey(std::size_t slotId) { if (m_generator.has_value() && hasKeyX(slotId) && hasKeyY(slotId)) { @@ -103,8 +103,8 @@ namespace Crypto { public: AESEngine() {} - void loadKeys(const std::filesystem::path& path); + bool haveKeys() { return keysLoaded; } constexpr bool hasKeyX(std::size_t slotId) { if (slotId >= AesKeySlotCount) { @@ -121,7 +121,6 @@ namespace Crypto { constexpr void setKeyX(std::size_t slotId, const AESKey &key) { if (slotId < AesKeySlotCount) { m_slots.at(slotId).keyX = key; - updateNormalKey(slotId); } } @@ -141,7 +140,6 @@ namespace Crypto { constexpr void setKeyY(std::size_t slotId, const AESKey &key) { if (slotId < AesKeySlotCount) { m_slots.at(slotId).keyY = key; - updateNormalKey(slotId); } } diff --git a/src/core/crypto/aes_engine.cpp b/src/core/crypto/aes_engine.cpp index 53694d02..f4bf3494 100644 --- a/src/core/crypto/aes_engine.cpp +++ b/src/core/crypto/aes_engine.cpp @@ -9,13 +9,12 @@ namespace Crypto { std::ifstream file(path, std::ios::in); if (file.fail()) { - Helpers::warn("keys: Couldn't read key file: %s", path.c_str()); + Helpers::warn("Keys: Couldn't read key file: %s", path.c_str()); return; } while (!file.eof()) { std::string line; - std::getline(file, line); // Skip obvious invalid lines @@ -24,9 +23,8 @@ namespace Crypto { } const auto parts = Helpers::split(line, '='); - if (parts.size() != 2) { - Helpers::warn("keys: Failed to parse %s", line.c_str()); + Helpers::warn("Keys: Failed to parse %s", line.c_str()); continue; } @@ -37,16 +35,15 @@ namespace Crypto { char keyType; bool is_generator = name == "generator"; - if (!is_generator && std::sscanf(name.c_str(), "slot0x%zXKey%c", &slotId, &keyType) != 2) { - Helpers::warn("keys: Ignoring unknown key %s", name.c_str()); + Helpers::warn("Keys: Ignoring unknown key %s", name.c_str()); continue; } auto key = createKeyFromHex(rawKeyHex); if (!key.has_value()) { - Helpers::warn("keys: Failed to parse raw key %s", rawKeyHex.c_str()); + Helpers::warn("Keys: Failed to parse raw key %s", rawKeyHex.c_str()); continue; } @@ -56,7 +53,7 @@ namespace Crypto { } if (slotId >= AesKeySlotCount) { - Helpers::warn("keys: Invalid key slot id %u", slotId); + Helpers::warn("Keys: Invalid key slot id %u", slotId); continue; } @@ -71,7 +68,7 @@ namespace Crypto { setNormalKey(slotId, key.value()); break; default: - Helpers::warn("keys: Invalid key type %c", keyType); + Helpers::warn("Keys: Invalid key type %c", keyType); break; } } @@ -80,5 +77,7 @@ namespace Crypto { for (std::size_t i = 0; i < AesKeySlotCount; i++) { updateNormalKey(i); } + + keysLoaded = true; } }; \ No newline at end of file diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 1a4178fe..0f29ddb5 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -54,6 +54,14 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit; if (encrypted) { + if (!aesEngine.haveKeys()) { + Helpers::panic( + "Loaded an encrypted ROM but AES keys don't seem to have been provided correctly! Navigate to the emulator's\n" + "app data folder and make sure you have a sysdata directory with a file called aes_keys.txt which contains your keys!" + ); + return false; + } + Crypto::AESKey primaryKeyY; Crypto::AESKey secondaryKeyY; std::memcpy(primaryKeyY.data(), header, primaryKeyY.size()); diff --git a/src/emulator.cpp b/src/emulator.cpp index f37e8825..d41c6928 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -137,15 +137,17 @@ bool Emulator::loadROM(const std::filesystem::path& path) { char* appData = SDL_GetPrefPath(nullptr, "Alber"); const std::filesystem::path appDataPath = std::filesystem::path(appData); const std::filesystem::path dataPath = appDataPath / path.filename().stem(); - const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt"; + const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt"; IOFile::setAppDataDir(dataPath); + SDL_free(appData); - if (std::filesystem::exists(aesKeysPath)) { + // Open the text file containing our AES keys if it exists. We use the std::filesystem::exists overload that takes an error code param to + // avoid the call throwing exceptions + std::error_code ec; + if (std::filesystem::exists(aesKeysPath, ec) && !ec) { aesEngine.loadKeys(aesKeysPath); } - SDL_free(appData); - kernel.initializeFS(); auto extension = path.extension(); diff --git a/src/main.cpp b/src/main.cpp index 39f6403c..d78dc407 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,7 @@ int main (int argc, char *argv[]) { emu.initGraphicsContext(); - auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "Metroid Prime - Federation Force (Europe) (En,Fr,De,Es,It).3ds"); + auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "OoT Demo Encrypted.3ds"); if (!emu.loadROM(romPath)) { // For some reason just .c_str() doesn't show the proper path Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); From 432b4b847b253903998311f9b4dce3c10f296f01 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 27 Jun 2023 01:49:12 +0300 Subject: [PATCH 7/8] Making helpers.hpp thinner --- CMakeLists.txt | 2 +- include/helpers.hpp | 57 ------------------------------------- include/metaprogramming.hpp | 16 +++++++++++ 3 files changed, 17 insertions(+), 58 deletions(-) create mode 100644 include/metaprogramming.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4250172b..4fde60ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/system_models.hpp include/services/dlp_srvr.hpp include/result/result.hpp include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp - include/crypto/aes_engine.hpp + include/crypto/aes_engine.hpp include/metaprogramming.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/helpers.hpp b/include/helpers.hpp index e110a8b6..40e8c0b7 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -2,13 +2,10 @@ #include #include #include -#include #include #include #include #include -#include -#include #include #include "termcolor.hpp" @@ -53,21 +50,6 @@ namespace Helpers { va_end(args); } - static std::vector loadROM(std::string directory) { - std::ifstream file(directory, std::ios::binary); - if (file.fail()) panic("Couldn't read %s", directory.c_str()); - - std::vector ROM; - - file.unsetf(std::ios::skipws); - ROM.insert(ROM.begin(), std::istream_iterator(file), std::istream_iterator()); - - file.close(); - - printf("%s loaded successfully\n", directory.c_str()); - return ROM; - } - static constexpr bool buildingInDebugMode() { #ifdef NDEBUG return false; @@ -122,36 +104,6 @@ namespace Helpers { return (value >> offset) & ones(); } - /// Check if a bit "bit" of value is set - static constexpr bool isBitSet(u32 value, int bit) { return (value >> bit) & 1; } - - /// rotate number right - template - static constexpr T rotr(T value, int bits) { - constexpr auto bitWidth = sizeof(T) * 8; - bits &= bitWidth - 1; - return (value >> bits) | (value << (bitWidth - bits)); - } - - // rotate number left - template - static constexpr T rotl(T value, int bits) { - constexpr auto bitWidth = sizeof(T) * 8; - bits &= bitWidth - 1; - return (value << bits) | (value >> (bitWidth - bits)); - } - - /// Used to make the compiler evaluate beeg loops at compile time for the tablegen - template - static constexpr void static_for_impl(Func&& f, std::integer_sequence) { - (f(std::integral_constant{}), ...); - } - - template - static constexpr void static_for(Func&& f) { - static_for_impl(std::forward(f), std::make_integer_sequence{}); - } - // For values < 0x99 static constexpr inline u8 incBCDByte(u8 value) { return ((value & 0xf) == 0x9) ? value + 7 : value + 1; } @@ -186,12 +138,3 @@ constexpr size_t operator""_KB(unsigned long long int x) { return 1024ULL * x; } constexpr size_t operator""_MB(unsigned long long int x) { return 1024_KB * x; } constexpr size_t operator""_GB(unsigned long long int x) { return 1024_MB * x; } -// useful macros -// likely/unlikely -#ifdef __GNUC__ -#define likely(x) __builtin_expect((x), 1) -#define unlikely(x) __builtin_expect((x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif diff --git a/include/metaprogramming.hpp b/include/metaprogramming.hpp new file mode 100644 index 00000000..e43decef --- /dev/null +++ b/include/metaprogramming.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace Helpers { + /// Used to make the compiler evaluate beeg loops at compile time for things like generating compile-time tables + template + static constexpr void static_for_impl(Func&& f, std::integer_sequence) { + (f(std::integral_constant{}), ...); + } + + template + static constexpr void static_for(Func&& f) { + static_for_impl(std::forward(f), std::make_integer_sequence{}); + } +} \ No newline at end of file From 747a9345e3a272fef7b568e9b26fe43920000e2f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:04:56 +0300 Subject: [PATCH 8/8] Some more thinning --- include/crypto/aes_engine.hpp | 2 +- include/helpers.hpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/include/crypto/aes_engine.hpp b/include/crypto/aes_engine.hpp index 3cb75bac..96ec900e 100644 --- a/include/crypto/aes_engine.hpp +++ b/include/crypto/aes_engine.hpp @@ -93,7 +93,7 @@ namespace Crypto { constexpr void updateNormalKey(std::size_t slotId) { if (m_generator.has_value() && hasKeyX(slotId) && hasKeyY(slotId)) { - auto &keySlot = m_slots.at(slotId); + auto& keySlot = m_slots.at(slotId); AESKey keyX = keySlot.keyX.value(); AESKey keyY = keySlot.keyY.value(); diff --git a/include/helpers.hpp b/include/helpers.hpp index 40e8c0b7..53c57c7c 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -104,9 +104,6 @@ namespace Helpers { return (value >> offset) & ones(); } - // For values < 0x99 - static constexpr inline u8 incBCDByte(u8 value) { return ((value & 0xf) == 0x9) ? value + 7 : value + 1; } - #ifdef HELPERS_APPLE_CLANG template constexpr To bit_cast(const From& from) noexcept {