From f6c2e390c1f08f804a9fb448f38733fe9db24f36 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 1 Oct 2022 16:21:05 +0300 Subject: [PATCH] More NCSD loading --- CMakeLists.txt | 6 ++-- include/emulator.hpp | 7 ++-- include/io_file.hpp | 6 ++-- include/loader/ncch.hpp | 6 +++- include/loader/ncsd.hpp | 1 + include/memory.hpp | 3 ++ src/core/{ => loader}/elf.cpp | 0 src/core/loader/ncch.cpp | 30 ++++++++++++++++ src/core/loader/ncsd.cpp | 68 +++++++++++++++++++++++++++++++++++ src/emulator.cpp | 26 ++++++++++---- src/main.cpp | 6 ++-- 11 files changed, 142 insertions(+), 17 deletions(-) rename src/core/{ => loader}/elf.cpp (100%) create mode 100644 src/core/loader/ncch.cpp create mode 100644 src/core/loader/ncsd.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7deabaef..4b840342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ else() message(FATAL_ERROR "THIS IS NOT x64 WAIT FOR THE KVM IMPLEMENTATION") endif() -set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/memory.cpp src/core/elf.cpp) +set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/memory.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 @@ -57,6 +57,7 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp src/core/PICA/shader_interpreter.cpp src/core/PICA/renderer_opengl.cpp ) +set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp) set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp include/termcolor.hpp include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/kernel/kernel.hpp @@ -80,11 +81,12 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp #add_library(Alber ${HEADER_FILES}) source_group("Header Files\\Core" FILES ${HEADER_FILES}) source_group("Source Files\\Core" FILES ${SOURCE_FILES}) +source_group("Source Files\\Loader" FILES ${LOADER_SOURCE_FILES}) source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES}) source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES}) source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES}) source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) -add_executable(Alber ${SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${PICA_SOURCE_FILES} +add_executable(Alber ${SOURCE_FILES} ${LOADER_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) target_link_libraries(Alber PRIVATE dynarmic SDL2-static) \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index 56c29967..3ef0ef68 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -10,7 +10,7 @@ #include "PICA/gpu.hpp" enum class ROMType { - None, ELF, Cart + None, ELF, NCSD }; class Emulator { @@ -28,7 +28,9 @@ class Emulator { bool running = true; // Keep the handle for the ROM here to reload when necessary and to prevent deleting it - std::ifstream loadedROM; + // 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()) { @@ -51,6 +53,7 @@ public: 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(); } diff --git a/include/io_file.hpp b/include/io_file.hpp index 3dea5737..d7b926ee 100644 --- a/include/io_file.hpp +++ b/include/io_file.hpp @@ -9,10 +9,12 @@ #define fseeko _fseeki64 #define ftello _ftelli64 #define fileno _fileno + +#pragma warning(disable : 4996) #endif -#ifdef _CRT_SECURE_NO_WARNINGS -#undef _CRT_SECURE_NO_WARNINGS +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS #endif class IOFile { diff --git a/include/loader/ncch.hpp b/include/loader/ncch.hpp index 993c29ce..45db24cb 100644 --- a/include/loader/ncch.hpp +++ b/include/loader/ncch.hpp @@ -1,15 +1,19 @@ +#pragma once #include "helpers.hpp" struct NCCH { bool isNew3DS = false; bool initialized = false; + bool compressExeFS = false; bool mountRomFS = false; bool encrypted = false; static constexpr u64 mediaUnit = 0x200; u64 size = 0; // Size of NCCH converted to bytes + u32 stackSize = 0; + u32 bssSize = 0; - // Header: 0x200 byte NCCH header + // Header: 0x200 + 0x800 byte NCCH header + exheadr // Returns true on success, false on failure bool loadFromHeader(u8* header); }; \ No newline at end of file diff --git a/include/loader/ncsd.hpp b/include/loader/ncsd.hpp index 2a8eddba..a4b35081 100644 --- a/include/loader/ncsd.hpp +++ b/include/loader/ncsd.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include "helpers.hpp" #include "io_file.hpp" diff --git a/include/memory.hpp b/include/memory.hpp index e979a8a8..cae97aca 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -1,11 +1,13 @@ #pragma once #include #include +#include #include #include #include #include "helpers.hpp" #include "handles.hpp" +#include "loader/ncsd.hpp" namespace PhysicalAddrs { enum : u32 { @@ -121,6 +123,7 @@ public: void* getReadPointer(u32 address); void* getWritePointer(u32 address); std::optional loadELF(std::ifstream& file); + std::optional loadNCSD(const std::filesystem::path& path); u8 read8(u32 vaddr); u16 read16(u32 vaddr); diff --git a/src/core/elf.cpp b/src/core/loader/elf.cpp similarity index 100% rename from src/core/elf.cpp rename to src/core/loader/elf.cpp diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp new file mode 100644 index 00000000..036dfcbf --- /dev/null +++ b/src/core/loader/ncch.cpp @@ -0,0 +1,30 @@ +#include "loader/ncch.hpp" + +bool NCCH::loadFromHeader(u8* header) { + const u8* exheader = &header[0x200]; // Extended NCCH header + + if (header[0x100] != 'N' || header[0x101] != 'C' || header[0x102] != 'C' || header[0x103] != 'H') { + printf("Invalid header on NCCH\n"); + return false; + } + + size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break + + // Read NCCH flags + isNew3DS = header[0x188 + 4] == 2; + mountRomFS = (header[0x188 + 7] & 0x1) != 0x1; + encrypted = (header[0x188 + 7] & 0x4) != 0x4; + + compressExeFS = (exheader[0xD] & 1) != 0; + + if (compressExeFS) { + Helpers::panic("Compressed ExeFS"); + } + + if (encrypted) { + Helpers::panic("Encrypted NCCH partition"); + } + + initialized = true; + return true; +} \ No newline at end of file diff --git a/src/core/loader/ncsd.cpp b/src/core/loader/ncsd.cpp new file mode 100644 index 00000000..b94d3221 --- /dev/null +++ b/src/core/loader/ncsd.cpp @@ -0,0 +1,68 @@ +#include +#include "loader/ncsd.hpp" +#include "memory.hpp" + +std::optional Memory::loadNCSD(const std::filesystem::path& path) { + NCSD ncsd; + if (!ncsd.file.open(path, "rb")) + return std::nullopt; + + u8 magic[4]; // Must be "NCSD" + ncsd.file.seek(0x100); + auto [success, bytes] = ncsd.file.readBytes(magic, 4); + + if (!success || bytes != 4) { + printf("Failed to read NCSD magic\n"); + return std::nullopt; + } + + if (magic[0] != 'N' || magic[1] != 'C' || magic[2] != 'S' || magic[3] != 'D') { + printf("NCSD with wrong magic value\n"); + return std::nullopt; + } + + std::tie(success, bytes) = ncsd.file.readBytes(&ncsd.size, 4); + if (!success || bytes != 4) { + printf("Failed to read NCSD size\n"); + return std::nullopt; + } + + ncsd.size *= NCSD::mediaUnit; // Convert size to bytes + + // Read partition data + ncsd.file.seek(0x120); + // 2 u32s per partition (offset and length), 8 partitions total + constexpr size_t partitionDataSize = 8 * 2; // Size of partition in u32s + u32 partitionData[8 * 2]; + std::tie(success, bytes) = ncsd.file.read(partitionData, partitionDataSize, sizeof(u32)); + if (!success || bytes != partitionDataSize) { + printf("Failed to read NCSD partition data\n"); + return std::nullopt; + } + + for (int i = 0; i < 8; i++) { + auto& partition = ncsd.partitions[i]; + partition.offset = u64(partitionData[i * 2]) * NCSD::mediaUnit; + partition.length = u64(partitionData[i * 2 + 1]) * NCSD::mediaUnit; + + if (partition.length != 0) { // Initialize the NCCH of each partition + ncsd.file.seek(partition.offset); + + // 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; + } + + if (!partition.ncch.loadFromHeader(ncchHeader)) { + printf("Invalid NCCH partition\n"); + return std::nullopt; + } + } + } + + return ncsd; +} \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index fe1b271b..d51d5222 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -11,7 +11,7 @@ void Emulator::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(loadedROM); + loadELF(loadedELF); } } @@ -54,26 +54,38 @@ bool Emulator::loadROM(const std::filesystem::path& path) { if (extension == ".elf" || extension == ".axf") return loadELF(path); else if (extension == ".3ds") - Helpers::panic("3DS file"); + 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(path); + + if (!opt.has_value()) { + return false; + } + + loadedNCSD = opt.value(); + return true; +} + bool Emulator::loadELF(const std::filesystem::path& path) { - loadedROM.open(path, std::ios_base::binary); // Open ROM in binary mode + loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode romType = ROMType::ELF; - return loadELF(loadedROM); + return loadELF(loadedELF); } bool Emulator::loadELF(std::ifstream& file) { // Rewind ifstream - loadedROM.clear(); - loadedROM.seekg(0); + loadedELF.clear(); + loadedELF.seekg(0); - std::optional entrypoint = memory.loadELF(loadedROM); + std::optional entrypoint = memory.loadELF(loadedELF); if (!entrypoint.has_value()) return false; diff --git a/src/main.cpp b/src/main.cpp index 9e8d6cd1..98bed483 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,10 +9,10 @@ int main (int argc, char *argv[]) { emu.initGraphicsContext(); - auto elfPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "Metroid2.3ds"); - if (!emu.loadROM(elfPath)) { + auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "SuperMario3DLand.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", elfPath.string().c_str()); + Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); } emu.run();