From 1251cecc8805c625e399cd5683cac8b78421f06f Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Mon, 19 Jun 2023 23:31:14 -0700 Subject: [PATCH 01/17] Add `setObjectLabel` for naming OpenGL functions Add `OPENGL_PRINTF_FORMAT` and `OPENGL_PRINTF_FORMAT_ATTR` macros for providing printf diagnostic information across platforms. --- include/opengl.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/opengl.hpp b/include/opengl.hpp index 16ed8221..9ecbc4c6 100644 --- a/include/opengl.hpp +++ b/include/opengl.hpp @@ -20,6 +20,7 @@ #pragma once #include #include +#include #include #include #include @@ -43,6 +44,15 @@ #include #endif +#ifdef _MSC_VER +#include +#define OPENGL_PRINTF_FORMAT _Printf_format_string_ +#define OPENGL_PRINTF_FORMAT_ATTR(format_arg_index, dots_arg_index) +#else +#define OPENGL_PRINTF_FORMAT +#define OPENGL_PRINTF_FORMAT_ATTR(format_arg_index, dots_arg_index) __attribute__((__format__(__printf__, format_arg_index, dots_arg_index))) +#endif + // Uncomment the following define if you want GL objects to automatically free themselves when their lifetime ends // #define OPENGL_DESTRUCTORS @@ -53,6 +63,16 @@ namespace OpenGL { template constexpr std::false_type AlwaysFalse{}; + OPENGL_PRINTF_FORMAT_ATTR(3, 4) + static void setObjectLabel(GLenum identifier, GLuint name, OPENGL_PRINTF_FORMAT const char* format, ...) { + GLchar label[256] = {}; + va_list args; + va_start(args, format); + const GLsizei length = vsnprintf(label, std::size(label), format, args); + va_end(args); + glObjectLabel(identifier, name, length, label); + } + struct VertexArray { GLuint m_handle = 0; From dbf0597bd82a0b44fc7ec0b11ced537f55096873 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 20 Jun 2023 00:24:51 -0700 Subject: [PATCH 02/17] Add `DebugScope` utility-class for RAII-based OpenGL debug-scopes Simply define this object in a C++ scope like: ```cpp OpenGL::DebugScope glScope("Renderer::display"); ``` and it will associate all functions within the current scope within a named group. Supports `printf` formatting. --- include/opengl.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/opengl.hpp b/include/opengl.hpp index 9ecbc4c6..42167388 100644 --- a/include/opengl.hpp +++ b/include/opengl.hpp @@ -73,6 +73,26 @@ namespace OpenGL { glObjectLabel(identifier, name, length, label); } + class DebugScope { + inline static GLuint scopeDepth = 0; + const GLuint m_scope_depth; + + public: + OPENGL_PRINTF_FORMAT_ATTR(2, 3) + DebugScope(OPENGL_PRINTF_FORMAT const char* format, ...) : m_scope_depth(scopeDepth++) { + GLchar message[256] = {}; + va_list args; + va_start(args, format); + const GLsizei length = vsnprintf(message, std::size(message), format, args); + va_end(args); + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, m_scope_depth, length, message); + } + ~DebugScope() { + glPopDebugGroup(); + scopeDepth--; + } + }; + struct VertexArray { GLuint m_handle = 0; From 62fdb296460409821b22ab0c28068e726f404767 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 20 Jun 2023 11:04:48 -0700 Subject: [PATCH 03/17] Add `GPU_DEBUG_INFO` CMake-Option Adds a project-wide setting for allowing renderer code to add additional diagnostic data. Currently used to allow `opengl.hpp` to conditionally implement debug-labeling and scopes. --- CMakeLists.txt | 9 ++++++++- include/opengl.hpp | 25 +++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34542f84..74c2d429 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ endif() project(Alber) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF) + include_directories(${PROJECT_SOURCE_DIR}/include/) include_directories(${PROJECT_SOURCE_DIR}/include/kernel) include_directories (${FMT_INCLUDE_DIR}) @@ -138,4 +140,9 @@ 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} ${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) -target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad) \ No newline at end of file +target_link_libraries(Alber PRIVATE dynarmic SDL2-static glad) + + +if(GPU_DEBUG_INFO) + target_compile_definitions(Alber PRIVATE GPU_DEBUG_INFO=1) +endif() \ No newline at end of file diff --git a/include/opengl.hpp b/include/opengl.hpp index 42167388..67c45689 100644 --- a/include/opengl.hpp +++ b/include/opengl.hpp @@ -44,6 +44,10 @@ #include #endif +#if defined(GPU_DEBUG_INFO) +#define OPENGL_DEBUG_INFO +#endif + #ifdef _MSC_VER #include #define OPENGL_PRINTF_FORMAT _Printf_format_string_ @@ -63,23 +67,29 @@ namespace OpenGL { template constexpr std::false_type AlwaysFalse{}; - OPENGL_PRINTF_FORMAT_ATTR(3, 4) + OPENGL_PRINTF_FORMAT_ATTR(3, 4) static void setObjectLabel(GLenum identifier, GLuint name, OPENGL_PRINTF_FORMAT const char* format, ...) { +#if defined(OPENGL_DEBUG_INFO) GLchar label[256] = {}; va_list args; va_start(args, format); const GLsizei length = vsnprintf(label, std::size(label), format, args); va_end(args); glObjectLabel(identifier, name, length, label); +#endif } - class DebugScope { + class DebugScope { +#if defined(OPENGL_DEBUG_INFO) inline static GLuint scopeDepth = 0; const GLuint m_scope_depth; +#endif public: OPENGL_PRINTF_FORMAT_ATTR(2, 3) - DebugScope(OPENGL_PRINTF_FORMAT const char* format, ...) : m_scope_depth(scopeDepth++) { + DebugScope(OPENGL_PRINTF_FORMAT const char* format, ...) +#if defined(OPENGL_DEBUG_INFO) + : m_scope_depth(scopeDepth++) { GLchar message[256] = {}; va_list args; va_start(args, format); @@ -87,13 +97,20 @@ namespace OpenGL { va_end(args); glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, m_scope_depth, length, message); } +#else + { + } +#endif + ~DebugScope() { +#if defined(OPENGL_DEBUG_INFO) glPopDebugGroup(); scopeDepth--; +#endif } }; - struct VertexArray { + struct VertexArray { GLuint m_handle = 0; void create() { From 2e5bc0cb14d79a4626e09ed2fee7e24fc2d25d87 Mon Sep 17 00:00:00 2001 From: Mary Date: Mon, 19 Jun 2023 23:13:20 +0200 Subject: [PATCH 04/17] 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 05/17] 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 06/17] 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 07/17] 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 08/17] 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 09/17] [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 10/17] 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 11/17] 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 { From 71dddc002014ff9a859e7700f609602f035f896f Mon Sep 17 00:00:00 2001 From: Nadia Holmquist Pedersen Date: Tue, 27 Jun 2023 22:40:38 +0200 Subject: [PATCH 12/17] Add basic controller input using the SDL2 GameController API --- include/emulator.hpp | 20 +++++++- include/services/hid.hpp | 10 +++- include/services/service_manager.hpp | 8 ++-- src/emulator.cpp | 70 +++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 46aa0ade..37429358 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -23,6 +23,8 @@ class Emulator { SDL_Window* window; SDL_GLContext glContext; + SDL_GameController* gameController; + int gameControllerID; static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens @@ -40,6 +42,13 @@ public: Helpers::panic("Failed to initialize SDL2"); } + // Make SDL use consistent positional button mapping + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); + + if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { + Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError()); + } + // Request OpenGL 4.1 Core (Max available on MacOS) // MacOS gets mad if we don't explicitly demand a core profile SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); @@ -61,6 +70,15 @@ public: Helpers::panic("OpenGL init failed: %s", SDL_GetError()); } + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + gameController = SDL_GameControllerOpen(0); + + if (gameController != nullptr) { + SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); + gameControllerID = SDL_JoystickInstanceID(stick); + } + } + reset(); } @@ -75,4 +93,4 @@ public: bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); void initGraphicsContext() { gpu.initGraphicsContext(); } -}; \ No newline at end of file +}; diff --git a/include/services/hid.hpp b/include/services/hid.hpp index ea36bb0d..28b840cb 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -87,6 +87,14 @@ public: void pressKey(u32 mask) { newButtons |= mask; } void releaseKey(u32 mask) { newButtons &= ~mask; } + s16 getCirclepadX() { + return circlePadX; + } + + s16 getCirclepadY() { + return circlePadY; + } + void setCirclepadX(s16 x) { circlePadX = x; @@ -129,4 +137,4 @@ public: void releaseTouchScreen() { touchScreenPressed = false; } -}; \ No newline at end of file +}; diff --git a/include/services/service_manager.hpp b/include/services/service_manager.hpp index e09fd455..ee2677ad 100644 --- a/include/services/service_manager.hpp +++ b/include/services/service_manager.hpp @@ -90,9 +90,11 @@ class ServiceManager { // Input function wrappers void pressKey(u32 key) { hid.pressKey(key); } void releaseKey(u32 key) { hid.releaseKey(key); } - void setCirclepadX(u16 x) { hid.setCirclepadX(x); } - void setCirclepadY(u16 y) { hid.setCirclepadY(y); } + s16 getCirclepadX() { return hid.getCirclepadX(); } + s16 getCirclepadY() { return hid.getCirclepadY(); } + void setCirclepadX(s16 x) { hid.setCirclepadX(x); } + void setCirclepadY(s16 y) { hid.setCirclepadY(y); } void updateInputs(u64 currentTimestamp) { hid.updateInputs(currentTimestamp); } void setTouchScreenPress(u16 x, u16 y) { hid.setTouchScreenPress(x, y); } void releaseTouchScreen() { hid.releaseTouchScreen(); } -}; \ No newline at end of file +}; diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..ba8fdb3f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -117,7 +117,75 @@ void Emulator::run() { srv.releaseTouchScreen(); } break; + + case SDL_CONTROLLERDEVICEADDED: + if (gameController != nullptr) { + break; + } + + gameController = SDL_GameControllerOpen(event.cdevice.which); + break; + + case SDL_CONTROLLERDEVICEREMOVED: + if (event.cdevice.which == gameControllerID) { + SDL_GameControllerClose(gameController); + gameController = nullptr; + gameControllerID = 0; + } + + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: { + u32 key = 0; + + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; + case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; + case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; + case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; + case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; + case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; + } + + if (key != 0) { + if (event.cbutton.state == SDL_PRESSED) { + srv.pressKey(key); + } else { + srv.releaseKey(key); + } + } } + } + } + + if (gameController != nullptr) { + const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + const s16 deadzone = 3276; + const s16 maxValue = 0x9C; + const s16 div = 0x8000 / maxValue; + + if (abs(stickX) < deadzone) { + // Avoid overriding the keyboard's circlepad input + if (abs(srv.getCirclepadX()) != maxValue) { + srv.setCirclepadX(0); + } + } else { + srv.setCirclepadX(stickX / div); + } + + if (abs(stickY) < deadzone) { + if (abs(srv.getCirclepadY()) != maxValue) { + srv.setCirclepadY(0); + } + } else { + srv.setCirclepadY(-(stickY / div)); + } } // Update inputs in the HID module @@ -191,4 +259,4 @@ bool Emulator::loadELF(std::ifstream& file) { Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode"); } return true; -} \ No newline at end of file +} From bc4e71bf409063a3f4e5f11849d80a32b3d10af5 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 28 Jun 2023 00:58:40 +0300 Subject: [PATCH 13/17] [Controller] Fix breaks, constexpr --- src/emulator.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 5a7940be..b973a28e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -119,11 +119,9 @@ void Emulator::run() { break; case SDL_CONTROLLERDEVICEADDED: - if (gameController != nullptr) { - break; + if (gameController == nullptr) { + gameController = SDL_GameControllerOpen(event.cdevice.which); } - - gameController = SDL_GameControllerOpen(event.cdevice.which); break; case SDL_CONTROLLERDEVICEREMOVED: @@ -132,6 +130,7 @@ void Emulator::run() { gameController = nullptr; gameControllerID = 0; } + break; case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONDOWN: { @@ -166,9 +165,9 @@ void Emulator::run() { if (gameController != nullptr) { const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); - const s16 deadzone = 3276; - const s16 maxValue = 0x9C; - const s16 div = 0x8000 / maxValue; + constexpr s16 deadzone = 3276; + constexpr s16 maxValue = 0x9C; + constexpr s16 div = 0x8000 / maxValue; if (abs(stickX) < deadzone) { // Avoid overriding the keyboard's circlepad input From 71582eff6d022ac7a6cbe5c9e7a54392914bf65c Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:40:23 +0300 Subject: [PATCH 14/17] [Controller] Slightly better analog handling --- include/emulator.hpp | 8 +++-- src/emulator.cpp | 76 +++++++++++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 81502960..5c55fc90 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -28,6 +28,12 @@ class Emulator { SDL_GameController* gameController; int gameControllerID; + // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard + // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state + // And so the user can still use the keyboard to control the analog + bool keyboardAnalogX = false; + bool keyboardAnalogY = false; + static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; @@ -46,7 +52,6 @@ public: // Make SDL use consistent positional button mapping SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); - if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError()); } @@ -63,7 +68,6 @@ public: } glContext = SDL_GL_CreateContext(window); - if (glContext == nullptr) { Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); } diff --git a/src/emulator.cpp b/src/emulator.cpp index b973a28e..7eccc82c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -57,10 +57,25 @@ void Emulator::run() { case SDLK_UP: srv.pressKey(Keys::Up); break; case SDLK_DOWN: srv.pressKey(Keys::Down); break; - case SDLK_w: srv.setCirclepadY(0x9C); break; - case SDLK_a: srv.setCirclepadX(-0x9C); break; - case SDLK_s: srv.setCirclepadY(-0x9C); break; - case SDLK_d: srv.setCirclepadX(0x9C); break; + case SDLK_w: + srv.setCirclepadY(0x9C); + keyboardAnalogY = true; + break; + + case SDLK_a: + srv.setCirclepadX(-0x9C); + keyboardAnalogX = true; + break; + + case SDLK_s: + srv.setCirclepadY(-0x9C); + keyboardAnalogY = true; + break; + + case SDLK_d: + srv.setCirclepadX(0x9C); + keyboardAnalogX = true; + break; case SDLK_RETURN: srv.pressKey(Keys::Start); break; case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break; @@ -82,10 +97,17 @@ void Emulator::run() { case SDLK_DOWN: srv.releaseKey(Keys::Down); break; // Err this is probably not ideal - case SDLK_w: srv.setCirclepadY(0); break; - case SDLK_a: srv.setCirclepadX(0); break; - case SDLK_s: srv.setCirclepadY(0); break; - case SDLK_d: srv.setCirclepadX(0); break; + case SDLK_w: + case SDLK_s: + srv.setCirclepadY(0); + keyboardAnalogY = false; + break; + + case SDLK_a: + case SDLK_d: + srv.setCirclepadX(0); + keyboardAnalogX = false; + break; case SDLK_RETURN: srv.releaseKey(Keys::Start); break; case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break; @@ -163,29 +185,25 @@ void Emulator::run() { } if (gameController != nullptr) { - const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); - const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); - constexpr s16 deadzone = 3276; - constexpr s16 maxValue = 0x9C; - constexpr s16 div = 0x8000 / maxValue; + const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + constexpr s16 deadzone = 3276; + constexpr s16 maxValue = 0x9C; + constexpr s16 div = 0x8000 / maxValue; - if (abs(stickX) < deadzone) { - // Avoid overriding the keyboard's circlepad input - if (abs(srv.getCirclepadX()) != maxValue) { - srv.setCirclepadX(0); - } - } else { - srv.setCirclepadX(stickX / div); - } + // Avoid overriding the keyboard's circlepad input + if (abs(stickX) < deadzone && !keyboardAnalogX) { + srv.setCirclepadX(0); + } else { + srv.setCirclepadX(stickX / div); + } - if (abs(stickY) < deadzone) { - if (abs(srv.getCirclepadY()) != maxValue) { - srv.setCirclepadY(0); - } - } else { - srv.setCirclepadY(-(stickY / div)); - } - } + if (abs(stickY) < deadzone && !keyboardAnalogY) { + srv.setCirclepadY(0); + } else { + srv.setCirclepadY(-(stickY / div)); + } + } // Update inputs in the HID module srv.updateInputs(cpu.getTicks()); From 29806ff8e58cef2429346eb862bc485ef4d903b2 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:48:34 +0300 Subject: [PATCH 15/17] clang-format --- include/emulator.hpp | 141 ++++++++------- include/services/hid.hpp | 9 +- src/emulator.cpp | 373 +++++++++++++++++++-------------------- 3 files changed, 257 insertions(+), 266 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 5c55fc90..59a7a293 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -1,102 +1,101 @@ #pragma once -#include -#include #include #include +#include +#include + +#include "PICA/gpu.hpp" #include "cpu.hpp" #include "crypto/aes_engine.hpp" #include "io_file.hpp" #include "memory.hpp" #include "opengl.hpp" -#include "PICA/gpu.hpp" -enum class ROMType { - None, ELF, NCSD -}; +enum class ROMType { None, ELF, NCSD }; class Emulator { - CPU cpu; - GPU gpu; - Memory memory; - Kernel kernel; - Crypto::AESEngine aesEngine; + CPU cpu; + GPU gpu; + Memory memory; + Kernel kernel; + Crypto::AESEngine aesEngine; - SDL_Window* window; - SDL_GLContext glContext; - SDL_GameController* gameController; - int gameControllerID; + SDL_Window* window; + SDL_GLContext glContext; + SDL_GameController* gameController; + int gameControllerID; - // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard - // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state - // And so the user can still use the keyboard to control the analog + // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard + // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state + // And so the user can still use the keyboard to control the analog bool keyboardAnalogX = false; bool keyboardAnalogY = false; - static constexpr u32 width = 400; - static constexpr u32 height = 240 * 2; // * 2 because 2 screens - ROMType romType = ROMType::None; - bool running = true; + static constexpr u32 width = 400; + static constexpr u32 height = 240 * 2; // * 2 because 2 screens + ROMType romType = ROMType::None; + bool running = true; - // Keep the handle for the ROM here to reload when necessary and to prevent deleting it - // This is currently only used for ELFs, NCSDs use the IOFile API instead - std::ifstream loadedELF; - NCSD loadedNCSD; + // Keep the handle for the ROM here to reload when necessary and to prevent deleting it + // This is currently only used for ELFs, NCSDs use the IOFile API instead + std::ifstream loadedELF; + NCSD loadedNCSD; -public: - Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory), memory(cpu.getTicksRef()) { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { - Helpers::panic("Failed to initialize SDL2"); - } + public: + Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory), memory(cpu.getTicksRef()) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { + Helpers::panic("Failed to initialize SDL2"); + } - // Make SDL use consistent positional button mapping - SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); - if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { - Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError()); - } + // Make SDL use consistent positional button mapping + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); + if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { + Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError()); + } - // Request OpenGL 4.1 Core (Max available on MacOS) - // MacOS gets mad if we don't explicitly demand a core profile - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); + // Request OpenGL 4.1 Core (Max available on MacOS) + // MacOS gets mad if we don't explicitly demand a core profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); - if (window == nullptr) { - Helpers::panic("Window creation failed: %s", SDL_GetError()); - } + if (window == nullptr) { + Helpers::panic("Window creation failed: %s", SDL_GetError()); + } - glContext = SDL_GL_CreateContext(window); - if (glContext == nullptr) { - Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); - } + glContext = SDL_GL_CreateContext(window); + if (glContext == nullptr) { + Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); + } - if(!gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress))) { - Helpers::panic("OpenGL init failed: %s", SDL_GetError()); - } + if (!gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress))) { + Helpers::panic("OpenGL init failed: %s", SDL_GetError()); + } - if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { - gameController = SDL_GameControllerOpen(0); + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + gameController = SDL_GameControllerOpen(0); - if (gameController != nullptr) { - SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); - gameControllerID = SDL_JoystickInstanceID(stick); - } - } + if (gameController != nullptr) { + SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); + gameControllerID = SDL_JoystickInstanceID(stick); + } + } - reset(); - } + reset(); + } - void step(); - void render(); - void reset(); - void run(); - void runFrame(); + void step(); + void render(); + void reset(); + void run(); + void runFrame(); - bool loadROM(const std::filesystem::path& path); - bool loadNCSD(const std::filesystem::path& path); - bool loadELF(const std::filesystem::path& path); - bool loadELF(std::ifstream& file); - void initGraphicsContext() { gpu.initGraphicsContext(); } + bool loadROM(const std::filesystem::path& path); + bool loadNCSD(const std::filesystem::path& path); + bool loadELF(const std::filesystem::path& path); + bool loadELF(std::ifstream& file); + void initGraphicsContext() { gpu.initGraphicsContext(); } }; diff --git a/include/services/hid.hpp b/include/services/hid.hpp index 28b840cb..a560c13f 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -87,13 +87,8 @@ public: void pressKey(u32 mask) { newButtons |= mask; } void releaseKey(u32 mask) { newButtons &= ~mask; } - s16 getCirclepadX() { - return circlePadX; - } - - s16 getCirclepadY() { - return circlePadY; - } + s16 getCirclepadX() { return circlePadX; } + s16 getCirclepadY() { return circlePadY; } void setCirclepadX(s16 x) { circlePadX = x; diff --git a/src/emulator.cpp b/src/emulator.cpp index 7eccc82c..7138c7a4 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,63 +1,61 @@ #include "emulator.hpp" void Emulator::reset() { - cpu.reset(); - gpu.reset(); - memory.reset(); - // Kernel must be reset last because it depends on CPU/Memory state - kernel.reset(); + cpu.reset(); + gpu.reset(); + memory.reset(); + // Kernel must be reset last because it depends on CPU/Memory state + kernel.reset(); - // Reloading r13 and r15 needs to happen after everything has been reset - // Otherwise resetting the kernel or cpu might nuke them - cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP - if (romType == ROMType::ELF) { // Reload ELF if we're using one - loadELF(loadedELF); - } + // Reloading r13 and r15 needs to happen after everything has been reset + // Otherwise resetting the kernel or cpu might nuke them + cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP + if (romType == ROMType::ELF) { // Reload ELF if we're using one + loadELF(loadedELF); + } } void Emulator::step() {} - -void Emulator::render() { - -} +void Emulator::render() {} void Emulator::run() { - while (running) { - gpu.getGraphicsContext(); // Give the GPU a rendering context - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics + while (running) { + gpu.getGraphicsContext(); // Give the GPU a rendering context + runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics - ServiceManager& srv = kernel.getServiceManager(); + ServiceManager& srv = kernel.getServiceManager(); - // Send VBlank interrupts - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + // Send VBlank interrupts + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); - SDL_Event event; - while (SDL_PollEvent(&event)) { - namespace Keys = HID::Keys; + SDL_Event event; + while (SDL_PollEvent(&event)) { + namespace Keys = HID::Keys; - switch (event.type) { - case SDL_QUIT: - printf("Bye :(\n"); - running = false; - return; - case SDL_KEYDOWN: - switch (event.key.keysym.sym) { - case SDLK_l: srv.pressKey(Keys::A); break; - case SDLK_k: srv.pressKey(Keys::B); break; - case SDLK_o: srv.pressKey(Keys::X); break; - case SDLK_i: srv.pressKey(Keys::Y); break; + switch (event.type) { + case SDL_QUIT: + printf("Bye :(\n"); + running = false; + return; - case SDLK_q: srv.pressKey(Keys::L); break; - case SDLK_p: srv.pressKey(Keys::R); break; + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_l: srv.pressKey(Keys::A); break; + case SDLK_k: srv.pressKey(Keys::B); break; + case SDLK_o: srv.pressKey(Keys::X); break; + case SDLK_i: srv.pressKey(Keys::Y); break; - case SDLK_RIGHT: srv.pressKey(Keys::Right); break; - case SDLK_LEFT: srv.pressKey(Keys::Left); break; - case SDLK_UP: srv.pressKey(Keys::Up); break; - case SDLK_DOWN: srv.pressKey(Keys::Down); break; + case SDLK_q: srv.pressKey(Keys::L); break; + case SDLK_p: srv.pressKey(Keys::R); break; - case SDLK_w: + case SDLK_RIGHT: srv.pressKey(Keys::Right); break; + case SDLK_LEFT: srv.pressKey(Keys::Left); break; + case SDLK_UP: srv.pressKey(Keys::Up); break; + case SDLK_DOWN: srv.pressKey(Keys::Down); break; + + case SDLK_w: srv.setCirclepadY(0x9C); keyboardAnalogY = true; break; @@ -77,26 +75,27 @@ void Emulator::run() { keyboardAnalogX = true; break; - case SDLK_RETURN: srv.pressKey(Keys::Start); break; - case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break; - } - break; - case SDL_KEYUP: - switch (event.key.keysym.sym) { - case SDLK_l: srv.releaseKey(Keys::A); break; - case SDLK_k: srv.releaseKey(Keys::B); break; - case SDLK_o: srv.releaseKey(Keys::X); break; - case SDLK_i: srv.releaseKey(Keys::Y); break; + case SDLK_RETURN: srv.pressKey(Keys::Start); break; + case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break; + } + break; - case SDLK_q: srv.releaseKey(Keys::L); break; - case SDLK_p: srv.releaseKey(Keys::R); break; + case SDL_KEYUP: + switch (event.key.keysym.sym) { + case SDLK_l: srv.releaseKey(Keys::A); break; + case SDLK_k: srv.releaseKey(Keys::B); break; + case SDLK_o: srv.releaseKey(Keys::X); break; + case SDLK_i: srv.releaseKey(Keys::Y); break; - case SDLK_RIGHT: srv.releaseKey(Keys::Right); break; - case SDLK_LEFT: srv.releaseKey(Keys::Left); break; - case SDLK_UP: srv.releaseKey(Keys::Up); break; - case SDLK_DOWN: srv.releaseKey(Keys::Down); break; + case SDLK_q: srv.releaseKey(Keys::L); break; + case SDLK_p: srv.releaseKey(Keys::R); break; - // Err this is probably not ideal + case SDLK_RIGHT: srv.releaseKey(Keys::Right); break; + case SDLK_LEFT: srv.releaseKey(Keys::Left); break; + case SDLK_UP: srv.releaseKey(Keys::Up); break; + case SDLK_DOWN: srv.releaseKey(Keys::Down); break; + + // Err this is probably not ideal case SDLK_w: case SDLK_s: srv.setCirclepadY(0); @@ -109,82 +108,81 @@ void Emulator::run() { keyboardAnalogX = false; break; - case SDLK_RETURN: srv.releaseKey(Keys::Start); break; - case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break; - } - break; - - case SDL_MOUSEBUTTONDOWN: { - if (event.button.button == SDL_BUTTON_LEFT) { - const s32 x = event.button.x; - const s32 y = event.button.y; - - // Check if touch falls in the touch screen area - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - srv.setTouchScreenPress(x_converted, y_converted); - } - else { - srv.releaseTouchScreen(); - } - } - break; - } - - case SDL_MOUSEBUTTONUP: - if (event.button.button == SDL_BUTTON_LEFT) { - srv.releaseTouchScreen(); - } - break; - - case SDL_CONTROLLERDEVICEADDED: - if (gameController == nullptr) { - gameController = SDL_GameControllerOpen(event.cdevice.which); - } - break; - - case SDL_CONTROLLERDEVICEREMOVED: - if (event.cdevice.which == gameControllerID) { - SDL_GameControllerClose(gameController); - gameController = nullptr; - gameControllerID = 0; - } + case SDLK_RETURN: srv.releaseKey(Keys::Start); break; + case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break; + } break; - case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERBUTTONDOWN: { - u32 key = 0; + case SDL_MOUSEBUTTONDOWN: { + if (event.button.button == SDL_BUTTON_LEFT) { + const s32 x = event.button.x; + const s32 y = event.button.y; - switch (event.cbutton.button) { - case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; - case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; - case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; - case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; - case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; - case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; - case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; - } + // Check if touch falls in the touch screen area + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; - if (key != 0) { - if (event.cbutton.state == SDL_PRESSED) { - srv.pressKey(key); - } else { - srv.releaseKey(key); - } - } - } - } - } + srv.setTouchScreenPress(x_converted, y_converted); + } else { + srv.releaseTouchScreen(); + } + } + break; + } - if (gameController != nullptr) { + case SDL_MOUSEBUTTONUP: + if (event.button.button == SDL_BUTTON_LEFT) { + srv.releaseTouchScreen(); + } + break; + + case SDL_CONTROLLERDEVICEADDED: + if (gameController == nullptr) { + gameController = SDL_GameControllerOpen(event.cdevice.which); + } + break; + + case SDL_CONTROLLERDEVICEREMOVED: + if (event.cdevice.which == gameControllerID) { + SDL_GameControllerClose(gameController); + gameController = nullptr; + gameControllerID = 0; + } + break; + + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: { + u32 key = 0; + + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; + case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; + case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; + case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; + case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; + case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; + } + + if (key != 0) { + if (event.cbutton.state == SDL_PRESSED) { + srv.pressKey(key); + } else { + srv.releaseKey(key); + } + } + } + } + } + + if (gameController != nullptr) { const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); constexpr s16 deadzone = 3276; @@ -205,84 +203,83 @@ void Emulator::run() { } } - // Update inputs in the HID module - srv.updateInputs(cpu.getTicks()); - SDL_GL_SwapWindow(window); - } + // Update inputs in the HID module + srv.updateInputs(cpu.getTicks()); + SDL_GL_SwapWindow(window); + } } -void Emulator::runFrame() { - cpu.runFrame(); -} +void Emulator::runFrame() { cpu.runFrame(); } bool Emulator::loadROM(const std::filesystem::path& path) { - // Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc) - // 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 appDataPath = std::filesystem::path(appData); - const std::filesystem::path dataPath = appDataPath / path.filename().stem(); + // Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc) + // 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 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); + IOFile::setAppDataDir(dataPath); SDL_free(appData); - // 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); - } + // 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); + } - kernel.initializeFS(); - auto extension = path.extension(); - - if (extension == ".elf" || extension == ".axf") - return loadELF(path); - else if (extension == ".3ds") - return loadNCSD(path); - else { - printf("Unknown file type\n"); - return false; - } + kernel.initializeFS(); + auto extension = path.extension(); + + if (extension == ".elf" || extension == ".axf") + return loadELF(path); + else if (extension == ".3ds") + return loadNCSD(path); + else { + printf("Unknown file type\n"); + return false; + } } bool Emulator::loadNCSD(const std::filesystem::path& path) { - romType = ROMType::NCSD; - std::optional opt = memory.loadNCSD(aesEngine, path); + romType = ROMType::NCSD; + std::optional opt = memory.loadNCSD(aesEngine, path); - if (!opt.has_value()) { - return false; - } + if (!opt.has_value()) { + return false; + } - loadedNCSD = opt.value(); - cpu.setReg(15, loadedNCSD.entrypoint); + loadedNCSD = opt.value(); + cpu.setReg(15, loadedNCSD.entrypoint); - if (loadedNCSD.entrypoint & 1) { - Helpers::panic("Misaligned NCSD entrypoint; should this start the CPU in Thumb mode?"); - } + if (loadedNCSD.entrypoint & 1) { + Helpers::panic("Misaligned NCSD entrypoint; should this start the CPU in Thumb mode?"); + } - return true; + return true; } bool Emulator::loadELF(const std::filesystem::path& path) { - loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode - romType = ROMType::ELF; + loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode + romType = ROMType::ELF; - return loadELF(loadedELF); + return loadELF(loadedELF); } bool Emulator::loadELF(std::ifstream& file) { - // Rewind ifstream - loadedELF.clear(); - loadedELF.seekg(0); + // Rewind ifstream + loadedELF.clear(); + loadedELF.seekg(0); - std::optional entrypoint = memory.loadELF(loadedELF); - if (!entrypoint.has_value()) - return false; + std::optional entrypoint = memory.loadELF(loadedELF); + if (!entrypoint.has_value()) { + return false; + } - cpu.setReg(15, entrypoint.value()); // Set initial PC - if (entrypoint.value() & 1) { - Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode"); - } - return true; + cpu.setReg(15, entrypoint.value()); // Set initial PC + if (entrypoint.value() & 1) { + Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode"); + } + return true; } From eff25180d5422b223a7d116f7d9e8de612b3b20c Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:51:21 +0300 Subject: [PATCH 16/17] [Controller] Set gameControllerID if a gamepad is connected mid-gameplay --- src/emulator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emulator.cpp b/src/emulator.cpp index 7138c7a4..3a862231 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -141,6 +141,7 @@ void Emulator::run() { case SDL_CONTROLLERDEVICEADDED: if (gameController == nullptr) { gameController = SDL_GameControllerOpen(event.cdevice.which); + gameControllerID = event.cdevice.which; } break; From e86aec0c337fb35575282b10639ac876c2e0bc26 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:53:26 +0300 Subject: [PATCH 17/17] More clang-format --- include/services/hid.hpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/include/services/hid.hpp b/include/services/hid.hpp index a560c13f..beadf7f1 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -1,6 +1,7 @@ #pragma once #include #include + #include "helpers.hpp" #include "kernel_types.hpp" #include "logger.hpp" @@ -22,12 +23,12 @@ namespace HID::Keys { X = 1 << 10, Y = 1 << 11, - GPIO0Inv = 1 << 12, // Inverted value of GPIO bit 0 - GPIO14Inv = 1 << 13, // Inverted value of GPIO bit 14 + GPIO0Inv = 1 << 12, // Inverted value of GPIO bit 0 + GPIO14Inv = 1 << 13, // Inverted value of GPIO bit 14 - CirclePadRight = 1 << 28, // X >= 41 - CirclePadLeft = 1 << 29, // X <= -41 - CirclePadUp = 1 << 30, // Y >= 41 + CirclePadRight = 1 << 28, // X >= 41 + CirclePadLeft = 1 << 29, // X <= -41 + CirclePadUp = 1 << 30, // Y >= 41 CirclePadDown = 1u << 31 // Y <= -41 }; } @@ -39,18 +40,18 @@ class HIDService { Handle handle = KernelHandles::HID; Memory& mem; Kernel& kernel; - u8* sharedMem = nullptr; // Pointer to HID shared memory + u8* sharedMem = nullptr; // Pointer to HID shared memory uint nextPadIndex; uint nextTouchscreenIndex; uint nextAccelerometerIndex; uint nextGyroIndex; - u32 newButtons; // The button state currently being edited - u32 oldButtons; // The previous pad state + u32 newButtons; // The button state currently being edited + u32 oldButtons; // The previous pad state - s16 circlePadX, circlePadY; // Circlepad state - s16 touchScreenX, touchScreenY; // Touchscreen state + s16 circlePadX, circlePadY; // Circlepad state + s16 touchScreenX, touchScreenY; // Touchscreen state bool accelerometerEnabled; bool eventsInitialized; @@ -79,7 +80,7 @@ class HIDService { *(T*)&sharedMem[offset] = value; } -public: + public: HIDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); @@ -87,7 +88,7 @@ public: void pressKey(u32 mask) { newButtons |= mask; } void releaseKey(u32 mask) { newButtons &= ~mask; } - s16 getCirclepadX() { return circlePadX; } + s16 getCirclepadX() { return circlePadX; } s16 getCirclepadY() { return circlePadY; } void setCirclepadX(s16 x) { @@ -96,9 +97,9 @@ public: // Turn bits 28 and 29 off in the new button state, which indicate whether the circlepad is steering left or right // Then, set them according to the new value of x newButtons &= ~0x3000'0000; - if (x >= 41) // Pressing right + if (x >= 41) // Pressing right newButtons |= 1 << 28; - else if (x <= -41) // Pressing left + else if (x <= -41) // Pressing left newButtons |= 1 << 29; } @@ -108,9 +109,9 @@ public: // Turn bits 30 and 31 off in the new button state, which indicate whether the circlepad is steering up or down // Then, set them according to the new value of y newButtons &= ~0xC000'0000; - if (y >= 41) // Pressing up + if (y >= 41) // Pressing up newButtons |= 1 << 30; - else if (y <= -41) // Pressing down + else if (y <= -41) // Pressing down newButtons |= 1 << 31; } @@ -118,7 +119,7 @@ public: void setSharedMem(u8* ptr) { sharedMem = ptr; - if (ptr != nullptr) { // Zero-fill shared memory in case the process tries to read stale service data or vice versa + if (ptr != nullptr) { // Zero-fill shared memory in case the process tries to read stale service data or vice versa std::memset(ptr, 0, 0x2b0); } }