Memory rework pt 2 (#801)

* Memory: Rework FCRAM management entirely

Disables a lot of functionality... but I didn't want to commit too much to this commit
Also reworks virtual memory management somewhat (but needs more work)

* Accurately handle MemoryState for virtual memory

Previously all non-free blocks were marked as Reserved

* Memory: Consolidate state and permission changes

Can now use a single function to change either state, permissions, or both
Also merge vmem blocks that have the same state and permissions

* Memory: Fix double reset for FCRAM manager

Fix minor bug with permission tracking

* Memory: Implement Protect operation in ControlMemory

* Memory: Implement Unmap in ControlMemory

Also do a sanity check to make sure the memory region is free for linear allocations

* Memory: Make TLS only 0x200 bytes for each thread

Also move TLS to Base region

* RO: Unmap CROs when unloaded

Thanks @noumidev

* Kernel: Return used app memory for Commit ResourceLimit

Not quite correct, but nothing to be done until process management is improved
Also remove the stack limit for CXIs (thanks amogus)

* Kernel: Report used app memory for GetProcessInfo 2

Not really correct, but it should be accurate for applications at least

* Formatting changes

* Initial fastmem support

* PCSX2 fastmem depression

* Move away from PCSX2 fastmem

* Add enum_flag_ops.hpp

* Finally building on Windows

* Almost got a PoC

* Fix arm64 builds

* This somehow works

* This also works...

* Properly fix fastmem

* Add free region manager

* Update boost

* Add ScopeExit

* Comment out asserts on Linux/Mac/Android

* Comment out ASSERT_MSG asserts too

* Fix derp

* Attempt to fix Android

* Disable fastmem on Android

* Fix Android again maybe pt 2

* android pls

* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

* Update host_memory.cpp

* Properly reset memory arena on reset

* Proper ashmem code for Android

* more

* Add temporary Android buildjet script for faster prototype builds

* Fix fastmem (again)

* Clean up shared memory

* Remove Android BuildJet runner

* a

* Revert "a"

This reverts commit 5443ad6f2a794c19c9b1a1567ca1c7f58eed78cd.

* Re-add ELF support

* Re-add 3DSX support

* GetSystemInfo, GetProcessInfo: Memory sizes should be in bytes

* Update Boost

* Update metal-cpp

* Fix metal renderer compilation

* Fix fastmem mapping

* Clean up fastmem code

* Fix oopsie again

* Emulator: Reorder struct

* Kernel types: Cleanup

* Cleanup

* More cleanup

* Make invalid mprotects warn instead of panicking

* Add setting for toggling fastmem

* More cleanup

* Properly initialize BSS to zeroes

* Remove unused code

* Formatting

* Cleanup

* Memory/CRO: Workaround for Pokemon XY

* NCSD loader: Fix BSS (again)

* NCSD loader: Fix BSS (again) (again)

* More memory fixes

* Memory: Remove unused code

* FS: Warn on unimplemented functions instead of panic

* Update software_keyboard.cpp

* Libretro: Add fastmem option

* FRD: Stub SaveLocalAccountData

---------

Co-authored-by: PSI-Rockin <PSI-Rockin@users.noreply.github.com>
This commit is contained in:
wheremyfoodat
2025-08-07 20:18:09 +03:00
committed by GitHub
parent 5ebee8ea72
commit 6d1ef7cb4f
40 changed files with 2901 additions and 526 deletions

View File

@@ -48,6 +48,7 @@ void EmulatorConfig::load() {
printAppVersion = toml::find_or<toml::boolean>(general, "PrintAppVersion", true);
circlePadProEnabled = toml::find_or<toml::boolean>(general, "EnableCirclePadPro", true);
fastmemEnabled = toml::find_or<toml::boolean>(general, "EnableFastmem", enableFastmemDefault);
systemLanguage = languageCodeFromString(toml::find_or<std::string>(general, "SystemLanguage", "en"));
}
}
@@ -180,6 +181,7 @@ void EmulatorConfig::save() {
data["General"]["PrintAppVersion"] = printAppVersion;
data["General"]["SystemLanguage"] = languageCodeToString(systemLanguage);
data["General"]["EnableCirclePadPro"] = circlePadProEnabled;
data["General"]["EnableFastmem"] = fastmemEnabled;
data["Window"]["AppVersionOnWindow"] = windowSettings.showAppVersion;
data["Window"]["RememberWindowPosition"] = windowSettings.rememberPosition;

View File

@@ -6,6 +6,7 @@
CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) {
cp15 = std::make_shared<CP15>();
mem.setCPUTicks(getTicksRef());
Dynarmic::A32::UserConfig config;
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
@@ -15,6 +16,12 @@ CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), sched
config.global_monitor = &exclusiveMonitor;
config.processor_id = 0;
if (mem.isFastmemEnabled()) {
config.fastmem_pointer = u64(mem.getFastmemArenaBase());
} else {
config.fastmem_pointer = std::nullopt;
}
jit = std::make_unique<Dynarmic::A32::Jit>(config);
}

View File

