From 29352d223bf2bdef523fd1ce1517c2b39ee4b151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20B?= Date: Sat, 2 Sep 2023 23:49:11 +0200 Subject: [PATCH] feat: 3dsx loading romFS works too, pretty neat --- CMakeLists.txt | 4 +- include/emulator.hpp | 2 + include/fs/archive_self_ncch.hpp | 3 +- include/loader/3dsx.hpp | 79 ++++++++ include/memory.hpp | 12 ++ src/core/fs/archive_self_ncch.cpp | 93 +++++---- src/core/loader/3dsx.cpp | 300 ++++++++++++++++++++++++++++++ src/emulator.cpp | 15 ++ 8 files changed, 469 insertions(+), 39 deletions(-) create mode 100644 include/loader/3dsx.hpp create mode 100644 src/core/loader/3dsx.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f2cf0958..d0ac2804 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,7 +144,7 @@ set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA src/core/PICA/dynapica/shader_rec_emitter_x64.cpp src/core/PICA/pica_hash.cpp ) -set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/lz77.cpp) +set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/3dsx.cpp src/core/loader/lz77.cpp) set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp @@ -161,7 +161,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp - include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp + include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/loader/3dsx.hpp include/io_file.hpp include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_self_ncch.hpp include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp diff --git a/include/emulator.hpp b/include/emulator.hpp index a3ab09a5..770d78df 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -24,6 +24,7 @@ enum class ROMType { ELF, NCSD, CXI, + HB_3DSX, }; class Emulator { @@ -99,6 +100,7 @@ class Emulator { bool loadROM(const std::filesystem::path& path); bool loadNCSD(const std::filesystem::path& path, ROMType type); + bool load3DSX(const std::filesystem::path& path); bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); void initGraphicsContext(); diff --git a/include/fs/archive_self_ncch.hpp b/include/fs/archive_self_ncch.hpp index e8877a47..aefb210a 100644 --- a/include/fs/archive_self_ncch.hpp +++ b/include/fs/archive_self_ncch.hpp @@ -18,7 +18,8 @@ public: // Returns whether the cart has a RomFS bool hasRomFS() { auto cxi = mem.getCXI(); - return (cxi != nullptr && cxi->hasRomFS()); + auto hb3dsx = mem.get3DSX(); + return (cxi != nullptr && cxi->hasRomFS()) | (hb3dsx != nullptr && hb3dsx->hasRomFs()); } // Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe) diff --git a/include/loader/3dsx.hpp b/include/loader/3dsx.hpp new file mode 100644 index 00000000..1429e70e --- /dev/null +++ b/include/loader/3dsx.hpp @@ -0,0 +1,79 @@ +#pragma once +#include +#include "helpers.hpp" +#include "io_file.hpp" +#include "loader/ncch.hpp" + +struct HB3DSX { + // File layout: + // - File header + // - Code, rodata and data relocation table headers + // - Code segment + // - Rodata segment + // - Loadable (non-BSS) part of the data segment + // - Code relocation table + // - Rodata relocation table + // - Data relocation table + + // Memory layout before relocations are applied: + // [0..codeSegSize) -> code segment + // [codeSegSize..rodataSegSize) -> rodata segment + // [rodataSegSize..dataSegSize) -> data segment + + // Memory layout after relocations are applied: well, however the loader sets it up :) + // The entrypoint is always the start of the code segment. + // The BSS section must be cleared manually by the application. + + // File header + struct Header { + // minus char magic[4] + u16 headerSize; + u16 relocHdrSize; + u32 formatVer; + u32 flags; + + // Sizes of the code, rodata and data segments + + // size of the BSS section (uninitialized latter half of the data segment) + u32 codeSegSize, rodataSegSize, dataSegSize, bssSize; + }; + + // Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts. + struct RelocHdr { + u32 cAbsolute; // # of absolute relocations (that is, fix address to post-relocation memory layout) + u32 cRelative; // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be patched) + // more? + + // Relocations are written in this order: + // - Absolute relocs + // - Relative relocs + }; + enum class RelocKind { + Absolute, + Relative, + }; + + // Relocation entry: from the current pointer, skip X words and patch Y words + struct Reloc { + u16 skip, patch; + }; + + // _prm structure + static constexpr std::array PRM_MAGIC = {'_', 'P', 'R', 'M'}; + struct PrmStruct { + char magic[4]; + u32 pSrvOverride; + u32 aptAppId; + u32 heapSize, linearHeapSize; + u32 pArgList; + u32 runFlags; + }; + + IOFile file; + + static constexpr u32 ENTRYPOINT = 0x00100000; // Initial ARM11 PC + u32 romFSSize = 0; + u32 romFSOffset = 0; + + bool hasRomFs() const; + std::pair readRomFSBytes(void *dst, std::size_t offset, std::size_t size); +}; diff --git a/include/memory.hpp b/include/memory.hpp index fd1b10b4..804d1e54 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -11,6 +11,7 @@ #include "handles.hpp" #include "helpers.hpp" #include "loader/ncsd.hpp" +#include "loader/3dsx.hpp" #include "services/region_codes.hpp" namespace PhysicalAddrs { @@ -167,10 +168,12 @@ public: void* getReadPointer(u32 address); void* getWritePointer(u32 address); std::optional loadELF(std::ifstream& file); + std::optional load3DSX(const std::filesystem::path& path); std::optional loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path); std::optional loadCXI(Crypto::AESEngine& aesEngine, const std::filesystem::path& path); bool mapCXI(NCSD& ncsd, NCCH& cxi); + bool map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header); u8 read8(u32 vaddr); u16 read16(u32 vaddr); @@ -221,6 +224,14 @@ public: } } + HB3DSX* get3DSX() { + if (loaded3DSX.has_value()) { + return &loaded3DSX.value(); + } else { + return nullptr; + } + } + // Returns whether "addr" is aligned to a page (4096 byte) boundary static constexpr bool isAligned(u32 addr) { return (addr & pageMask) == 0; @@ -256,6 +267,7 @@ public: // Backup of the game's CXI partition info, if any std::optional loadedCXI = std::nullopt; + std::optional loaded3DSX = std::nullopt; // File handle for reading the loaded ncch IOFile CXIFile; diff --git a/src/core/fs/archive_self_ncch.cpp b/src/core/fs/archive_self_ncch.cpp index 7c679350..d4750a18 100644 --- a/src/core/fs/archive_self_ncch.cpp +++ b/src/core/fs/archive_self_ncch.cpp @@ -69,57 +69,78 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 return std::nullopt; } - auto cxi = mem.getCXI(); - IOFile& ioFile = mem.CXIFile; + bool success = false; + std::size_t bytesRead = 0; + std::vector data; - NCCH::FSInfo fsInfo; + if (auto cxi = mem.getCXI(); cxi != nullptr) { + IOFile& ioFile = mem.CXIFile; - // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc - switch (type) { - case PathType::RomFS: { - const u64 romFSSize = cxi->romFS.size; - const u64 romFSOffset = cxi->romFS.offset; - if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { - Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + NCCH::FSInfo fsInfo; + + // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc + switch (type) { + case PathType::RomFS: { + const u64 romFSSize = cxi->romFS.size; + const u64 romFSOffset = cxi->romFS.offset; + if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { + Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + } + + fsInfo = cxi->romFS; + offset += 0x1000; + break; } - fsInfo = cxi->romFS; - offset += 0x1000; - break; - } + case PathType::ExeFS: { + const u64 exeFSSize = cxi->exeFS.size; + const u64 exeFSOffset = cxi->exeFS.offset; + if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) { + Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + } - case PathType::ExeFS: { - const u64 exeFSSize = cxi->exeFS.size; - const u64 exeFSOffset = cxi->exeFS.offset; - if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) { - Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + fsInfo = cxi->exeFS; + break; } - fsInfo = cxi->exeFS; - break; - } + // Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed. + // So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS. + case PathType::UpdateRomFS: { + Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n"); - // Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed. - // So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS. - case PathType::UpdateRomFS: { - Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n"); + const u64 romFSSize = cxi->romFS.size; + const u64 romFSOffset = cxi->romFS.offset; + if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { + Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + } - const u64 romFSSize = cxi->romFS.size; - const u64 romFSOffset = cxi->romFS.offset; - if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { - Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + fsInfo = cxi->romFS; + offset += 0x1000; + break; } - fsInfo = cxi->romFS; - offset += 0x1000; - break; + default: Helpers::panic("Unimplemented file path type for SelfNCCH archive"); } - default: Helpers::panic("Unimplemented file path type for SelfNCCH archive"); + data.resize(size); + std::tie(success, bytesRead) = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size); } + else if (auto hb3dsx = mem.get3DSX(); hb3dsx != nullptr) { + switch (type) { + case PathType::RomFS: { + const u64 romFSSize = hb3dsx->romFSSize; + if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { + Helpers::panic("Tried to read from SelfNCCH with too big of an offset"); + } + break; + } - std::unique_ptr data(new u8[size]); - auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size); + default: Helpers::panic("Unimplemented file path type for 3DSX SelfNCCH archive"); + } + + data.resize(size); + std::tie(success, bytesRead) = hb3dsx->readRomFSBytes(&data[0], offset, size); + } if (!success) { Helpers::panic("Failed to read from SelfNCCH archive"); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp new file mode 100644 index 00000000..4140cf35 --- /dev/null +++ b/src/core/loader/3dsx.cpp @@ -0,0 +1,300 @@ +#include "loader/3dsx.hpp" + +#include +#include +#include + +#include "memory.hpp" + +namespace { + +struct LoadInfo { + u32 codeSegSizeAligned; + u32 rodataSegSizeAligned; + u32 dataSegSizeAligned; +}; + +static inline u32 TranslateAddr(const u32 off, const u32* addrs, const u32* offsets) +{ + if (off < offsets[1]) + return addrs[0] + off; + if (off < offsets[2]) + return addrs[1] + off - offsets[1]; + return addrs[2] + off - offsets[2]; +} + +} + +bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { + const LoadInfo hbInfo = { + (header.codeSegSize+0xFFF) &~ 0xFFF, + (header.rodataSegSize+0xFFF) &~ 0xFFF, + (header.dataSegSize+0xFFF) &~ 0xFFF, + }; + + const u32 textSegAddr = HB3DSX::ENTRYPOINT; + const u32 rodataSegAddr = textSegAddr + hbInfo.codeSegSizeAligned; + const u32 dataSegAddr = rodataSegAddr + hbInfo.rodataSegSizeAligned; + const u32 extraPageAddr = dataSegAddr + hbInfo.dataSegSizeAligned; + + printf("Text address = %08X, size = %08X\n", textSegAddr, hbInfo.codeSegSizeAligned); + printf("Rodata address = %08X, size = %08X\n", rodataSegAddr, hbInfo.rodataSegSizeAligned); + printf("Data address = %08X, size = %08X\n", dataSegAddr, hbInfo.dataSegSizeAligned); + + // Allocate stack, 3dsx/libctru don't require anymore than this + if (!allocateMainThreadStack(0x1000)) { + // Should be unreachable + printf("Failed to allocate stack for 3DSX.\n"); + return false; + } + + // Map code file to memory + // Total memory to allocate for loading + // suum of aligned values is always aligned, have an extra RW page for libctru + const u32 totalSize = hbInfo.codeSegSizeAligned + hbInfo.rodataSegSizeAligned + hbInfo.dataSegSizeAligned + 0x1000; + + const auto opt = findPaddr(totalSize); + if (!opt.has_value()) { + Helpers::panic("Failed to find paddr to map 3DSX file's code to"); + return false; + } + + // Map the ROM on the kernel side + const u32 textOffset = 0; + const u32 rodataOffset = textOffset + hbInfo.codeSegSizeAligned; + const u32 dataOffset = rodataOffset + hbInfo.rodataSegSizeAligned; + const u32 extraPageOffset = dataOffset + hbInfo.dataSegSizeAligned; + + std::array relocHdrs; + auto [success, count] = hb3dsx.file.read(&relocHdrs[0], relocHdrs.size(), sizeof(HB3DSX::RelocHdr)); + if (!success || count != relocHdrs.size()) { + Helpers::panic("Failed to read 3DSX relocation headers"); + return false; + } + + const u32 dataLoadsize = header.dataSegSize - header.bssSize; // 3DSX data size in header includes bss + std::vector code(totalSize, 0); + + std::tie(success, count) = hb3dsx.file.readBytes(&code[textOffset], header.codeSegSize); + if (!success || count != header.codeSegSize) { + Helpers::panic("Failed to read 3DSX text segment"); + return false; + } + + std::tie(success, count) = hb3dsx.file.readBytes(&code[rodataOffset], header.rodataSegSize); + if (!success || count != header.rodataSegSize) { + Helpers::panic("Failed to read 3DSX rodata segment"); + return false; + } + + std::tie(success, count) = hb3dsx.file.readBytes(&code[dataOffset], dataLoadsize); + if (!success || count != dataLoadsize) { + Helpers::panic("Failed to read 3DSX data segment"); + return false; + } + + std::vector currentRelocs; + const u32 segAddrs[] = { + textSegAddr, + rodataSegAddr, + dataSegAddr, + extraPageAddr, + }; + const u32 segOffs[] = { + textOffset, + rodataOffset, + dataOffset, + extraPageOffset, + }; + const u32 segSizes[] = { + header.codeSegSize, + header.rodataSegSize, + dataLoadsize, + 0x1000, + }; + + for (const auto& relocHdr : relocHdrs) { + currentRelocs.resize(relocHdr.cAbsolute + relocHdr.cRelative); + std::tie(success, count) = hb3dsx.file.read(¤tRelocs[0], currentRelocs.size(), sizeof(HB3DSX::Reloc)); + if (!success || count != currentRelocs.size()) { + Helpers::panic("Failed to read 3DSX relocations"); + return false; + } + + const auto allRelocs = std::span(currentRelocs); + const auto absoluteRelocs = allRelocs.subspan(0, relocHdr.cAbsolute); + const auto relativeRelocs = allRelocs.subspan(relocHdr.cAbsolute, relocHdr.cRelative); + + const auto currentSeg = &relocHdr - &relocHdrs[0]; + const auto sectionDataStartAs = std::span(code).subspan(segOffs[currentSeg], segSizes[currentSeg]); + + auto sectionData = sectionDataStartAs; + const auto RelocationAction = [&](const HB3DSX::Reloc& reloc, const HB3DSX::RelocKind relocKind) -> bool { + if(reloc.skip) { + sectionData = sectionData.subspan(reloc.skip * sizeof(u32)); // advance by `skip` words (32-bit values) + } + + for (u32 m = 0; m < reloc.patch && !sectionData.empty(); ++m) { + const u32 inAddr = textSegAddr + (sectionData.data() - code.data()); // byte offset -> word count + u32 origData = 0; + std::memcpy(&origData, §ionData[0], sizeof(u32)); + const u32 subType = origData >> (32-4); + const u32 addr = TranslateAddr(origData &~ 0xF0000000, segAddrs, segOffs); + + switch (relocKind) { + case HB3DSX::RelocKind::Absolute: { + if (subType != 0) { + Helpers::panic("Unsupported absolute reloc subtype"); + return false; + } + std::memcpy(§ionData[0], &addr, sizeof(u32)); + break; + } + case HB3DSX::RelocKind::Relative: { + u32 data = addr - inAddr; + switch (subType) { + case 1: // 31-bit signed offset + data &= ~(1u << 31); + case 0: // 32-bit signed offset + std::memcpy(§ionData[0], &data, sizeof(u32)); + break; + default: + Helpers::panic("Unsupported relative reloc subtype"); + return false; + } + break; + } + } + sectionData = sectionData.subspan(sizeof(u32)); + } + + return true; + }; + + for (const auto& reloc : absoluteRelocs) { + if(!RelocationAction(reloc, HB3DSX::RelocKind::Absolute)) { + return false; + } + } + + sectionData = sectionDataStartAs; // restart from the beginning for the next part + for (const auto& reloc : relativeRelocs) { + if(!RelocationAction(reloc, HB3DSX::RelocKind::Relative)) { + return false; + } + } + } + + // Detect and fill _prm structure + HB3DSX::PrmStruct pst; + std::memcpy(&pst, &code[4], sizeof(pst)); + if (pst.magic[0] == '_' && pst.magic[1] == 'p' && pst.magic[2] == 'r' && pst.magic[3] == 'm' ) { + // if there was any argv to put, it would go there + // first u32: argc + // remaining: continuous argv string (NUL-char separated, ofc) + // std::memcpy(&code[extraPageOffset], argvBuffer, ...); + + // setting to NULL (default) = run from system. load romfs from process. + // non-NULL = homebrew launcher. load romfs from 3dsx @ argv[0] + // pst.pSrvOverride = extraPageAddr + 0xFFC; + + pst.pArgList = extraPageAddr; + + // RUNFLAG_APTREINIT: Reinitialize APT. + // From libctru. Because there's no previously running software here + pst.runFlags |= 1 << 1; + + /* s64 dummy; + bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0; + if (isN3DS) + { + pst->heapSize = 48*1024*1024; + pst->linearHeapSize = 64*1024*1024; + } else */ { + pst.heapSize = 24*1024*1024; + pst.linearHeapSize = 32*1024*1024; + } + + std::memcpy(&code[4], &pst, sizeof(pst)); + } + + const auto paddr = opt.value(); + std::memcpy(&fcram[paddr], &code[0], totalSize); // Copy the 3 segments + BSS to FCRAM + + allocateMemory(textSegAddr, paddr + textOffset, hbInfo.codeSegSizeAligned, true, true, false, true); // Text is R-X + allocateMemory(rodataSegAddr, paddr + rodataOffset, hbInfo.rodataSegSizeAligned, true, true, false, false); // Rodata is R-- + allocateMemory(dataSegAddr, paddr + dataOffset, hbInfo.dataSegSizeAligned + 0x1000, true, true, true, false); // Data+BSS+Extra is RW- + + return true; +} + +std::optional Memory::load3DSX(const std::filesystem::path& path) { + HB3DSX hb3dsx; + if (!hb3dsx.file.open(path, "rb")) return std::nullopt; + + u8 magic[4]; // Must be "3DSX" + auto [success, bytes] = hb3dsx.file.readBytes(magic, 4); + + if (!success || bytes != 4) { + printf("Failed to read 3DSX magic\n"); + return std::nullopt; + } + + if (magic[0] != '3' || magic[1] != 'D' || magic[2] != 'S' || magic[3] != 'X') { + printf("3DSX with wrong magic value\n"); + return std::nullopt; + } + + HB3DSX::Header hbHeader; + std::tie(success, bytes) = hb3dsx.file.readBytes(&hbHeader, sizeof(hbHeader)); + if (!success || bytes != sizeof(hbHeader)) { + printf("Failed to read 3DSX header\n"); + return std::nullopt; + } + + if (hbHeader.headerSize == 0x20 || hbHeader.headerSize == 0x2C) { + if (hbHeader.headerSize == 0x2C) + { + hb3dsx.file.seek(8, SEEK_CUR); // skip SMDH info + std::tie(success, bytes) = hb3dsx.file.readBytes(&hb3dsx.romFSOffset, 4); + if (!success || bytes != 4) { + printf("Failed to read 3DSX romFS offset\n"); + return std::nullopt; + } + const auto fileSize = hb3dsx.file.size(); + if (!fileSize) { + printf("Failed to get 3DSX size\n"); + return std::nullopt; + } + hb3dsx.romFSSize = *fileSize - hb3dsx.romFSOffset; + } + } + else { + printf("Invalid 3DSX header size\n"); + return std::nullopt; + } + + if (!map3DSX(hb3dsx, hbHeader)) { + printf("Failed to map 3DSX\n"); + return std::nullopt; + } + + loaded3DSX = std::move(hb3dsx); + return HB3DSX::ENTRYPOINT; +} + +bool HB3DSX::hasRomFs() const { + return romFSSize != 0 && romFSOffset != 0; +} + +std::pair HB3DSX::readRomFSBytes(void *dst, std::size_t offset, std::size_t size) { + if (!hasRomFs()) { + return { false, 0 }; + } + + if (!file.seek(romFSOffset + offset)) { + return { false, 0 }; + } + + return file.readBytes(dst, size); +} diff --git a/src/emulator.cpp b/src/emulator.cpp index 75b5dbdd..dff335f5 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -453,6 +453,8 @@ bool Emulator::loadROM(const std::filesystem::path& path) { success = loadNCSD(path, ROMType::NCSD); else if (extension == ".cxi" || extension == ".app") success = loadNCSD(path, ROMType::CXI); + else if (extension == ".3dsx") + success = load3DSX(path); else { printf("Unknown file type\n"); success = false; @@ -492,6 +494,19 @@ bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) { return true; } +bool Emulator::load3DSX(const std::filesystem::path& path) { + std::optional entrypoint = memory.load3DSX(path); + romType = ROMType::HB_3DSX; + + if (!entrypoint.has_value()) { + return false; + } + + cpu.setReg(15, entrypoint.value()); // Set initial PC + + 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;