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_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 e8877a47..ed882a7d 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..9d351832 --- /dev/null +++ b/include/loader/3dsx.hpp @@ -0,0 +1,80 @@ +#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 relocHeaderSize; + 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 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 RelocType { + 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/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/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 7c679350..9b572f96 100644 --- a/src/core/fs/archive_self_ncch.cpp +++ b/src/core/fs/archive_self_ncch.cpp @@ -69,57 +69,79 @@ 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); } - std::unique_ptr data(new u8[size]); - auto [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; + } + + 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..ca6bdd19 --- /dev/null +++ b/src/core/loader/3dsx.cpp @@ -0,0 +1,302 @@ +#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]; + } +} // 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, + }; + + 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(4_KB)) { + // 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 + 4_KB; + + 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 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; + } + + 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& 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"); + return false; + } + + const auto allRelocs = std::span(currentRelocs); + const auto absoluteRelocs = allRelocs.subspan(0, relocHeader.absoluteCount); + const auto relativeRelocs = allRelocs.subspan(relocHeader.absoluteCount, relocHeader.relativeCount); + + 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::RelocType relocType) -> 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 (relocType) { + case HB3DSX::RelocType::Absolute: { + if (subType != 0) { + Helpers::panic("Unsupported absolute reloc subtype"); + return false; + } + std::memcpy(§ionData[0], &addr, sizeof(u32)); + break; + } + + case HB3DSX::RelocType::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::RelocType::Absolute)) { + return false; + } + } + + sectionData = sectionDataStartAs; // restart from the beginning for the next part + for (const auto& reloc : relativeRelocs) { + if (!RelocationAction(reloc, HB3DSX::RelocType::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 = u32(48_MB); + pst->linearHeapSize = u32(64_MB); + } else */ { + pst.heapSize = u32(24_MB); + pst.linearHeapSize = u32(32_MB); + } + + 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/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 diff --git a/src/emulator.cpp b/src/emulator.cpp index 75b5dbdd..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 @@ -453,6 +457,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 +498,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;