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 1/5] 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; From 2f056e820ab0772b591abe1956b93770735e808a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20B?= Date: Sun, 3 Sep 2023 01:23:19 +0200 Subject: [PATCH 2/5] libctru expects this to be 32-readable --- src/core/memory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b532fb41..5bf0f79b 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -150,6 +150,7 @@ u32 Memory::read32(u32 vaddr) { return *(u32*)(pointer + offset); } else { switch (vaddr) { + case 0x1FF80000: return u32(kernelVersion) << 16; case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits case ConfigMem::Datetime0 + 4: return u32(timeSince3DSEpoch() >> 32); // top 32 bits From 38be95fffab5bb4823a0862e10759b3c0b463bcf Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 3 Sep 2023 10:36:54 +0300 Subject: [PATCH 3/5] Gently bonk 3DSX --- include/loader/3dsx.hpp | 13 ++++--- readme.md | 1 + src/core/loader/3dsx.cpp | 80 ++++++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/include/loader/3dsx.hpp b/include/loader/3dsx.hpp index 1429e70e..9d351832 100644 --- a/include/loader/3dsx.hpp +++ b/include/loader/3dsx.hpp @@ -28,7 +28,7 @@ struct HB3DSX { struct Header { // minus char magic[4] u16 headerSize; - u16 relocHdrSize; + u16 relocHeaderSize; u32 formatVer; u32 flags; @@ -38,16 +38,17 @@ struct HB3DSX { }; // 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) + struct RelocHeader { + u32 absoluteCount; // # of absolute relocations (that is, fix address to post-relocation memory layout) + u32 relativeCount; // # 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 { + + enum class RelocType { Absolute, Relative, }; @@ -70,7 +71,7 @@ struct HB3DSX { IOFile file; - static constexpr u32 ENTRYPOINT = 0x00100000; // Initial ARM11 PC + static constexpr u32 entrypoint = 0x00100000; // Initial ARM11 PC u32 romFSSize = 0; u32 romFSOffset = 0; diff --git a/readme.md b/readme.md index c96fe28c..0fde88ee 100644 --- a/readme.md +++ b/readme.md @@ -61,6 +61,7 @@ Panda3DS can load ROMs in the following formats: - .3ds/.cci - .cxi/.app - .elf/.axf +- .3dsx Both decrypted and encrypted dumps are supported. However for encrypted dumps you must provide your AES keys file by adding a `sysdata` folder to the emulator's app data directory with a file called `aes_keys.txt` including your keys. Currently .cia files are not supported yet (support is planned for the future), however if you want you can usually use Citra to extract the .app/.cxi file out of your .cia and run that. diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 4140cf35..c5a9bae2 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -14,12 +14,14 @@ struct LoadInfo { 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]; +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]; } @@ -27,12 +29,12 @@ static inline u32 TranslateAddr(const u32 off, const u32* addrs, const u32* offs bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { const LoadInfo hbInfo = { - (header.codeSegSize+0xFFF) &~ 0xFFF, - (header.rodataSegSize+0xFFF) &~ 0xFFF, - (header.dataSegSize+0xFFF) &~ 0xFFF, + .codeSegSizeAligned = (header.codeSegSize+0xFFF) &~ 0xFFF, + .rodataSegSizeAligned = (header.rodataSegSize+0xFFF) &~ 0xFFF, + .dataSegSizeAligned = (header.dataSegSize+0xFFF) &~ 0xFFF, }; - const u32 textSegAddr = HB3DSX::ENTRYPOINT; + const u32 textSegAddr = HB3DSX::entrypoint; const u32 rodataSegAddr = textSegAddr + hbInfo.codeSegSizeAligned; const u32 dataSegAddr = rodataSegAddr + hbInfo.rodataSegSizeAligned; const u32 extraPageAddr = dataSegAddr + hbInfo.dataSegSizeAligned; @@ -42,7 +44,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { printf("Data address = %08X, size = %08X\n", dataSegAddr, hbInfo.dataSegSizeAligned); // Allocate stack, 3dsx/libctru don't require anymore than this - if (!allocateMainThreadStack(0x1000)) { + if (!allocateMainThreadStack(4_KB)) { // Should be unreachable printf("Failed to allocate stack for 3DSX.\n"); return false; @@ -51,7 +53,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { // 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 u32 totalSize = hbInfo.codeSegSizeAligned + hbInfo.rodataSegSizeAligned + hbInfo.dataSegSizeAligned + 4_KB; const auto opt = findPaddr(totalSize); if (!opt.has_value()) { @@ -65,9 +67,9 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { 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()) { + std::array relocHeaders; + auto [success, count] = hb3dsx.file.read(&relocHeaders[0], relocHeaders.size(), sizeof(HB3DSX::RelocHeader)); + if (!success || count != relocHeaders.size()) { Helpers::panic("Failed to read 3DSX relocation headers"); return false; } @@ -94,18 +96,21 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { } std::vector currentRelocs; + const u32 segAddrs[] = { textSegAddr, rodataSegAddr, dataSegAddr, extraPageAddr, }; + const u32 segOffs[] = { textOffset, rodataOffset, dataOffset, extraPageOffset, }; + const u32 segSizes[] = { header.codeSegSize, header.rodataSegSize, @@ -113,8 +118,8 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { 0x1000, }; - for (const auto& relocHdr : relocHdrs) { - currentRelocs.resize(relocHdr.cAbsolute + relocHdr.cRelative); + for (const auto& relocHeader : relocHeaders) { + currentRelocs.resize(relocHeader.absoluteCount + relocHeader.relativeCount); 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"); @@ -122,15 +127,15 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { } 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 absoluteRelocs = allRelocs.subspan(0, relocHeader.absoluteCount); + const auto relativeRelocs = allRelocs.subspan(relocHeader.absoluteCount, relocHeader.relativeCount); - const auto currentSeg = &relocHdr - &relocHdrs[0]; + const auto currentSeg = &relocHeader - &relocHeaders[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) { + + const auto RelocationAction = [&](const HB3DSX::Reloc& reloc, const HB3DSX::RelocType relocType) -> bool { + if (reloc.skip) { sectionData = sectionData.subspan(reloc.skip * sizeof(u32)); // advance by `skip` words (32-bit values) } @@ -139,10 +144,10 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { u32 origData = 0; std::memcpy(&origData, §ionData[0], sizeof(u32)); const u32 subType = origData >> (32-4); - const u32 addr = TranslateAddr(origData &~ 0xF0000000, segAddrs, segOffs); + const u32 addr = translateAddr(origData &~ 0xF0000000, segAddrs, segOffs); - switch (relocKind) { - case HB3DSX::RelocKind::Absolute: { + switch (relocType) { + case HB3DSX::RelocType::Absolute: { if (subType != 0) { Helpers::panic("Unsupported absolute reloc subtype"); return false; @@ -150,7 +155,8 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { std::memcpy(§ionData[0], &addr, sizeof(u32)); break; } - case HB3DSX::RelocKind::Relative: { + + case HB3DSX::RelocType::Relative: { u32 data = addr - inAddr; switch (subType) { case 1: // 31-bit signed offset @@ -165,6 +171,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { break; } } + sectionData = sectionData.subspan(sizeof(u32)); } @@ -172,14 +179,14 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { }; for (const auto& reloc : absoluteRelocs) { - if(!RelocationAction(reloc, HB3DSX::RelocKind::Absolute)) { + if (!RelocationAction(reloc, HB3DSX::RelocType::Absolute)) { return false; } } sectionData = sectionDataStartAs; // restart from the beginning for the next part for (const auto& reloc : relativeRelocs) { - if(!RelocationAction(reloc, HB3DSX::RelocKind::Relative)) { + if (!RelocationAction(reloc, HB3DSX::RelocType::Relative)) { return false; } } @@ -208,11 +215,11 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0; if (isN3DS) { - pst->heapSize = 48*1024*1024; - pst->linearHeapSize = 64*1024*1024; + pst->heapSize = u32(48_MB); + pst->linearHeapSize = u32(64_MB); } else */ { - pst.heapSize = 24*1024*1024; - pst.linearHeapSize = 32*1024*1024; + pst.heapSize = u32(24_MB); + pst.linearHeapSize = u32(32_MB); } std::memcpy(&code[4], &pst, sizeof(pst)); @@ -230,7 +237,9 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { std::optional Memory::load3DSX(const std::filesystem::path& path) { HB3DSX hb3dsx; - if (!hb3dsx.file.open(path, "rb")) return std::nullopt; + if (!hb3dsx.file.open(path, "rb")) { + return std::nullopt; + } u8 magic[4]; // Must be "3DSX" auto [success, bytes] = hb3dsx.file.readBytes(magic, 4); @@ -261,6 +270,7 @@ std::optional Memory::load3DSX(const std::filesystem::path& path) { 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"); @@ -280,7 +290,7 @@ std::optional Memory::load3DSX(const std::filesystem::path& path) { } loaded3DSX = std::move(hb3dsx); - return HB3DSX::ENTRYPOINT; + return HB3DSX::entrypoint; } bool HB3DSX::hasRomFs() const { From 53917841bc50e440d400fbf6c2060b328118b12d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 3 Sep 2023 10:40:57 +0300 Subject: [PATCH 4/5] Run clang-format --- src/core/loader/3dsx.cpp | 98 ++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 53 deletions(-) diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index c5a9bae2..ca6bdd19 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -7,31 +7,29 @@ #include "memory.hpp" namespace { + struct LoadInfo { + u32 codeSegSizeAligned; + u32 rodataSegSizeAligned; + u32 dataSegSizeAligned; + }; -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; + } -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]; } - - if (off < offsets[2]) { - return addrs[1] + off - offsets[1]; - } - return addrs[2] + off - offsets[2]; -} - -} +} // namespace bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { const LoadInfo hbInfo = { - .codeSegSizeAligned = (header.codeSegSize+0xFFF) &~ 0xFFF, - .rodataSegSizeAligned = (header.rodataSegSize+0xFFF) &~ 0xFFF, - .dataSegSizeAligned = (header.dataSegSize+0xFFF) &~ 0xFFF, + .codeSegSizeAligned = (header.codeSegSize + 0xFFF) & ~0xFFF, + .rodataSegSizeAligned = (header.rodataSegSize + 0xFFF) & ~0xFFF, + .dataSegSizeAligned = (header.dataSegSize + 0xFFF) & ~0xFFF, }; const u32 textSegAddr = HB3DSX::entrypoint; @@ -74,7 +72,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { return false; } - const u32 dataLoadsize = header.dataSegSize - header.bssSize; // 3DSX data size in header includes bss + 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); @@ -90,13 +88,13 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { } std::tie(success, count) = hb3dsx.file.readBytes(&code[dataOffset], dataLoadsize); - if (!success || count != dataLoadsize) { + if (!success || count != dataLoadsize) { Helpers::panic("Failed to read 3DSX data segment"); return false; } std::vector currentRelocs; - + const u32 segAddrs[] = { textSegAddr, rodataSegAddr, @@ -110,7 +108,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { dataOffset, extraPageOffset, }; - + const u32 segSizes[] = { header.codeSegSize, header.rodataSegSize, @@ -121,7 +119,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { for (const auto& relocHeader : relocHeaders) { currentRelocs.resize(relocHeader.absoluteCount + relocHeader.relativeCount); std::tie(success, count) = hb3dsx.file.read(¤tRelocs[0], currentRelocs.size(), sizeof(HB3DSX::Reloc)); - if (!success || count != currentRelocs.size()) { + if (!success || count != currentRelocs.size()) { Helpers::panic("Failed to read 3DSX relocations"); return false; } @@ -136,15 +134,15 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { const auto RelocationAction = [&](const HB3DSX::Reloc& reloc, const HB3DSX::RelocType relocType) -> bool { if (reloc.skip) { - sectionData = sectionData.subspan(reloc.skip * sizeof(u32)); // advance by `skip` words (32-bit values) + 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 + 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); + const u32 subType = origData >> (32 - 4); + const u32 addr = translateAddr(origData & ~0xF0000000, segAddrs, segOffs); switch (relocType) { case HB3DSX::RelocType::Absolute: { @@ -159,14 +157,12 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { case HB3DSX::RelocType::Relative: { u32 data = addr - inAddr; switch (subType) { - case 1: // 31-bit signed offset + case 1: // 31-bit signed offset data &= ~(1u << 31); - case 0: // 32-bit signed offset + case 0: // 32-bit signed offset std::memcpy(§ionData[0], &data, sizeof(u32)); break; - default: - Helpers::panic("Unsupported relative reloc subtype"); - return false; + default: Helpers::panic("Unsupported relative reloc subtype"); return false; } break; } @@ -184,7 +180,7 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { } } - sectionData = sectionDataStartAs; // restart from the beginning for the next part + sectionData = sectionDataStartAs; // restart from the beginning for the next part for (const auto& reloc : relativeRelocs) { if (!RelocationAction(reloc, HB3DSX::RelocType::Relative)) { return false; @@ -195,22 +191,22 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { // 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 (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) @@ -228,9 +224,9 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) { 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- + 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; } @@ -262,9 +258,8 @@ std::optional Memory::load3DSX(const std::filesystem::path& path) { } if (hbHeader.headerSize == 0x20 || hbHeader.headerSize == 0x2C) { - if (hbHeader.headerSize == 0x2C) - { - hb3dsx.file.seek(8, SEEK_CUR); // skip SMDH info + 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"); @@ -278,8 +273,7 @@ std::optional Memory::load3DSX(const std::filesystem::path& path) { } hb3dsx.romFSSize = *fileSize - hb3dsx.romFSOffset; } - } - else { + } else { printf("Invalid 3DSX header size\n"); return std::nullopt; } @@ -293,17 +287,15 @@ std::optional Memory::load3DSX(const std::filesystem::path& path) { return HB3DSX::entrypoint; } -bool HB3DSX::hasRomFs() const { - return romFSSize != 0 && romFSOffset != 0; -} +bool HB3DSX::hasRomFs() const { return romFSSize != 0 && romFSOffset != 0; } -std::pair HB3DSX::readRomFSBytes(void *dst, std::size_t offset, std::size_t size) { +std::pair HB3DSX::readRomFSBytes(void* dst, std::size_t offset, std::size_t size) { if (!hasRomFs()) { - return { false, 0 }; + return {false, 0}; } if (!file.seek(romFSOffset + offset)) { - return { false, 0 }; + return {false, 0}; } return file.readBytes(dst, size); From a380aa83f091c43849f7db5cc3361ddc8f3f0bd9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 3 Sep 2023 11:04:54 +0300 Subject: [PATCH 5/5] Add 3DSX RomFS reads via NCCH archive --- include/fs/archive_ncch.hpp | 3 ++- include/fs/archive_self_ncch.hpp | 2 +- src/core/fs/archive_ncch.cpp | 13 +++++++------ src/core/fs/archive_self_ncch.cpp | 1 + src/emulator.cpp | 4 ++++ 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/include/fs/archive_ncch.hpp b/include/fs/archive_ncch.hpp index 275bcd20..de19cbe5 100644 --- a/include/fs/archive_ncch.hpp +++ b/include/fs/archive_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/fs/archive_self_ncch.hpp b/include/fs/archive_self_ncch.hpp index aefb210a..ed882a7d 100644 --- a/include/fs/archive_self_ncch.hpp +++ b/include/fs/archive_self_ncch.hpp @@ -19,7 +19,7 @@ public: bool hasRomFS() { auto cxi = mem.getCXI(); auto hb3dsx = mem.get3DSX(); - return (cxi != nullptr && cxi->hasRomFS()) | (hb3dsx != nullptr && hb3dsx->hasRomFs()); + 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/src/core/fs/archive_ncch.cpp b/src/core/fs/archive_ncch.cpp index 6684ccf4..5a808ade 100644 --- a/src/core/fs/archive_ncch.cpp +++ b/src/core/fs/archive_ncch.cpp @@ -131,20 +131,20 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size } auto cxi = mem.getCXI(); - IOFile& ioFile = mem.CXIFile; + auto hb3dsx = mem.get3DSX(); - NCCH::FSInfo fsInfo; + // If isCXI is true then we've loaded a CXI/3DS file, else it's 3DSX + bool isCXI = cxi != nullptr; // 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; + const u64 romFSSize = isCXI ? cxi->romFS.size : hb3dsx->romFSSize; + const u64 romFSOffset = isCXI ? cxi->romFS.offset : hb3dsx->romFSOffset; if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { Helpers::panic("Tried to read from NCCH with too big of an offset"); } - fsInfo = cxi->romFS; offset += 0x1000; break; } @@ -154,7 +154,8 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size } std::unique_ptr data(new u8[size]); - auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size); + auto [success, bytesRead] = + isCXI ? cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size) : hb3dsx->readRomFSBytes(&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 d4750a18..9b572f96 100644 --- a/src/core/fs/archive_self_ncch.cpp +++ b/src/core/fs/archive_self_ncch.cpp @@ -125,6 +125,7 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 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: { diff --git a/src/emulator.cpp b/src/emulator.cpp index dff335f5..2f8c81cc 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -426,6 +426,10 @@ bool Emulator::loadROM(const std::filesystem::path& path) { reset(ReloadOption::NoReload); } + // Reset whatever state needs to be reset before loading a new ROM + memory.loadedCXI = std::nullopt; + memory.loaded3DSX = std::nullopt; + // 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