Merge pull request #251 from LiquidFenrir/load-3dsx

added feature: 3dsx loading
This commit is contained in:
wheremyfoodat 2023-09-03 11:31:43 +03:00 committed by GitHub
commit a00375cc51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 488 additions and 46 deletions

View file

@ -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

View file

@ -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();

View file

@ -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)

View file

@ -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)

80
include/loader/3dsx.hpp Normal file
View file

@ -0,0 +1,80 @@
#pragma once
#include <array>
#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<char, 4> 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<bool, std::size_t> readRomFSBytes(void *dst, std::size_t offset, std::size_t size);
};

View file

@ -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<u32> loadELF(std::ifstream& file);
std::optional<u32> load3DSX(const std::filesystem::path& path);
std::optional<NCSD> loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
std::optional<NCSD> 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<NCCH> loadedCXI = std::nullopt;
std::optional<HB3DSX> loaded3DSX = std::nullopt;
// File handle for reading the loaded ncch
IOFile CXIFile;

View file

@ -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.

View file

@ -131,20 +131,20 @@ std::optional<u32> 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<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
}
std::unique_ptr<u8[]> 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");

View file

@ -69,57 +69,79 @@ std::optional<u32> 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<u8> 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<u8[]> 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");

302
src/core/loader/3dsx.cpp Normal file
View file

@ -0,0 +1,302 @@
#include "loader/3dsx.hpp"
#include <cstring>
#include <optional>
#include <span>
#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<HB3DSX::RelocHeader, 3> 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<u8> 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<HB3DSX::Reloc> 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(&currentRelocs[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, &sectionData[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(&sectionData[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(&sectionData[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<u32> 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<bool, std::size_t> 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);
}

View file

@ -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

View file

@ -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<u32> 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;