@@ -55,7 +55,7 @@ Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock* sharedMem
}
mem.write16(textAddress, 0); // Write UTF-16 null terminator
// Temporarily hardcode the pressed button to be the firs tone
// Temporarily hardcode the pressed button to be the first one
switch (config.numButtonsM1) {
case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break;
case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click1; break;

102
src/core/kernel/fcram.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "fcram.hpp"
#include "memory.hpp"
void KFcram::Region::reset(u32 start, size_t size) {
this->start = start;
pages = size >> 12;
freePages = pages;
Block initialBlock(pages, 0);
blocks.clear();
blocks.push_back(initialBlock);
}
void KFcram::Region::alloc(std::list<FcramBlock>& out, s32 allocPages, bool linear) {
for (auto it = blocks.begin(); it != blocks.end(); it++) {
if (it->used) continue;
// On linear allocations, only a single contiguous block may be used
if (it->pages < allocPages && linear) continue;
// If the current block is bigger than the allocation, split it
if (it->pages > allocPages) {
Block newBlock(it->pages - allocPages, it->pageOffset + allocPages);
it->pages = allocPages;
blocks.insert(it, newBlock);
}
// Mark the block as allocated and add it to the output list
it->used = true;
allocPages -= it->pages;
freePages -= it->pages;
u32 paddr = start + (it->pageOffset << 12);
FcramBlock outBlock(paddr, it->pages);
out.push_back(outBlock);
if (allocPages < 1) {
return;
}
}
// Official kernel panics here
Helpers::panic("Failed to allocate FCRAM, not enough guest memory");
}
u32 KFcram::Region::getUsedCount() { return pages - freePages; }
u32 KFcram::Region::getFreeCount() { return freePages; }
KFcram::KFcram(Memory& mem) : mem(mem) {}
void KFcram::reset(size_t ramSize, size_t appSize, size_t sysSize, size_t baseSize) {
fcram = mem.getFCRAM();
refs = std::unique_ptr<u32>(new u32[ramSize >> 12]);
std::memset(refs.get(), 0, (ramSize >> 12) * sizeof(u32));
appRegion.reset(0, appSize);
sysRegion.reset(appSize, sysSize);
baseRegion.reset(appSize + sysSize, baseSize);
}
void KFcram::alloc(FcramBlockList& out, s32 pages, FcramRegion region, bool linear) {
switch (region) {
case FcramRegion::App: appRegion.alloc(out, pages, linear); break;
case FcramRegion::Sys: sysRegion.alloc(out, pages, linear); break;
case FcramRegion::Base: baseRegion.alloc(out, pages, linear); break;
default: Helpers::panic("Invalid FCRAM region chosen for allocation!"); break;
}
incRef(out);
}
void KFcram::incRef(FcramBlockList& list) {
for (auto it = list.begin(); it != list.end(); it++) {
for (int i = 0; i < it->pages; i++) {
u32 index = (it->paddr >> 12) + i;
refs.get()[index]++;
}
}
}
void KFcram::decRef(FcramBlockList& list) {
for (auto it = list.begin(); it != list.end(); it++) {
for (int i = 0; i < it->pages; i++) {
u32 index = (it->paddr >> 12) + i;
refs.get()[index]--;
if (!refs.get()[index]) {
Helpers::panic("TODO: Freeing FCRAM");
}
}
}
}
u32 KFcram::getUsedCount(FcramRegion region) {
switch (region) {
case FcramRegion::App: return appRegion.getUsedCount();
case FcramRegion::Sys: return sysRegion.getUsedCount();
case FcramRegion::Base: return baseRegion.getUsedCount();
default: Helpers::panic("Invalid FCRAM region in getUsedCount!");
}
}

View File

@@ -17,6 +17,8 @@ idle_thread_main:
b idle_thread_main
*/
using namespace KernelMemoryTypes;
static constexpr u8 idleThreadCode[] = {
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
@@ -27,18 +29,16 @@ static constexpr u8 idleThreadCode[] = {
// Set up an idle thread to run when no thread is able to run
void Kernel::setupIdleThread() {
Thread& t = threads[idleThreadIndex];
constexpr u32 codeAddress = 0xBFC00000;
// Reserve some memory for the idle thread's code. We map this memory to vaddr BFC00000 which is not userland-accessible
// Reserve some memory for the idle thread's code. We map this memory to vaddr 3FC00000 which shouldn't be accessed by applications
// We only allocate 4KB (1 page) because our idle code is pretty small
const u32 fcramIndex = mem.allocateSysMemory(Memory::pageSize);
auto vaddr = mem.allocateMemory(codeAddress, fcramIndex, Memory::pageSize, true, true, false, true, false, true);
if (!vaddr.has_value() || vaddr.value() != codeAddress) {
constexpr u32 codeAddress = 0x3FC00000;
if (!mem.allocMemory(codeAddress, 1, FcramRegion::Base, true, true, false, MemoryState::Locked)) {
Helpers::panic("Failed to setup idle thread");
}
// Copy idle thread code to the allocated FCRAM
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
mem.copyToVaddr(codeAddress, idleThreadCode, sizeof(idleThreadCode));
t.entrypoint = codeAddress;
t.initialSP = 0;

View File

@@ -6,7 +6,7 @@
#include "kernel_types.hpp"
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config, lua) {
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config, lua), fcramManager(mem) {
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
mutexHandles.reserve(8);
portHandles.reserve(32);
@@ -180,9 +180,7 @@ void Kernel::reset() {
}
// Get pointer to thread-local storage
u32 Kernel::getTLSPointer() {
return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize;
}
u32 Kernel::getTLSPointer() { return VirtualAddrs::TLSBase + currentThreadIndex * VirtualAddrs::TLSSize; }
// Result CloseHandle(Handle handle)
void Kernel::svcCloseHandle() {
@@ -271,7 +269,7 @@ void Kernel::getProcessInfo() {
// According to 3DBrew: Amount of private (code, data, heap) memory used by the process + total supervisor-mode
// stack size + page-rounded size of the external handle table
case 2:
regs[1] = mem.getUsedUserMem();
regs[1] = fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
regs[2] = 0;
break;
@@ -364,7 +362,7 @@ void Kernel::getSystemInfo() {
switch (subtype) {
// Total used memory size in the APPLICATION memory region
case 1:
regs[1] = mem.getUsedUserMem();
regs[1] = fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
regs[2] = 0;
break;

View File

@@ -30,10 +30,10 @@ namespace MemoryPermissions {
};
}
using namespace KernelMemoryTypes;
// Returns whether "value" is aligned to a page boundary (Ie a boundary of 4096 bytes)
static constexpr bool isAligned(u32 value) {
return (value & 0xFFF) == 0;
}
static constexpr bool isAligned(u32 value) { return (value & 0xFFF) == 0; }
// Result ControlMemory(u32* outaddr, u32 addr0, u32 addr1, u32 size,
// MemoryOperation operation, MemoryPermission permissions)
@@ -44,6 +44,7 @@ void Kernel::controlMemory() {
u32 addr0 = regs[1];
u32 addr1 = regs[2];
u32 size = regs[3];
u32 pages = size >> 12; // Official kernel truncates nonaligned sizes
u32 perms = regs[4];
if (perms == MemoryPermissions::DontCare) {
@@ -61,7 +62,7 @@ void Kernel::controlMemory() {
Helpers::panic("ControlMemory: attempted to allocate executable memory");
}
if (!isAligned(addr0) || !isAligned(addr1) || !isAligned(size)) {
if (!isAligned(addr0) || !isAligned(addr1)) {
Helpers::panic("ControlMemory: Unaligned parameters\nAddr0: %08X\nAddr1: %08X\nSize: %08X", addr0, addr1, size);
}
@@ -72,22 +73,54 @@ void Kernel::controlMemory() {
switch (operation & 0xFF) {
case Operation::Commit: {
std::optional<u32> address = mem.allocateMemory(addr0, 0, size, linear, r, w, x, true);
if (!address.has_value()) {
Helpers::panic("ControlMemory: Failed to allocate memory");
// TODO: base this from the exheader
auto region = FcramRegion::App;
u32 outAddr = 0;
if (linear) {
if (!mem.allocMemoryLinear(outAddr, addr0, pages, region, r, w, false)) {
Helpers::panic("ControlMemory: Failed to allocate linear memory");
}
} else {
if (!mem.allocMemory(addr0, pages, region, r, w, false, MemoryState::Private)) {
Helpers::panic("ControlMemory: Failed to allocate memory");
}
outAddr = addr0;
}
regs[1] = address.value();
regs[1] = outAddr;
break;
}
case Operation::Map: mem.mirrorMapping(addr0, addr1, size); break;
case Operation::Map:
// Official kernel only allows Private regions to be mapped to Free regions. An Alias or Aliased region cannot be mapped again
if (!mem.mapVirtualMemory(
addr0, addr1, pages, r, w, false, MemoryState::Free, MemoryState::Private, MemoryState::Alias, MemoryState::Aliased
))
Helpers::panic("ControlMemory: Failed to map memory");
break;
case Operation::Unmap:
// The same as a Map operation, except in reverse
if (!mem.mapVirtualMemory(
addr0, addr1, pages, false, false, false, MemoryState::Alias, MemoryState::Aliased, MemoryState::Free, MemoryState::Private
)) {
Helpers::panic("ControlMemory: Failed to unmap memory");
}
break;
case Operation::Protect:
Helpers::warn(
"Ignoring mprotect! Hope nothing goes wrong but if the game accesses invalid memory or crashes then we prolly need to implement "
"this\n"
);
// Official kernel has an internal state bit to indicate that the region's permissions may be changed
// But this should account for all cases
if (!mem.testMemoryState(addr0, pages, MemoryState::Private) && !mem.testMemoryState(addr0, pages, MemoryState::Alias) &&
!mem.testMemoryState(addr0, pages, MemoryState::Aliased) && !mem.testMemoryState(addr0, pages, MemoryState::AliasCode)) {
Helpers::warn("Tried to mprotect invalid region!");
return;
}
mem.changePermissions(addr0, pages, r, w, false);
regs[1] = addr0;
break;
default: Helpers::warn("ControlMemory: unknown operation %X\n", operation); break;
@@ -104,10 +137,11 @@ void Kernel::queryMemory() {
logSVC("QueryMemory(mem info pointer = %08X, page info pointer = %08X, addr = %08X)\n", memInfo, pageInfo, addr);
const auto info = mem.queryMemory(addr);
regs[0] = Result::Success;
KernelMemoryTypes::MemoryInfo info;
const auto result = mem.queryMemory(info, addr);
regs[0] = result;
regs[1] = info.baseAddr;
regs[2] = info.size;
regs[2] = info.pages * Memory::pageSize;
regs[3] = info.perms;
regs[4] = info.state;
regs[5] = 0; // page flags

View File

@@ -82,7 +82,9 @@ void Kernel::getResourceLimitCurrentValues() {
s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName) {
const auto data = static_cast<ResourceLimits*>(limit->data);
switch (resourceName) {
case ResourceType::Commit: return mem.usedUserMemory;
// TODO: needs to use the current amount of memory allocated by the process
case ResourceType::Commit: return fcramManager.getUsedCount(FcramRegion::App) * Memory::pageSize;
case ResourceType::Thread: return threadIndices.size();
default: Helpers::panic("Attempted to get current value of unknown kernel resource: %d\n", resourceName);
}

View File

@@ -6,6 +6,8 @@
#include "memory.hpp"
using namespace KernelMemoryTypes;
namespace {
struct LoadInfo {
u32 codeSegSizeAligned;
@@ -53,12 +55,6 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
// 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;
@@ -213,7 +209,8 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
{
pst->heapSize = u32(48_MB);
pst->linearHeapSize = u32(64_MB);
} else */ {
} else */
{
pst.heapSize = u32(24_MB);
pst.linearHeapSize = u32(32_MB);
}
@@ -221,12 +218,17 @@ bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
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
// Text is R-X
allocMemory(textSegAddr, hbInfo.codeSegSizeAligned / Memory::pageSize, FcramRegion::App, true, false, true, MemoryState::Code);
copyToVaddr(textSegAddr, &code[textOffset], hbInfo.codeSegSizeAligned);
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-
// Rodata is R--
allocMemory(rodataSegAddr, hbInfo.rodataSegSizeAligned / Memory::pageSize, FcramRegion::App, true, false, false, MemoryState::Code);
copyToVaddr(rodataSegAddr, &code[rodataOffset], hbInfo.rodataSegSizeAligned);
// Data + BSS + Extra is RW-. We allocate 1 extra page (4KB) which is not initialized to anything.
allocMemory(dataSegAddr, (hbInfo.dataSegSizeAligned + 4_KB) / Memory::pageSize, FcramRegion::App, true, true, false, MemoryState::Private);
copyToVaddr(dataSegAddr, &code[dataOffset], hbInfo.dataSegSizeAligned);
return true;
}

View File

@@ -4,6 +4,7 @@
#include "memory.hpp"
using namespace ELFIO;
using namespace KernelMemoryTypes;
std::optional<u32> Memory::loadELF(std::ifstream& file) {
loadedCXI = std::nullopt; // ELF files don't have a CXI, so set this to null
@@ -24,6 +25,7 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
auto segNum = reader.segments.size();
printf("Number of segments: %d\n", segNum);
printf(" # Perms Vaddr File Size Mem Size\n");
for (int i = 0; i < segNum; ++i) {
const auto seg = reader.segments[i];
const auto flags = seg->get_flags();
@@ -55,12 +57,8 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
Helpers::warn("Rounding ELF segment size to %08X\n", memorySize);
}
// This should also assert that findPaddr doesn't fail
u32 fcramAddr = findPaddr(memorySize).value();
std::memcpy(&fcram[fcramAddr], data, fileSize);
// Allocate the segment on the OS side
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x);
allocMemory(vaddr, memorySize / Memory::pageSize, FcramRegion::App, r, w, x, MemoryState::Code);
copyToVaddr(vaddr, data, fileSize);
}
// ELF can't specify a region, make it default to USA

View File

@@ -3,8 +3,11 @@
#include <cstring>
#include <optional>
#include "kernel/fcram.hpp"
#include "memory.hpp"
using namespace KernelMemoryTypes;
bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
printf("Text address = %08X, size = %08X\n", cxi.text.address, cxi.text.size);
printf("Rodata address = %08X, size = %08X\n", cxi.rodata.address, cxi.rodata.size);
@@ -24,12 +27,6 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
// Round up the size of the CXI stack size to a page (4KB) boundary, as the OS can only allocate memory this way
u32 stackSize = (cxi.stackSize + pageSize - 1) & -pageSize;
if (stackSize > 512_KB) {
// TODO: Figure out the actual max stack size
Helpers::warn("CXI stack size is %08X which seems way too big. Clamping to 512KB", stackSize);
stackSize = 512_KB;
}
// Allocate stack
if (!allocateMainThreadStack(stackSize)) {
// Should be unreachable
@@ -42,40 +39,41 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
u32 bssSize = (cxi.bssSize + 0xfff) & ~0xfff; // Round BSS size up to a page boundary
// Total memory to allocate for loading
u32 totalSize = (cxi.text.pageCount + cxi.rodata.pageCount + cxi.data.pageCount) * pageSize + bssSize;
code.resize(code.size() + bssSize, 0); // Pad the .code file with zeroes for the BSS segment
if (code.size() < totalSize) {
if (code.size() + bssSize < totalSize) {
Helpers::panic("Total code size as reported by the exheader is larger than the .code file");
return false;
}
const auto opt = findPaddr(totalSize);
if (!opt.has_value()) {
Helpers::panic("Failed to find paddr to map CXI file's code to");
return false;
}
const auto paddr = opt.value();
std::memcpy(&fcram[paddr], &code[0], totalSize); // Copy the 3 segments + BSS to FCRAM
// Map the ROM on the kernel side
u32 textOffset = 0;
u32 textAddr = cxi.text.address;
u32 textSize = cxi.text.pageCount * pageSize;
u32 rodataOffset = textOffset + textSize;
u32 rodataAddr = cxi.rodata.address;
u32 rodataSize = cxi.rodata.pageCount * pageSize;
u32 dataOffset = rodataOffset + rodataSize;
u32 dataAddr = cxi.data.address;
u32 dataSize = cxi.data.pageCount * pageSize + bssSize; // We're merging the data and BSS segments, as BSS is just pre-initted .data
allocateMemory(textAddr, paddr + textOffset, textSize, true, true, false, true); // Text is R-X
allocateMemory(rodataAddr, paddr + rodataOffset, rodataSize, true, true, false, false); // Rodata is R--
allocateMemory(dataAddr, paddr + dataOffset, dataSize, true, true, true, false); // Data+BSS is RW-
// TODO: base this off the exheader
auto region = FcramRegion::App;
u32 bssAddr = dataAddr + (cxi.data.pageCount << 12);
ncsd.entrypoint = textAddr;
allocMemory(textAddr, cxi.text.pageCount, region, true, false, true, MemoryState::Code);
allocMemory(rodataAddr, cxi.rodata.pageCount, region, true, false, false, MemoryState::Code);
allocMemory(dataAddr, cxi.data.pageCount, region, true, true, false, MemoryState::Private);
allocMemory(bssAddr, bssSize >> 12, region, true, true, false, MemoryState::Private);
// Copy .code file to FCRAM
copyToVaddr(textAddr, code.data(), textSize);
copyToVaddr(rodataAddr, code.data() + textSize, rodataSize);
copyToVaddr(dataAddr, code.data() + textSize + rodataSize, cxi.data.pageCount << 12);
// Set BSS to zeroes
std::vector<u8> bss(bssSize, 0);
copyToVaddr(bssAddr, bss.data(), bssSize);
ncsd.entrypoint = cxi.text.address;
// Back the IOFile for accessing the ROM, as well as the ROM's CXI partition, in the memory class.
CXIFile = ncsd.file;
@@ -85,7 +83,9 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
std::optional<NCSD> Memory::loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path) {
NCSD ncsd;
if (!ncsd.file.open(path, "rb")) return std::nullopt;
if (!ncsd.file.open(path, "rb")) {
return std::nullopt;
}
u8 magic[4]; // Must be "NCSD"
ncsd.file.seek(0x100);

View File

@@ -6,6 +6,7 @@
#include <ctime>
#include "config_mem.hpp"
#include "kernel/fcram.hpp"
#include "resource_limits.hpp"
#include "services/fonts.hpp"
#include "services/ptm.hpp"
@@ -14,38 +15,43 @@ CMRC_DECLARE(ConsoleFonts);
using namespace KernelMemoryTypes;
Memory::Memory(u64& cpuTicks, const EmulatorConfig& config) : cpuTicks(cpuTicks), config(config) {
fcram = new uint8_t[FCRAM_SIZE]();
Memory::Memory(KFcram& fcramManager, const EmulatorConfig& config) : fcramManager(fcramManager), config(config) {
const bool fastmemEnabled = config.fastmemEnabled;
arena = new Common::HostMemory(FASTMEM_BACKING_SIZE, FASTMEM_VIRTUAL_SIZE, fastmemEnabled);
readTable.resize(totalPageCount, 0);
writeTable.resize(totalPageCount, 0);
memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs
paddrTable.resize(totalPageCount, 0);
fcram = arena->BackingBasePointer() + FASTMEM_FCRAM_OFFSET;
// arenaDSPRam = arena->BackingBasePointer() + FASTMEM_DSP_RAM_OFFSET;
useFastmem = fastmemEnabled && arena->VirtualBasePointer() != nullptr;
}
void Memory::reset() {
// Unallocate all memory
// Mark the entire process address space as free
constexpr static int MAX_USER_PAGES = 0x40000000 >> 12;
memoryInfo.clear();
usedFCRAMPages.reset();
usedUserMemory = u32(0_MB);
usedSystemMemory = u32(0_MB);
memoryInfo.push_back(MemoryInfo(0, MAX_USER_PAGES, 0, KernelMemoryTypes::Free));
// TODO: remove this, only needed to make the subsequent allocations work for now
fcramManager.reset(FCRAM_SIZE, FCRAM_APPLICATION_SIZE, FCRAM_SYSTEM_SIZE, FCRAM_BASE_SIZE);
if (useFastmem) {
// Unmap any mappings when resetting
arena->Unmap(0, 4_GB, false);
}
for (u32 i = 0; i < totalPageCount; i++) {
readTable[i] = 0;
writeTable[i] = 0;
paddrTable[i] = 0;
}
// Map (32 * 4) KB of FCRAM before the stack for the TLS of each thread
std::optional<u32> tlsBaseOpt = findPaddr(32 * 4_KB);
if (!tlsBaseOpt.has_value()) { // Should be unreachable but still good to have
Helpers::panic("Failed to allocate memory for thread-local storage");
}
u32 basePaddrForTLS = tlsBaseOpt.value();
for (u32 i = 0; i < appResourceLimits.maxThreads; i++) {
u32 vaddr = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
allocateMemory(vaddr, basePaddrForTLS, VirtualAddrs::TLSSize, true);
basePaddrForTLS += VirtualAddrs::TLSSize;
}
// Allocate 512 bytes of TLS for each thread. Since the smallest allocatable unit is 4 KB, that means allocating one page for every 8 threads
// Note that TLS is always allocated in the Base region
s32 tlsPages = (appResourceLimits.maxThreads + 7) >> 3;
allocMemory(VirtualAddrs::TLSBase, tlsPages, FcramRegion::Base, true, true, false, MemoryState::Locked);
// Initialize shared memory blocks and reserve memory for them
for (auto& e : sharedMemBlocks) {
@@ -56,19 +62,23 @@ void Memory::reset() {
}
e.mapped = false;
e.paddr = allocateSysMemory(e.size);
FcramBlockList memBlock;
fcramManager.alloc(memBlock, e.size >> 12, FcramRegion::Sys, false);
e.paddr = memBlock.begin()->paddr;
}
// Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF]
constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages
constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space
constexpr u32 dspRamPages = DSP_RAM_SIZE / pageSize; // Number of DSP RAM pages
for (u32 i = 0; i < dspRamPages; i++) {
auto pointer = uintptr_t(&dspRam[i * pageSize]);
u32 vaddr = VirtualAddrs::DSPMemStart;
u32 paddr = PhysicalAddrs::DSP_RAM;
readTable[i + initialPage] = pointer;
writeTable[i + initialPage] = pointer;
}
Operation op{.newState = MemoryState::Static, .r = true, .w = true, .changeState = true, .changePerms = true};
changeMemoryState(vaddr, dspRamPages, op);
mapPhysicalMemory(vaddr, paddr, dspRamPages, true, true, false);
// Allocate RW mapping for DSP RAM
// addFastmemView(VirtualAddrs::DSPMemStart, FASTMEM_DSP_RAM_OFFSET, DSP_RAM_SIZE, true, false);
// Later adjusted based on ROM header when possible
region = Regions::USA;
@@ -76,14 +86,9 @@ void Memory::reset() {
bool Memory::allocateMainThreadStack(u32 size) {
// Map stack pages as R/W
std::optional<u32> basePaddr = findPaddr(size);
if (!basePaddr.has_value()) { // Should also be unreachable but still good to have
return false;
}
// TODO: get the region from the exheader
const u32 stackBottom = VirtualAddrs::StackTop - size;
std::optional<u32> result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt
return result.has_value();
return allocMemory(stackBottom, size >> 12, FcramRegion::App, true, true, false, MemoryState::Locked);
}
u8 Memory::read8(u32 vaddr) {
@@ -120,7 +125,7 @@ u8 Memory::read8(u32 vaddr) {
case ConfigMem::FirmRevision: return firm.revision;
case ConfigMem::FirmVersionMinor: return firm.minor;
case ConfigMem::FirmVersionMajor: return firm.major;
case ConfigMem::WifiLevel: return 0; // No wifi :(
case ConfigMem::WifiLevel: return 0; // No wifi :(
case ConfigMem::WifiMac:
case ConfigMem::WifiMac + 1:
@@ -163,8 +168,8 @@ u32 Memory::read32(u32 vaddr) {
case ConfigMem::Datetime0 + 4:
return u32(timeSince3DSEpoch() >> 32); // top 32 bits
// Ticks since time was last updated. For now we return the current tick count
case ConfigMem::Datetime0 + 8: return u32(cpuTicks);
case ConfigMem::Datetime0 + 12: return u32(cpuTicks >> 32);
case ConfigMem::Datetime0 + 8: return u32(*cpuTicks);
case ConfigMem::Datetime0 + 12: return u32(*cpuTicks >> 32);
case ConfigMem::Datetime0 + 16: return 0xFFB0FF0; // Unknown, set by PTM
case ConfigMem::Datetime0 + 20:
case ConfigMem::Datetime0 + 24:
@@ -172,11 +177,10 @@ u32 Memory::read32(u32 vaddr) {
case ConfigMem::AppMemAlloc: return appResourceLimits.maxCommit;
case ConfigMem::SyscoreVer: return 2;
case 0x1FF81000: return 0; // TODO: Figure out what this config mem address does
case 0x1FF81000:
return 0; // TODO: Figure out what this config mem address does
// Wifi MAC: First 4 bytes of MAC Address
case ConfigMem::WifiMac:
return (u32(MACAddress[3]) << 24) | (u32(MACAddress[2]) << 16) | (u32(MACAddress[1]) << 8) |
MACAddress[0];
case ConfigMem::WifiMac: return (u32(MACAddress[3]) << 24) | (u32(MACAddress[2]) << 16) | (u32(MACAddress[1]) << 8) | MACAddress[0];
// 3D slider. Float in range 0.0 = off, 1.0 = max.
case ConfigMem::SliderState3D: return Helpers::bit_cast<u32, float>(0.0f);
@@ -186,7 +190,7 @@ u32 Memory::read32(u32 vaddr) {
default:
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
static int shutUpCounter = 0;
if (shutUpCounter < 5) { // Stop spamming about VRAM reads after the first 5
if (shutUpCounter < 5) { // Stop spamming about VRAM reads after the first 5
shutUpCounter++;
Helpers::warn("VRAM read!\n");
}
@@ -296,149 +300,254 @@ std::string Memory::readString(u32 address, u32 maxSize) {
// thanks to the New 3DS having more FCRAM
u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; }
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x, bool adjustAddrs, bool isMap) {
// Kernel-allocated memory & size must always be aligned to a page boundary
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland
// If we're mapping there's no fear of OoM, because we're not really allocating memory, just binding vaddrs to specific paddrs
assert(isAligned(vaddr) && isAligned(paddr) && isAligned(size));
assert(size <= FCRAM_APPLICATION_SIZE || isMap);
assert(usedUserMemory + size <= FCRAM_APPLICATION_SIZE || isMap);
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
void Memory::changeMemoryState(u32 vaddr, s32 pages, const Operation& op) {
assert(!(vaddr & 0xFFF));
// Amount of available user FCRAM pages and FCRAM pages to allocate respectively
const u32 availablePageCount = (FCRAM_APPLICATION_SIZE - usedUserMemory) / pageSize;
const u32 neededPageCount = size / pageSize;
if (!op.changePerms && !op.changeState) Helpers::panic("Invalid op passed to changeMemoryState!");
assert(availablePageCount >= neededPageCount || isMap);
bool blockFound = false;
// If the paddr is 0, that means we need to select our own
// TODO: Fix. This method always tries to allocate blocks linearly.
// However, if the allocation is non-linear, the panic will trigger when it shouldn't.
// Non-linear allocation needs special handling
if (paddr == 0 && adjustAddrs) {
std::optional<u32> newPaddr = findPaddr(size);
if (!newPaddr.has_value()) {
Helpers::panic("Failed to find paddr");
for (auto it = memoryInfo.begin(); it != memoryInfo.end(); it++) {
// Find the block that the memory region is located in
u32 blockStart = it->baseAddr;
u32 blockEnd = it->end();
u32 reqStart = vaddr;
u32 reqEnd = vaddr + (pages << 12);
if (!(reqStart >= blockStart && reqEnd <= blockEnd)) continue;
// Now that the block has been found, fill it with the necessary info
auto oldState = it->state;
u32 oldPerms = it->perms;
it->baseAddr = reqStart;
it->pages = pages;
if (op.changePerms) it->perms = (op.r ? PERMISSION_R : 0) | (op.w ? PERMISSION_W : 0) | (op.x ? PERMISSION_X : 0);
if (op.changeState) it->state = op.newState;
// If the requested memory region is smaller than the block found, the block must be split
if (blockStart < reqStart) {
MemoryInfo startBlock(blockStart, (reqStart - blockStart) >> 12, oldPerms, oldState);
memoryInfo.insert(it, startBlock);
}
paddr = newPaddr.value();
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
}
// If the vaddr is 0 that means we need to select our own
// Depending on whether our mapping should be linear or not we allocate from one of the 2 typical heap spaces
// We don't plan on implementing freeing any time soon, so we can pick added userUserMemory to the vaddr base to
// Get the full vaddr.
// TODO: Fix this
if (vaddr == 0 && adjustAddrs) {
// Linear memory needs to be allocated in a way where you can easily get the paddr by subtracting the linear heap base
// In order to be able to easily send data to hardware like the GPU
if (linear) {
vaddr = getLinearHeapVaddr() + paddr;
} else {
vaddr = usedUserMemory + VirtualAddrs::NormalHeapStart;
}
}
if (!isMap) {
usedUserMemory += size;
}
// Do linear mapping
u32 virtualPage = vaddr >> pageShift;
u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary
for (u32 i = 0; i < neededPageCount; i++) {
if (r) {
readTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
}
if (w) {
writeTable[virtualPage] = uintptr_t(&fcram[physPage * pageSize]);
if (reqEnd < blockEnd) {
auto itAfter = std::next(it);
MemoryInfo endBlock(reqEnd, (blockEnd - reqEnd) >> 12, oldPerms, oldState);
memoryInfo.insert(itAfter, endBlock);
}
// Mark FCRAM page as allocated and go on
usedFCRAMPages[physPage] = true;
virtualPage++;
physPage++;
blockFound = true;
break;
}
// Back up the info for this allocation in our memoryInfo vector
u32 perms = (r ? PERMISSION_R : 0) | (w ? PERMISSION_W : 0) | (x ? PERMISSION_X : 0);
memoryInfo.push_back(std::move(MemoryInfo(vaddr, size, perms, KernelMemoryTypes::Reserved)));
if (!blockFound) Helpers::panic("Unable to find block in changeMemoryState!");
return vaddr;
// Merge all blocks with the same state and permissions
for (auto it = memoryInfo.begin(); it != memoryInfo.end();) {
auto next = std::next(it);
if (next == memoryInfo.end()) break;
if (it->state != next->state || it->perms != next->perms) {
it++;
continue;
}
next->baseAddr = it->baseAddr;
next->pages += it->pages;
it = memoryInfo.erase(it);
}
}
// Find a paddr which we can use for allocating "size" bytes
std::optional<u32> Memory::findPaddr(u32 size) {
assert(isAligned(size));
const u32 neededPages = size / pageSize;
void Memory::queryPhysicalBlocks(FcramBlockList& outList, u32 vaddr, s32 pages) {
s32 srcPages = pages;
for (auto& alloc : memoryInfo) {
u32 blockStart = alloc.baseAddr;
u32 blockEnd = alloc.end();
// The FCRAM page we're testing to see if it's appropriate to use
u32 candidatePage = 0;
// The number of linear available pages we could find starting from this candidate page.
// If this ends up >= than neededPages then the paddr is good (ie we can use the candidate page as a base address)
u32 counter = 0;
if (!(vaddr >= blockStart && vaddr < blockEnd)) continue;
for (u32 i = 0; i < FCRAM_APPLICATION_PAGE_COUNT; i++) {
if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate
candidatePage = i + 1;
counter = 0;
} else { // The paddr we're testing has 1 more free page
counter++;
// Check if there's enough free memory to use this page
// We use == instead of >= because some software does 0-byte allocations
if (counter >= neededPages) {
return candidatePage * pageSize;
s32 blockPaddr = paddrTable[vaddr >> 12];
s32 blockPages = alloc.pages - ((vaddr - blockStart) >> 12);
blockPages = std::min(srcPages, blockPages);
FcramBlock physicalBlock(blockPaddr, blockPages);
outList.push_back(physicalBlock);
vaddr += blockPages << 12;
srcPages -= blockPages;
if (srcPages == 0) break;
}
if (srcPages != 0) Helpers::panic("Unable to find virtual pages to map!");
}
void Memory::mapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages, bool r, bool w, bool x) {
assert(!(vaddr & 0xFFF));
assert(!(paddr & 0xFFF));
// TODO: make this a separate function
u8* hostPtr = nullptr;
if (paddr < FCRAM_SIZE) {
hostPtr = fcram + paddr; // FIXME: FCRAM doesn't actually start from physical address 0, but from 0x20000000
if (useFastmem) {
addFastmemView(vaddr, FASTMEM_FCRAM_OFFSET + paddr, usize(pages) * pageSize, w);
}
} else if (paddr >= VirtualAddrs::DSPMemStart && paddr < VirtualAddrs::DSPMemStart + DSP_RAM_SIZE) {
hostPtr = dspRam + (paddr - VirtualAddrs::DSPMemStart);
}
for (int i = 0; i < pages; i++) {
u32 index = (vaddr >> 12) + i;
paddrTable[index] = paddr + (i << 12);
if (r)
readTable[index] = (uintptr_t)(hostPtr + (i << 12));
else
readTable[index] = 0;
if (w)
writeTable[index] = (uintptr_t)(hostPtr + (i << 12));
else
writeTable[index] = 0;
}
}
void Memory::unmapPhysicalMemory(u32 vaddr, u32 paddr, s32 pages) {
for (int i = 0; i < pages; i++) {
u32 index = (vaddr >> 12) + i;
paddrTable[index] = 0;
readTable[index] = 0;
writeTable[index] = 0;
}
if (useFastmem) {
arena->Unmap(vaddr, pages * pageSize, false);
}
}
bool Memory::allocMemory(u32 vaddr, s32 pages, FcramRegion region, bool r, bool w, bool x, MemoryState state) {
auto res = testMemoryState(vaddr, pages, MemoryState::Free);
if (res.isFailure()) return false;
FcramBlockList memList;
fcramManager.alloc(memList, pages, region, false);
for (auto it = memList.begin(); it != memList.end(); it++) {
Operation op{.newState = state, .r = r, .w = w, .x = x, .changeState = true, .changePerms = true};
changeMemoryState(vaddr, it->pages, op);
mapPhysicalMemory(vaddr, it->paddr, it->pages, r, w, x);
vaddr += it->pages << 12;
}
return true;
}
bool Memory::allocMemoryLinear(u32& outVaddr, u32 inVaddr, s32 pages, FcramRegion region, bool r, bool w, bool x) {
if (inVaddr) Helpers::panic("inVaddr specified for linear allocation!");
FcramBlockList memList;
fcramManager.alloc(memList, pages, region, true);
u32 paddr = memList.begin()->paddr;
u32 vaddr = getLinearHeapVaddr() + paddr;
auto res = testMemoryState(vaddr, pages, MemoryState::Free);
if (res.isFailure()) Helpers::panic("Unable to map linear allocation (vaddr:%08X pages:%08X)", vaddr, pages);
Operation op{.newState = MemoryState::Continuous, .r = r, .w = w, .x = x, .changeState = true, .changePerms = true};
changeMemoryState(vaddr, pages, op);
mapPhysicalMemory(vaddr, paddr, pages, r, w, x);
outVaddr = vaddr;
return true;
}
bool Memory::mapVirtualMemory(
u32 dstVaddr, u32 srcVaddr, s32 pages, bool r, bool w, bool x, MemoryState oldDstState, MemoryState oldSrcState, MemoryState newDstState,
MemoryState newSrcState, bool unmapPages
) {
// Check that the regions have the specified state
// TODO: check src perms
auto res = testMemoryState(srcVaddr, pages, oldSrcState);
if (res.isFailure()) return false;
res = testMemoryState(dstVaddr, pages, oldDstState);
if (res.isFailure()) return false;
// Change the virtual memory state for both regions
Operation srcOp{.newState = newSrcState, .changeState = true};
changeMemoryState(srcVaddr, pages, srcOp);
Operation dstOp{.newState = newDstState, .r = r, .w = w, .x = x, .changeState = true, .changePerms = true};
changeMemoryState(dstVaddr, pages, dstOp);
// Get a list of physical blocks in the source region
FcramBlockList physicalList;
queryPhysicalBlocks(physicalList, srcVaddr, pages);
// Map or unmap each physical block
for (auto& block : physicalList) {
if (newDstState == MemoryState::Free) {
// TODO: Games with CROs will unmap the CRO yet still continue accessing the address it was mapped to?
if (unmapPages) {
unmapPhysicalMemory(dstVaddr, block.paddr, block.pages);
}
} else {
mapPhysicalMemory(dstVaddr, block.paddr, block.pages, r, w, x);
}
dstVaddr += block.pages << 12;
}
// Couldn't find any page :(
return std::nullopt;
return true;
}
u32 Memory::allocateSysMemory(u32 size) {
// Should never be triggered, only here as a sanity check
if (!isAligned(size)) {
Helpers::panic("Memory::allocateSysMemory: Size is not page aligned (val = %08X)", size);
void Memory::changePermissions(u32 vaddr, s32 pages, bool r, bool w, bool x) {
Operation op{.r = r, .w = w, .x = x, .changePerms = true};
changeMemoryState(vaddr, pages, op);
// Now that permissions have been changed, update the corresponding host tables
FcramBlockList physicalList;
queryPhysicalBlocks(physicalList, vaddr, pages);
for (auto& block : physicalList) {
mapPhysicalMemory(vaddr, block.paddr, block.pages, r, w, x);
vaddr += block.pages;
}
// We use a pretty dumb allocator for OS memory since this is not really accessible to the app and is only used internally
// It works by just allocating memory linearly, starting from index 0 of OS memory and going up
// This should also be unreachable in practice and exists as a sanity check
if (size > remainingSysFCRAM()) {
Helpers::panic("Memory::allocateSysMemory: Overflowed OS FCRAM");
}
const u32 pageCount = size / pageSize; // Number of pages that will be used up
const u32 startIndex = sysFCRAMIndex() + usedSystemMemory; // Starting FCRAM index
const u32 startingPage = startIndex / pageSize;
for (u32 i = 0; i < pageCount; i++) {
if (usedFCRAMPages[startingPage + i]) // Also a theoretically unreachable panic for safety
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
usedFCRAMPages[startingPage + i] = true;
}
usedSystemMemory += size;
return startIndex;
}
// The way I understand how the kernel's QueryMemory is supposed to work is that you give it a vaddr
// And the kernel looks up the memory allocations it's performed, finds which one it belongs in and returns its info?
// TODO: Verify this
MemoryInfo Memory::queryMemory(u32 vaddr) {
Result::HorizonResult Memory::queryMemory(MemoryInfo& out, u32 vaddr) {
// Check each allocation
for (auto& alloc : memoryInfo) {
// Check if the memory address belongs in this allocation and return the info if so
if (vaddr >= alloc.baseAddr && vaddr < alloc.end()) {
return alloc;
out = alloc;
return Result::Success;
}
}
// Otherwise, if this vaddr was never allocated
// TODO: I think this is meant to return how much memory starting here is free as the size?
return MemoryInfo(vaddr, pageSize, 0, KernelMemoryTypes::Free);
// Official kernel just returns an error here
Helpers::warn("Failed to find block in QueryMemory!");
return Result::FailurePlaceholder;
}
Result::HorizonResult Memory::testMemoryState(u32 vaddr, s32 pages, MemoryState desiredState) {
for (auto& alloc : memoryInfo) {
// Don't bother checking if we're to the left of the requested region
if (vaddr >= alloc.end()) continue;
if (alloc.state != desiredState) return Result::FailurePlaceholder; // TODO: error for state mismatch
// If the end of this block comes after the end of the requested range with no errors, it's a success
if (alloc.end() >= vaddr + (pages << 12)) return Result::Success;
}
// TODO: error for when address is outside of userland
return Result::FailurePlaceholder;
}
void Memory::copyToVaddr(u32 dstVaddr, const u8* srcHost, s32 size) {
// TODO: check for noncontiguous allocations
u8* dstHost = (u8*)readTable[dstVaddr >> 12] + (dstVaddr & 0xFFF);
memcpy(dstHost, srcHost, size);
}
u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms) {
@@ -459,13 +568,11 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
bool w = myPerms & 0b010;
bool x = myPerms & 0b100;
const auto result = allocateMemory(vaddr, paddr, size, true, r, w, x, false, true);
e.mapped = true;
if (!result.has_value()) {
Helpers::panic("Memory::mapSharedMemory: Failed to map shared memory block");
return nullptr;
}
Operation op{.newState = MemoryState::Shared, .r = r, .w = x, .x = x, .changeState = true, .changePerms = true};
changeMemoryState(vaddr, size >> 12, op);
mapPhysicalMemory(vaddr, paddr, size >> 12, r, w, x);
e.mapped = true;
return &fcram[paddr];
}
}
@@ -475,24 +582,6 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
return nullptr;
}
void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) {
// Should theoretically be unreachable, only here for safety purposes
assert(isAligned(destAddress) && isAligned(sourceAddress) && isAligned(size));
const u32 pageCount = size / pageSize; // How many pages we need to mirror
for (u32 i = 0; i < pageCount; i++) {
// Redo the shift here to "properly" handle wrapping around the address space instead of reading OoB
const u32 sourcePage = sourceAddress / pageSize;
const u32 destPage = destAddress / pageSize;
readTable[destPage] = readTable[sourcePage];
writeTable[destPage] = writeTable[sourcePage];
sourceAddress += pageSize;
destAddress += pageSize;
}
}
// Get the number of ms since Jan 1 1900
u64 Memory::timeSince3DSEpoch() {
using namespace std::chrono;

View File

@@ -16,27 +16,27 @@ namespace PICA {
decodeTexelAI8ToRG8,
true,
{
.red = MTL::TextureSwizzleRed,
.green = MTL::TextureSwizzleRed,
.blue = MTL::TextureSwizzleRed,
.alpha = MTL::TextureSwizzleGreen,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleGreen,
}}, // IA8
{MTL::PixelFormatRG8Unorm, 2, decodeTexelGR8ToRG8}, // RG8
{MTL::PixelFormatR8Unorm,
1,
decodeTexelI8ToR8,
true,
{.red = MTL::TextureSwizzleRed, .green = MTL::TextureSwizzleRed, .blue = MTL::TextureSwizzleRed, .alpha = MTL::TextureSwizzleOne}}, // I8
{MTL::PixelFormatA8Unorm, 1, decodeTexelA8ToA8}, // A8
{MTL::PixelFormatABGR4Unorm, 2, decodeTexelAI4ToABGR4}, // IA4
{MTL::TextureSwizzleRed, MTL::TextureSwizzleRed, MTL::TextureSwizzleRed, MTL::TextureSwizzleOne}}, // I8
{MTL::PixelFormatA8Unorm, 1, decodeTexelA8ToA8}, // A8
{MTL::PixelFormatABGR4Unorm, 2, decodeTexelAI4ToABGR4}, // IA4
{MTL::PixelFormatR8Unorm,
1,
decodeTexelI4ToR8,
true,
{.red = MTL::TextureSwizzleRed, .green = MTL::TextureSwizzleRed, .blue = MTL::TextureSwizzleRed, .alpha = MTL::TextureSwizzleOne}}, // I4
{MTL::PixelFormatA8Unorm, 1, decodeTexelA4ToA8}, // A4
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1ToRGBA8}, // ETC1
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1A4ToRGBA8}, // ETC1A4
{MTL::TextureSwizzleRed, MTL::TextureSwizzleRed, MTL::TextureSwizzleRed, MTL::TextureSwizzleOne}}, // I4
{MTL::PixelFormatA8Unorm, 1, decodeTexelA4ToA8}, // A4
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1ToRGBA8}, // ETC1
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1A4ToRGBA8}, // ETC1A4
};
void checkForMTLPixelFormatSupport(MTL::Device* device) {
@@ -57,10 +57,10 @@ namespace PICA {
decodeTexelAI4ToRG8,
true,
{
.red = MTL::TextureSwizzleRed,
.green = MTL::TextureSwizzleRed,
.blue = MTL::TextureSwizzleRed,
.alpha = MTL::TextureSwizzleGreen,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleRed,
MTL::TextureSwizzleGreen,
}
};
}

View File

@@ -27,6 +27,7 @@ namespace FRDCommands {
GetFriendAttributeFlags = 0x00170042,
UpdateGameModeDescription = 0x001D0002,
SaveLocalAccountData = 0x04050000,
UpdateMii = 0x040C0800,
};
}
@@ -61,6 +62,7 @@ void FRDService::handleSyncRequest(u32 messagePointer, FRDService::Type type) {
if (type == Type::A) {
switch (command) {
case FRDCommands::UpdateMii: updateMii(messagePointer); break;
case FRDCommands::SaveLocalAccountData: saveLocalAccountData(messagePointer); break;
default: Helpers::panic("FRD:A service requested. Command: %08X\n", command); break;
}
} else {
@@ -265,6 +267,13 @@ void FRDService::logout(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::saveLocalAccountData(u32 messagePointer) {
log("FRD::SaveLocalAccountData (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x405, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::updateMii(u32 messagePointer) {
log("FRD::UpdateMii (stubbed)\n");

View File

@@ -194,7 +194,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::SetThisSaveDataSecureValue: setThisSaveDataSecureValue(messagePointer); break;
case FSCommands::AbnegateAccessRight: abnegateAccessRight(messagePointer); break;
case FSCommands::TheGameboyVCFunction: theGameboyVCFunction(messagePointer); break;
default: Helpers::panic("FS service requested. Command: %08X\n", command);
default:
Helpers::warn("Unimplemented FS service requested. Command: %08X\n", command);
mem.write32(messagePointer + 4, Result::Success);
break;
}
}

View File

@@ -23,7 +23,8 @@ namespace CROHeader {
NameOffset = 0x084,
NextCRO = 0x088,
PrevCRO = 0x08C,
FixedSize = 0x98,
FileSize = 0x090,
FixedSize = 0x098,
OnUnresolved = 0x0AC,
CodeOffset = 0x0B0,
DataOffset = 0x0B8,
@@ -146,8 +147,10 @@ static const std::string CRO_MAGIC("CRO0");
static const std::string CRO_MAGIC_FIXED("FIXD");
static const std::string CRR_MAGIC("CRR0");
using namespace KernelMemoryTypes;
class CRO {
Memory &mem;
Memory& mem;
u32 croPointer; // Origin address of CRO in RAM
u32 oldDataSegmentOffset;
@@ -155,7 +158,7 @@ class CRO {
bool isCRO; // False if CRS
public:
CRO(Memory &mem, u32 croPointer, bool isCRO) : mem(mem), croPointer(croPointer), oldDataSegmentOffset(0), isCRO(isCRO) {}
CRO(Memory& mem, u32 croPointer, bool isCRO) : mem(mem), croPointer(croPointer), oldDataSegmentOffset(0), isCRO(isCRO) {}
~CRO() = default;
std::string getModuleName() {
@@ -164,25 +167,15 @@ class CRO {
return mem.readString(moduleName.offset, moduleName.size);
}
u32 getNextCRO() {
return mem.read32(croPointer + CROHeader::NextCRO);
}
u32 getPrevCRO() {
return mem.read32(croPointer + CROHeader::PrevCRO);
}
u32 getNextCRO() { return mem.read32(croPointer + CROHeader::NextCRO); }
u32 getFixedSize() {
return mem.read32(croPointer + CROHeader::FixedSize);
}
u32 getPrevCRO() { return mem.read32(croPointer + CROHeader::PrevCRO); }
void setNextCRO(u32 nextCRO) {
mem.write32(croPointer + CROHeader::NextCRO, nextCRO);
}
u32 getFixedSize() { return mem.read32(croPointer + CROHeader::FixedSize); }
void setPrevCRO(u32 prevCRO) {
mem.write32(croPointer + CROHeader::PrevCRO, prevCRO);
}
void setNextCRO(u32 nextCRO) { mem.write32(croPointer + CROHeader::NextCRO, nextCRO); }
void setPrevCRO(u32 prevCRO) { mem.write32(croPointer + CROHeader::PrevCRO, prevCRO); }
u32 getSize() { return mem.read32(croPointer + CROHeader::FileSize); }
void write32(u32 addr, u32 value) {
// Note: some games export symbols to the static module, which doesn't contain any segments.
@@ -228,21 +221,17 @@ class CRO {
return entryOffset + offset;
}
u32 getOnUnresolvedAddr() {
return getSegmentAddr(mem.read32(croPointer + CROHeader::OnUnresolved));
}
u32 getOnUnresolvedAddr() { return getSegmentAddr(mem.read32(croPointer + CROHeader::OnUnresolved)); }
u32 getNamedExportSymbolAddr(const std::string& symbolName) {
// Note: The CRO contains a trie for fast symbol lookup. For simplicity,
// we won't use it and instead look up the symbol in the named export symbol table
const u32 exportStringSize = mem.read32(croPointer + CROHeader::ExportStringSize);
const CROHeaderEntry namedExportTable = getHeaderEntry(CROHeader::NamedExportTableOffset);
for (u32 namedExport = 0; namedExport < namedExportTable.size; namedExport++) {
const u32 nameOffset = mem.read32(namedExportTable.offset + 8 * namedExport + NamedExportTable::NameOffset);
const std::string exportSymbolName = mem.readString(nameOffset, exportStringSize);
if (symbolName.compare(exportSymbolName) == 0) {
@@ -437,7 +426,7 @@ class CRO {
return true;
}
bool rebaseSegmentTable(u32 dataVaddr, u32 bssVaddr, u32 *oldDataVaddr) {
bool rebaseSegmentTable(u32 dataVaddr, u32 bssVaddr, u32* oldDataVaddr) {
const CROHeaderEntry segmentTable = getHeaderEntry(CROHeader::SegmentTableOffset);
for (u32 segment = 0; segment < segmentTable.size; segment++) {
@@ -446,13 +435,16 @@ class CRO {
const u32 segmentID = mem.read32(segmentTable.offset + 12 * segment + SegmentTable::ID);
switch (segmentID) {
case SegmentTable::SegmentID::DATA:
*oldDataVaddr = segmentOffset + croPointer; oldDataSegmentOffset = segmentOffset; segmentOffset = dataVaddr; break;
*oldDataVaddr = segmentOffset + croPointer;
oldDataSegmentOffset = segmentOffset;
segmentOffset = dataVaddr;
break;
case SegmentTable::SegmentID::BSS: segmentOffset = bssVaddr; break;
case SegmentTable::SegmentID::TEXT:
case SegmentTable::SegmentID::RODATA:
if (segmentOffset != 0) segmentOffset += croPointer; break;
default:
Helpers::panic("Unknown segment ID = %u", segmentID);
if (segmentOffset != 0) segmentOffset += croPointer;
break;
default: Helpers::panic("Unknown segment ID = %u", segmentID);
}
mem.write32(segmentTable.offset + 12 * segment + SegmentTable::Offset, segmentOffset);
@@ -473,9 +465,9 @@ class CRO {
case SegmentTable::SegmentID::BSS: segmentOffset = 0; break;
case SegmentTable::SegmentID::TEXT:
case SegmentTable::SegmentID::RODATA:
if (segmentOffset != 0) segmentOffset -= croPointer; break;
default:
Helpers::panic("Unknown segment ID = %u", segmentID);
if (segmentOffset != 0) segmentOffset -= croPointer;
break;
default: Helpers::panic("Unknown segment ID = %u", segmentID);
}
mem.write32(segmentTable.offset + 12 * segment + SegmentTable::Offset, segmentOffset);
@@ -639,7 +631,9 @@ class CRO {
u32 relocationOffset = mem.read32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
if (relocationOffset != 0) {
mem.write32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset + croPointer);
mem.write32(
anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset + croPointer
);
}
}
@@ -653,7 +647,9 @@ class CRO {
u32 relocationOffset = mem.read32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset);
if (relocationOffset != 0) {
mem.write32(anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset - croPointer);
mem.write32(
anonymousImportTable.offset + 8 * anonymousImport + AnonymousImportTable::RelocationOffset, relocationOffset - croPointer
);
}
}
@@ -673,7 +669,6 @@ class CRO {
const u32 addend = mem.read32(relocationPatchTable.offset + 12 * relocationPatch + RelocationPatch::Addend);
const u32 segmentAddr = getSegmentAddr(segmentOffset);
const u32 entryID = mem.read32(segmentTable.offset + 12 * (segmentOffset & 0xF) + SegmentTable::ID);
u32 relocationTarget = segmentAddr;
@@ -1198,9 +1193,7 @@ class CRO {
}
};
void LDRService::reset() {
loadedCRS = 0;
}
void LDRService::reset() { loadedCRS = 0; }
void LDRService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
@@ -1245,7 +1238,14 @@ void LDRService::initialize(u32 messagePointer) {
}
// Map CRO to output address
mem.mirrorMapping(mapVaddr, crsPointer, size);
// TODO: how to handle permissions?
bool succeeded = mem.mapVirtualMemory(
mapVaddr, crsPointer, size >> 12, true, true, true, MemoryState::Free, MemoryState::Private, MemoryState::Locked, MemoryState::AliasCode
);
if (!succeeded) {
Helpers::panic("Failed to map CRS");
}
CRO crs(mem, mapVaddr, false);
@@ -1312,7 +1312,9 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) {
const u32 fixLevel = mem.read32(messagePointer + 40);
const Handle process = mem.read32(messagePointer + 52);
log("LDR_RO::LoadCRO (isNew = %d, buffer = %08X, vaddr = %08X, size = %08X, .data vaddr = %08X, .data size = %08X, .bss vaddr = %08X, .bss size = %08X, auto link = %d, fix level = %X, process = %X)\n", isNew, croPointer, mapVaddr, size, dataVaddr, dataSize, bssVaddr, bssSize, autoLink, fixLevel, process);
log("LDR_RO::LoadCRO (isNew = %d, buffer = %08X, vaddr = %08X, size = %08X, .data vaddr = %08X, .data size = %08X, .bss vaddr = %08X, .bss size "
"= %08X, auto link = %d, fix level = %X, process = %X)\n",
isNew, croPointer, mapVaddr, size, dataVaddr, dataSize, bssVaddr, bssSize, autoLink, fixLevel, process);
// Sanity checks
if (size < CRO_HEADER_SIZE) {
@@ -1332,7 +1334,14 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) {
}
// Map CRO to output address
mem.mirrorMapping(mapVaddr, croPointer, size);
// TODO: how to handle permissions?
bool succeeded = mem.mapVirtualMemory(
mapVaddr, croPointer, size >> 12, true, true, true, MemoryState::Free, MemoryState::Private, MemoryState::Locked, MemoryState::AliasCode
);
if (!succeeded) {
Helpers::panic("Failed to map CRO");
}
CRO cro(mem, mapVaddr, true);
@@ -1392,7 +1401,18 @@ void LDRService::unloadCRO(u32 messagePointer) {
Helpers::panic("Failed to unrebase CRO");
}
u32 size = cro.getSize();
bool succeeded = mem.mapVirtualMemory(
mapVaddr, croPointer, size >> 12, false, false, false, MemoryState::Locked, MemoryState::AliasCode, MemoryState::Free, MemoryState::Private,
false
);
if (!succeeded) {
Helpers::panic("Failed to unmap CRO");
}
kernel.clearInstructionCacheRange(mapVaddr, cro.getFixedSize());
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

96
src/dynamic_library.cpp Normal file
View File

@@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "dynamic_library.hpp"
#include <fmt/format.h>
#include <string>
#include <utility>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace Common {
DynamicLibrary::DynamicLibrary() = default;
DynamicLibrary::DynamicLibrary(const char* filename) { void(open(filename)); }
DynamicLibrary::DynamicLibrary(void* handle_) : handle{handle_} {}
DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)} {}
DynamicLibrary& DynamicLibrary::operator=(DynamicLibrary&& rhs) noexcept {
close();
handle = std::exchange(rhs.handle, nullptr);
return *this;
}
DynamicLibrary::~DynamicLibrary() { close(); }
std::string DynamicLibrary::getUnprefixedFilename(const char* filename) {
#if defined(_WIN32)
return std::string(filename) + ".dll";
#elif defined(__APPLE__)
return std::string(filename) + ".dylib";
#else
return std::string(filename) + ".so";
#endif
}
std::string DynamicLibrary::getVersionedFilename(const char* libname, int major, int minor) {
#if defined(_WIN32)
if (major >= 0 && minor >= 0)
return fmt::format("{}-{}-{}.dll", libname, major, minor);
else if (major >= 0)
return fmt::format("{}-{}.dll", libname, major);
else
return fmt::format("{}.dll", libname);
#elif defined(__APPLE__)
const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
if (major >= 0 && minor >= 0)
return fmt::format("{}{}.{}.{}.dylib", prefix, libname, major, minor);
else if (major >= 0)
return fmt::format("{}{}.{}.dylib", prefix, libname, major);
else
return fmt::format("{}{}.dylib", prefix, libname);
#else
const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
if (major >= 0 && minor >= 0)
return fmt::format("{}{}.so.{}.{}", prefix, libname, major, minor);
else if (major >= 0)
return fmt::format("{}{}.so.{}", prefix, libname, major);
else
return fmt::format("{}{}.so", prefix, libname);
#endif
}
bool DynamicLibrary::open(const char* filename) {
#ifdef _WIN32
handle = reinterpret_cast<void*>(LoadLibraryA(filename));
#else
handle = dlopen(filename, RTLD_NOW);
#endif
return handle != nullptr;
}
void DynamicLibrary::close() {
if (!isOpen()) return;
#ifdef _WIN32
FreeLibrary(reinterpret_cast<HMODULE>(handle));
#else
dlclose(handle);
#endif
handle = nullptr;
}
void* DynamicLibrary::getSymbolAddress(const char* name) const {
#ifdef _WIN32
return reinterpret_cast<void*>(GetProcAddress(reinterpret_cast<HMODULE>(handle), name));
#else
return reinterpret_cast<void*>(dlsym(handle, name));
#endif
}
} // namespace Common

View File

@@ -20,8 +20,8 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
Emulator::Emulator()
: config(getConfigPath()), kernel(cpu, memory, gpu, config, lua), cpu(memory, kernel, *this), gpu(memory, config),
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this),
running(false)
memory(kernel.getFcramManager(), config), cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig),
lua(*this), running(false)
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
,
httpServer(this)
@@ -159,20 +159,21 @@ void Emulator::pollScheduler() {
scheduler.updateNextTimestamp();
switch (eventType) {
case Scheduler::EventType::VBlank: [[likely]] {
// Signal that we've reached the end of a frame
frameDone = true;
lua.signalEvent(LuaEvent::Frame);
case Scheduler::EventType::VBlank:
[[likely]] {
// Signal that we've reached the end of a frame
frameDone = true;
lua.signalEvent(LuaEvent::Frame);
// Send VBlank interrupts
ServiceManager& srv = kernel.getServiceManager();
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
// Send VBlank interrupts
ServiceManager& srv = kernel.getServiceManager();
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
// Queue next VBlank event
scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60);
break;
}
// Queue next VBlank event
scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60);
break;
}
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: {
@@ -352,8 +353,7 @@ bool Emulator::loadELF(std::ifstream& file) {
std::span<u8> Emulator::getSMDH() {
switch (romType) {
case ROMType::NCSD:
case ROMType::CXI:
return memory.getCXI()->smdh;
case ROMType::CXI: return memory.getCXI()->smdh;
default: {
return std::span<u8>();
}
@@ -385,7 +385,7 @@ static void dumpRomFSNode(const RomFS::RomFSNode& node, const char* romFSBase, c
for (auto& directory : node.directories) {
const auto newPath = path / directory->name;
// Create the directory for the new folder
std::error_code ec;
std::filesystem::create_directories(newPath, ec);
@@ -464,7 +464,7 @@ void Emulator::reloadSettings() {
loadRenderdoc();
}
gpu.getRenderer()->setHashTextures(config.hashTextures);
gpu.getRenderer()->setHashTextures(config.hashTextures);
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
// Reload RPC setting if we're compiling with RPC support

View File

@@ -153,6 +153,7 @@ static int fetchVariableRange(std::string key, int min, int max) { return std::c
static void configInit() {
static const retro_variable values[] = {
{"panda3ds_use_fastmem", EmulatorConfig::enableFastmemDefault ? "Enable fastmem; enabled|disabled" : "Enable fastmem; disabled|enabled"},
{"panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault ? "Enable shader JIT; enabled|disabled" : "Enable shader JIT; disabled|enabled"},
{"panda3ds_accelerate_shaders",
EmulatorConfig::accelerateShadersDefault ? "Run 3DS shaders on the GPU; enabled|disabled" : "Run 3DS shaders on the GPU; disabled|enabled"},
@@ -188,9 +189,10 @@ static void configUpdate() {
config.rendererType = RendererType::OpenGL;
config.vsyncEnabled = fetchVariableBool("panda3ds_use_vsync", true);
config.shaderJitEnabled = fetchVariableBool("panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault);
config.fastmemEnabled = fetchVariableBool("panda3ds_use_fastmem", EmulatorConfig::enableFastmemDefault);
config.systemLanguage = EmulatorConfig::languageCodeFromString(fetchVariable("panda3ds_system_language", "en"));
config.chargerPlugged = fetchVariableBool("panda3ds_use_charger", true);
config.batteryPercentage = fetchVariableRange("panda3ds_battery_level", 5, 100);
config.systemLanguage = EmulatorConfig::languageCodeFromString(fetchVariable("panda3ds_system_language", "en"));
config.dspType = Audio::DSPCore::typeFromString(fetchVariable("panda3ds_dsp_emulation", "null"));
config.audioEnabled = fetchVariableBool("panda3ds_use_audio", false);

View File

@@ -172,6 +172,10 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
connectCheckbox(circlePadProEnabled, config.circlePadProEnabled);
genLayout->addRow(circlePadProEnabled);
QCheckBox* fastmemEnabled = new QCheckBox(tr("Enable Fastmem"));
connectCheckbox(fastmemEnabled, config.fastmemEnabled);
genLayout->addRow(fastmemEnabled);
QCheckBox* discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC"));
connectCheckbox(discordRpcEnabled, config.discordRpcEnabled);
genLayout->addRow(discordRpcEnabled);