diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f844a61..d276af52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_compile_definitions(SDL_MAIN_HANDLED) set(SDL_STATIC ON CACHE BOOL "" FORCE) set(SDL_SHARED OFF CACHE BOOL "" FORCE) +set(SDL_TEST OFF CACHE BOOL "" FORCE) add_subdirectory(third_party/SDL2) add_subdirectory(third_party/glad) add_subdirectory(third_party/toml11) @@ -91,7 +92,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/gl_state.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp - src/httpserver.cpp + src/httpserver.cpp src/stb_image_write.c ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 2de48c01..a4adc816 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -26,7 +26,7 @@ class GPU { MAKE_LOG_FUNCTION(log, gpuLogger) static constexpr u32 maxAttribCount = 12; // Up to 12 vertex attributes - static constexpr u32 vramSize = 6_MB; + static constexpr u32 vramSize = u32(6_MB); Registers regs; // GPU internal registers std::array currentAttributes; // Vertex attributes before being passed to the shader diff --git a/include/emulator.hpp b/include/emulator.hpp index 3985c613..83b832f6 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -61,12 +61,18 @@ class Emulator { std::optional romPath = std::nullopt; public: + // Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity. + // If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to + // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current ROM + // and just resets the emu + enum class ReloadOption { NoReload, Reload }; + Emulator(); ~Emulator(); void step(); void render(); - void reset(); + void reset(ReloadOption reload); void run(); void runFrame(); diff --git a/include/helpers.hpp b/include/helpers.hpp index 9830cc88..4162309e 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -113,9 +113,16 @@ namespace Helpers { } /// Extract bits from an integer-type - template - static constexpr T getBits(T value) { - return (value >> offset) & ones(); + template + static constexpr ReturnT getBits(ValueT value) { + static_assert((offset + bits) <= (CHAR_BIT * sizeof(ValueT)), "Invalid bit range"); + static_assert(bits > 0, "Invalid bit size"); + return ReturnT(ValueT(value >> offset) & ones()); + } + + template + static constexpr ValueT getBits(ValueT value) { + return getBits(value); } #ifdef HELPERS_APPLE_CLANG diff --git a/include/httpserver.hpp b/include/httpserver.hpp index 0526045a..8e01cce7 100644 --- a/include/httpserver.hpp +++ b/include/httpserver.hpp @@ -1,7 +1,9 @@ #ifdef PANDA3DS_ENABLE_HTTP_SERVER #pragma once +#include #include +#include #include #include "helpers.hpp" @@ -16,7 +18,19 @@ struct HttpServer { std::mutex actionMutex = {}; u32 pendingKey = 0; + HttpServer(); + void startHttpServer(); + std::string status(); + +private: + std::map> keyMap; + std::array pressedKeys = {}; + bool paused = false; + + u32 stringToKey(const std::string& key_name); + bool getKeyState(const std::string& key_name); + void setKeyState(const std::string& key_name, bool state); }; #endif // PANDA3DS_ENABLE_HTTP_SERVER \ No newline at end of file diff --git a/include/kernel/config_mem.hpp b/include/kernel/config_mem.hpp index dd4a3fd3..65163a52 100644 --- a/include/kernel/config_mem.hpp +++ b/include/kernel/config_mem.hpp @@ -11,20 +11,16 @@ namespace ConfigMem { AppMemAlloc = 0x1FF80040, HardwareType = 0x1FF81004, Datetime0 = 0x1FF81020, + WifiMac = 0x1FF81060, NetworkState = 0x1FF81067, LedState3D = 0x1FF81084, BatteryState = 0x1FF81085, Unknown1086 = 0x1FF81086, - HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here? + HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here? }; // Shows what type of hardware we're running on namespace HardwareCodes { - enum : u8 { - Product = 1, - Devboard = 2, - Debugger = 3, - Capture = 4 - }; + enum : u8 { Product = 1, Devboard = 2, Debugger = 3, Capture = 4 }; } -} \ No newline at end of file +} // namespace ConfigMem diff --git a/include/memory.hpp b/include/memory.hpp index 40349b46..6f33d895 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -110,7 +110,7 @@ class Memory { std::vector memoryInfo; std::array sharedMemBlocks = { - SharedMemoryBlock(0, _shared_font_len, KernelHandles::FontSharedMemHandle), // Shared memory for the system font + SharedMemoryBlock(0, u32(_shared_font_len), KernelHandles::FontSharedMemHandle), // Shared memory for the system font SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory }; @@ -121,14 +121,14 @@ public: static constexpr u32 pageMask = pageSize - 1; static constexpr u32 totalPageCount = 1 << (32 - pageShift); - static constexpr u32 FCRAM_SIZE = 128_MB; - static constexpr u32 FCRAM_APPLICATION_SIZE = 64_MB; + static constexpr u32 FCRAM_SIZE = u32(128_MB); + static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB); static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize; static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize; - static constexpr u32 DSP_RAM_SIZE = 512_KB; - static constexpr u32 DSP_CODE_MEMORY_OFFSET = 0_KB; - static constexpr u32 DSP_DATA_MEMORY_OFFSET = 256_KB; + static constexpr u32 DSP_RAM_SIZE = u32(512_KB); + static constexpr u32 DSP_CODE_MEMORY_OFFSET = u32(0_KB); + static constexpr u32 DSP_DATA_MEMORY_OFFSET = u32(256_KB); private: std::bitset usedFCRAMPages; @@ -141,8 +141,8 @@ private: public: u16 kernelVersion = 0; - u32 usedUserMemory = 0_MB; // How much of the APPLICATION FCRAM range is used (allocated to the appcore) - u32 usedSystemMemory = 0_MB; // Similar for the SYSTEM range (reserved for the syscore) + u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore) + u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore) Memory(u64& cpuTicks); void reset(); diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index f687532e..0757ea2d 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -34,6 +34,9 @@ class GPUService { u32 privilegedProcess; std::optional interruptEvent; + // Number of threads registered via RegisterInterruptRelayQueue + u32 gspThreadCount = 0; + MAKE_LOG_FUNCTION(log, gspGPULogger) void processCommandBuffer(); diff --git a/src/config.cpp b/src/config.cpp index 9964c1c7..6c9a8450 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -11,7 +11,8 @@ void EmulatorConfig::load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return - if (!std::filesystem::exists(path)) { + std::error_code error; + if (!std::filesystem::exists(path, error)) { save(path); return; } @@ -38,13 +39,19 @@ void EmulatorConfig::load(const std::filesystem::path& path) { void EmulatorConfig::save(const std::filesystem::path& path) { toml::basic_value data; - if (std::filesystem::exists(path)) { + std::error_code error; + if (std::filesystem::exists(path, error)) { try { data = toml::parse(path); } catch (std::exception& ex) { - Helpers::warn("Got exception trying to save config file. Exception: %s\n", ex.what()); + Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what()); return; } + } else { + if (error) { + Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str()); + } + printf("Saving new configuration file %s\n", path.string().c_str()); } data["GPU"]["EnableShaderJIT"] = shaderJitEnabled; @@ -52,4 +59,4 @@ void EmulatorConfig::save(const std::filesystem::path& path) { std::ofstream file(path, std::ios::out); file << data; file.close(); -} \ No newline at end of file +} diff --git a/src/core/fs/archive_ncch.cpp b/src/core/fs/archive_ncch.cpp index 3a3330b8..6684ccf4 100644 --- a/src/core/fs/archive_ncch.cpp +++ b/src/core/fs/archive_ncch.cpp @@ -138,8 +138,8 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc switch (type) { case PathType::RomFS: { - const u32 romFSSize = cxi->romFS.size; - const u32 romFSOffset = cxi->romFS.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 NCCH with too big of an offset"); } @@ -161,8 +161,8 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size } for (u64 i = 0; i < bytesRead; i++) { - mem.write8(dataPointer + i, data[i]); + mem.write8(u32(dataPointer + i), data[i]); } - return bytesRead; + return u32(bytesRead); } \ No newline at end of file diff --git a/src/core/fs/archive_self_ncch.cpp b/src/core/fs/archive_self_ncch.cpp index fa141b03..9b3aff29 100644 --- a/src/core/fs/archive_self_ncch.cpp +++ b/src/core/fs/archive_self_ncch.cpp @@ -76,8 +76,8 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc switch (type) { case PathType::RomFS: { - const u32 romFSSize = cxi->romFS.size; - const u32 romFSOffset = cxi->romFS.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"); } @@ -88,8 +88,8 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 } case PathType::ExeFS: { - const u32 exeFSSize = cxi->exeFS.size; - const u32 exeFSOffset = cxi->exeFS.offset; + 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"); } @@ -110,8 +110,8 @@ std::optional SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 } for (u64 i = 0; i < bytesRead; i++) { - mem.write8(dataPointer + i, data[i]); + mem.write8(u32(dataPointer + i), data[i]); } - return bytesRead; + return u32(bytesRead); } \ No newline at end of file diff --git a/src/core/kernel/directory_operations.cpp b/src/core/kernel/directory_operations.cpp index a1204724..fca1e108 100644 --- a/src/core/kernel/directory_operations.cpp +++ b/src/core/kernel/directory_operations.cpp @@ -96,4 +96,4 @@ void Kernel::readDirectory(u32 messagePointer, Handle directory) { mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, count); -} \ No newline at end of file +} diff --git a/src/core/kernel/file_operations.cpp b/src/core/kernel/file_operations.cpp index 455e9fc2..c7837100 100644 --- a/src/core/kernel/file_operations.cpp +++ b/src/core/kernel/file_operations.cpp @@ -97,11 +97,11 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) { } else { for (size_t i = 0; i < bytesRead; i++) { - mem.write8(dataPointer + i, data[i]); + mem.write8(u32(dataPointer + i), data[i]); } mem.write32(messagePointer + 4, Result::Success); - mem.write32(messagePointer + 8, bytesRead); + mem.write32(messagePointer + 8, u32(bytesRead)); } return; @@ -142,7 +142,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) { std::unique_ptr data(new u8[size]); for (size_t i = 0; i < size; i++) { - data[i] = mem.read8(dataPointer + i); + data[i] = mem.read8(u32(dataPointer + i)); } IOFile f(file->fd); @@ -153,7 +153,7 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) { Helpers::panic("Kernel::WriteFile failed"); } else { mem.write32(messagePointer + 4, Result::Success); - mem.write32(messagePointer + 8, bytesWritten); + mem.write32(messagePointer + 8, u32(bytesWritten)); } } diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index c8325031..587d5fc4 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -286,7 +286,7 @@ int Kernel::wakeupOneThread(u64 waitlist, Handle handle) { // Get the index of the event in the object's waitlist, write it to r1 for (size_t i = 0; i < t.waitList.size(); i++) { if (t.waitList[i] == handle) { - t.gprs[1] = i; + t.gprs[1] = u32(i); break; } } @@ -321,7 +321,7 @@ void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) { // Get the index of the event in the object's waitlist, write it to r1 for (size_t i = 0; i < t.waitList.size(); i++) { if (t.waitList[i] == handle) { - t.gprs[1] = i; + t.gprs[1] = u32(i); break; } } diff --git a/src/core/loader/lz77.cpp b/src/core/loader/lz77.cpp index 254053fd..60021b13 100644 --- a/src/core/loader/lz77.cpp +++ b/src/core/loader/lz77.cpp @@ -12,7 +12,7 @@ u32 CartLZ77::decompressedSize(const u8* buffer, u32 compressedSize) { } bool CartLZ77::decompress(std::vector& output, const std::vector& input) { - u32 sizeCompressed = input.size() * sizeof(u8); + u32 sizeCompressed = u32(input.size() * sizeof(u8)); u32 sizeDecompressed = decompressedSize(input); output.resize(sizeDecompressed); diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index bbc025cc..c1ce1b98 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -112,7 +112,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn // It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header // This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra if (u32(programID) == u32(jumpID) && encrypted) { - printf("NCSD is supposedly ecrypted but not actually encrypted\n"); + printf("NCSD is supposedly encrypted but not actually encrypted\n"); encrypted = false; // Cartridge is not actually encrypted, set all of our encryption info structures to nullopt @@ -312,4 +312,4 @@ std::pair NCCH::readFromFile(IOFile& file, const FSInfo &info } return { success, bytes}; -} \ No newline at end of file +} diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 00f28eba..d3533620 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,9 +1,11 @@ #include "memory.hpp" + +#include +#include // For time since epoch +#include + #include "config_mem.hpp" #include "resource_limits.hpp" -#include -#include // For time since epoch -#include using namespace KernelMemoryTypes; @@ -13,15 +15,15 @@ Memory::Memory(u64& cpuTicks) : cpuTicks(cpuTicks) { readTable.resize(totalPageCount, 0); writeTable.resize(totalPageCount, 0); - memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs + memoryInfo.reserve(32); // Pre-allocate some room for memory allocation info to avoid dynamic allocs } void Memory::reset() { // Unallocate all memory memoryInfo.clear(); usedFCRAMPages.reset(); - usedUserMemory = 0_MB; - usedSystemMemory = 0_MB; + usedUserMemory = u32(0_MB); + usedSystemMemory = u32(0_MB); for (u32 i = 0; i < totalPageCount; i++) { readTable[i] = 0; @@ -35,7 +37,7 @@ void Memory::reset() { } u32 basePaddrForTLS = tlsBaseOpt.value(); - for (int i = 0; i < appResourceLimits.maxThreads; i++) { + for (u32 i = 0; i < appResourceLimits.maxThreads; i++) { u32 vaddr = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize; allocateMemory(vaddr, basePaddrForTLS, VirtualAddrs::TLSSize, true); basePaddrForTLS += VirtualAddrs::TLSSize; @@ -48,8 +50,8 @@ void Memory::reset() { } // 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 + constexpr u32 initialPage = VirtualAddrs::DSPMemStart / pageSize; // First page of DSP RAM in the virtual address space for (u32 i = 0; i < dspRamPages; i++) { auto pointer = uintptr_t(&dspRam[i * pageSize]); @@ -67,7 +69,7 @@ bool Memory::allocateMainThreadStack(u32 size) { } const u32 stackBottom = VirtualAddrs::StackTop - size; - std::optional result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt + std::optional result = allocateMemory(stackBottom, basePaddr.value(), size, true); // Should never be nullopt return result.has_value(); } @@ -78,18 +80,17 @@ u8 Memory::read8(u32 vaddr) { uintptr_t pointer = readTable[page]; if (pointer != 0) [[likely]] { return *(u8*)(pointer + offset); - } - else { + } else { switch (vaddr) { case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars); case ConfigMem::EnvInfo: return envInfo; case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product; case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff); case ConfigMem::KernelVersionMajor: return u8(kernelVersion >> 8); - case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now - case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection + case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now + case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection case ConfigMem::HeadphonesConnectedMaybe: return 0; - case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1 + case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1 default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr); } } @@ -102,9 +103,11 @@ u16 Memory::read16(u32 vaddr) { uintptr_t pointer = readTable[page]; if (pointer != 0) [[likely]] { return *(u16*)(pointer + offset); - } - else { - Helpers::panic("Unimplemented 16-bit read, addr: %08X", vaddr); + } else { + switch (vaddr) { + case ConfigMem::WifiMac + 4: return 0xEEFF; // Wifi MAC: Last 2 bytes of MAC Address + default: Helpers::panic("Unimplemented 16-bit read, addr: %08X", vaddr); + } } } @@ -117,18 +120,21 @@ u32 Memory::read32(u32 vaddr) { return *(u32*)(pointer + offset); } else { switch (vaddr) { - 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 + 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 // 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 + 16: return 0xFFB0FF0; // Unknown, set by PTM - case ConfigMem::Datetime0 + 20: case ConfigMem::Datetime0 + 24: case ConfigMem::Datetime0 + 28: - return 0; // Set to 0 by PTM + case ConfigMem::Datetime0 + 16: return 0xFFB0FF0; // Unknown, set by PTM + case ConfigMem::Datetime0 + 20: + case ConfigMem::Datetime0 + 24: + case ConfigMem::Datetime0 + 28: return 0; // Set to 0 by PTM 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 + case ConfigMem::WifiMac: return 0xFF07F440; // Wifi MAC: First 4 bytes of MAC Address default: if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) { Helpers::warn("VRAM read!\n"); @@ -154,13 +160,12 @@ void Memory::write8(u32 vaddr, u8 value) { uintptr_t pointer = writeTable[page]; if (pointer != 0) [[likely]] { *(u8*)(pointer + offset) = value; - } - else { + } else { // VRAM write if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) { // TODO: Invalidate renderer caches here vram[vaddr - VirtualAddrs::VramStart] = value; - } + } else { Helpers::panic("Unimplemented 8-bit write, addr: %08X, val: %02X", vaddr, value); @@ -219,11 +224,13 @@ void* Memory::getWritePointer(u32 address) { std::string Memory::readString(u32 address, u32 maxSize) { std::string string; string.reserve(maxSize); - + for (std::size_t i = 0; i < maxSize; ++i) { char c = read8(address++); - if (c == '\0') + if (c == '\0') { break; + } + string.push_back(c); } string.shrink_to_fit(); @@ -233,12 +240,9 @@ std::string Memory::readString(u32 address, u32 maxSize) { // Return a pointer to the linear heap vaddr based on the kernel ver, because it needed to be moved // thanks to the New 3DS having more FCRAM -u32 Memory::getLinearHeapVaddr() { - return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; -} +u32 Memory::getLinearHeapVaddr() { return (kernelVersion < 0x22C) ? VirtualAddrs::LinearHeapStartOld : VirtualAddrs::LinearHeapStartNew; } -std::optional Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x, - bool adjustAddrs, bool isMap) { +std::optional 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 @@ -259,8 +263,9 @@ std::optional Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l // Non-linear allocation needs special handling if (paddr == 0 && adjustAddrs) { std::optional newPaddr = findPaddr(size); - if (!newPaddr.has_value()) + if (!newPaddr.has_value()) { Helpers::panic("Failed to find paddr"); + } paddr = newPaddr.value(); assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap); @@ -281,12 +286,13 @@ std::optional Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l } } - if (!isMap) + if (!isMap) { usedUserMemory += size; + } // Do linear mapping u32 virtualPage = vaddr >> pageShift; - u32 physPage = paddr >> pageShift; // TODO: Special handle when non-linear mapping is necessary + 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]); @@ -320,11 +326,10 @@ std::optional Memory::findPaddr(u32 size) { u32 counter = 0; for (u32 i = 0; i < FCRAM_APPLICATION_PAGE_COUNT; i++) { - if (usedFCRAMPages[i]) { // Page is occupied already, go to new candidate + 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 + } 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 @@ -351,12 +356,12 @@ u32 Memory::allocateSysMemory(u32 size) { 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 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 + 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; } @@ -419,7 +424,7 @@ 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 + 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; @@ -437,20 +442,20 @@ void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) { u64 Memory::timeSince3DSEpoch() { using namespace std::chrono; - std::time_t rawTime = std::time(nullptr); // Get current UTC time - auto localTime = std::localtime(&rawTime); // Convert to local time + std::time_t rawTime = std::time(nullptr); // Get current UTC time + auto localTime = std::localtime(&rawTime); // Convert to local time - bool daylightSavings = localTime->tm_isdst > 0; // Get if time includes DST + bool daylightSavings = localTime->tm_isdst > 0; // Get if time includes DST localTime = std::gmtime(&rawTime); // Use gmtime + mktime to calculate difference between local time and UTC auto timezoneDifference = rawTime - std::mktime(localTime); if (daylightSavings) { - timezoneDifference += 60ull * 60ull; // Add 1 hour (60 seconds * 60 minutes) + timezoneDifference += 60ull * 60ull; // Add 1 hour (60 seconds * 60 minutes) } // seconds between Jan 1 1900 and Jan 1 1970 constexpr u64 offset = 2208988800ull; milliseconds ms = duration_cast(seconds(rawTime + timezoneDifference + offset)); return ms.count(); -} \ No newline at end of file +} diff --git a/src/core/renderer_gl/etc1.cpp b/src/core/renderer_gl/etc1.cpp index ae5abb65..82f06724 100644 --- a/src/core/renderer_gl/etc1.cpp +++ b/src/core/renderer_gl/etc1.cpp @@ -60,14 +60,14 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) { }; // Parse colour data for 4x4 block - const u32 subindices = getBits<0, 16>(colourData); - const u32 negationFlags = getBits<16, 16>(colourData); + const u32 subindices = getBits<0, 16, u32>(colourData); + const u32 negationFlags = getBits<16, 16, u32>(colourData); const bool flip = getBit<32>(colourData); const bool diffMode = getBit<33>(colourData); // Note: index1 is indeed stored on the higher bits, with index2 in the lower bits - const u32 tableIndex1 = getBits<37, 3>(colourData); - const u32 tableIndex2 = getBits<34, 3>(colourData); + const u32 tableIndex1 = getBits<37, 3, u32>(colourData); + const u32 tableIndex2 = getBits<34, 3, u32>(colourData); const u32 texelIndex = u * 4 + v; // Index of the texel in the block if (flip) @@ -75,14 +75,14 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) { s32 r, g, b; if (diffMode) { - r = getBits<59, 5>(colourData); - g = getBits<51, 5>(colourData); - b = getBits<43, 5>(colourData); + r = getBits<59, 5, s32>(colourData); + g = getBits<51, 5, s32>(colourData); + b = getBits<43, 5, s32>(colourData); if (u >= 2) { - r += signExtend3To32(getBits<56, 3>(colourData)); - g += signExtend3To32(getBits<48, 3>(colourData)); - b += signExtend3To32(getBits<40, 3>(colourData)); + r += signExtend3To32(getBits<56, 3, u32>(colourData)); + g += signExtend3To32(getBits<48, 3, u32>(colourData)); + b += signExtend3To32(getBits<40, 3, u32>(colourData)); } // Expand from 5 to 8 bits per channel @@ -91,13 +91,13 @@ u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) { b = Colour::convert5To8Bit(b); } else { if (u < 2) { - r = getBits<60, 4>(colourData); - g = getBits<52, 4>(colourData); - b = getBits<44, 4>(colourData); + r = getBits<60, 4, s32>(colourData); + g = getBits<52, 4, s32>(colourData); + b = getBits<44, 4, s32>(colourData); } else { - r = getBits<56, 4>(colourData); - g = getBits<48, 4>(colourData); - b = getBits<40, 4>(colourData); + r = getBits<56, 4, s32>(colourData); + g = getBits<48, 4, s32>(colourData); + b = getBits<40, 4, s32>(colourData); } // Expand from 4 to 8 bits per channel diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 73fb0919..3a13b31d 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -888,8 +888,8 @@ void Renderer::drawVertices(PICA::PrimType primType, std::span ver } // TODO: Actually use this - float viewportWidth = f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0; - float viewportHeight = f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0; + GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f); + GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f); OpenGL::setViewport(viewportWidth, viewportHeight); // Note: The code below must execute after we've bound the colour buffer & its framebuffer @@ -911,7 +911,7 @@ void Renderer::drawVertices(PICA::PrimType primType, std::span ver } vbo.bufferVertsSub(vertices); - OpenGL::draw(primitiveTopology, vertices.size()); + OpenGL::draw(primitiveTopology, GLsizei(vertices.size())); } constexpr u32 topScreenBuffer = 0x1f000000; @@ -929,10 +929,10 @@ void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 cont return; log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control); - const float r = float(getBits<24, 8>(value)) / 255.0; - const float g = float(getBits<16, 8>(value)) / 255.0; - const float b = float(getBits<8, 8>(value)) / 255.0; - const float a = float(value & 0xff) / 255.0; + const float r = float(getBits<24, 8>(value)) / 255.0f; + const float g = float(getBits<16, 8>(value)) / 255.0f; + const float b = float(getBits<8, 8>(value)) / 255.0f; + const float a = float(value & 0xff) / 255.0f; if (startAddress == topScreenBuffer) { log("GPU: Cleared top screen\n"); diff --git a/src/core/renderer_gl/textures.cpp b/src/core/renderer_gl/textures.cpp index e4df36a0..819bf783 100644 --- a/src/core/renderer_gl/textures.cpp +++ b/src/core/renderer_gl/textures.cpp @@ -119,10 +119,10 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) { auto ptr = static_cast(data); u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8); - u8 alpha = Colour::convert4To8Bit(getBits<0, 4>(texel)); - u8 b = Colour::convert4To8Bit(getBits<4, 4>(texel)); - u8 g = Colour::convert4To8Bit(getBits<8, 4>(texel)); - u8 r = Colour::convert4To8Bit(getBits<12, 4>(texel)); + u8 alpha = Colour::convert4To8Bit(getBits<0, 4, u8>(texel)); + u8 b = Colour::convert4To8Bit(getBits<4, 4, u8>(texel)); + u8 g = Colour::convert4To8Bit(getBits<8, 4, u8>(texel)); + u8 r = Colour::convert4To8Bit(getBits<12, 4, u8>(texel)); return (alpha << 24) | (b << 16) | (g << 8) | r; } @@ -133,9 +133,9 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) { u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8); u8 alpha = getBit<0>(texel) ? 0xff : 0; - u8 b = Colour::convert5To8Bit(getBits<1, 5>(texel)); - u8 g = Colour::convert5To8Bit(getBits<6, 5>(texel)); - u8 r = Colour::convert5To8Bit(getBits<11, 5>(texel)); + u8 b = Colour::convert5To8Bit(getBits<1, 5, u8>(texel)); + u8 g = Colour::convert5To8Bit(getBits<6, 5, u8>(texel)); + u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel)); return (alpha << 24) | (b << 16) | (g << 8) | r; } @@ -145,9 +145,9 @@ u32 Texture::decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data) { auto ptr = static_cast(data); u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8); - u8 b = Colour::convert5To8Bit(getBits<0, 5>(texel)); - u8 g = Colour::convert6To8Bit(getBits<5, 6>(texel)); - u8 r = Colour::convert5To8Bit(getBits<11, 5>(texel)); + u8 b = Colour::convert5To8Bit(getBits<0, 5, u8>(texel)); + u8 g = Colour::convert6To8Bit(getBits<5, 6, u8>(texel)); + u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel)); return (0xff << 24) | (b << 16) | (g << 8) | r; } diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index 376a08a0..69eb9fb3 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -143,7 +143,7 @@ std::vector DSPService::readPipe(u32 pipe, u32 size) { } std::vector& data = pipeData[pipe]; - size = std::min(size, data.size()); // Clamp size to the maximum available data size + size = std::min(size, u32(data.size())); // Clamp size to the maximum available data size if (size == 0) return {}; @@ -168,7 +168,7 @@ void DSPService::readPipeIfPossible(u32 messagePointer) { } mem.write32(messagePointer + 4, Result::Success); - mem.write16(messagePointer + 8, data.size()); // Number of bytes read + mem.write16(messagePointer + 8, u16(data.size())); // Number of bytes read } void DSPService::recvData(u32 messagePointer) { diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 2fd3b8e6..7b64b234 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -219,7 +219,7 @@ void FSService::openArchive(u32 messagePointer) { } void FSService::openFile(u32 messagePointer) { - const Handle archiveHandle = mem.read64(messagePointer + 8); + const Handle archiveHandle = Handle(mem.read64(messagePointer + 8)); const u32 filePathType = mem.read32(messagePointer + 16); const u32 filePathSize = mem.read32(messagePointer + 20); const u32 openFlags = mem.read32(messagePointer + 24); @@ -342,7 +342,7 @@ void FSService::openFileDirectly(u32 messagePointer) { } void FSService::createFile(u32 messagePointer) { - const Handle archiveHandle = mem.read64(messagePointer + 8); + const Handle archiveHandle = Handle(mem.read64(messagePointer + 8)); const u32 filePathType = mem.read32(messagePointer + 16); const u32 filePathSize = mem.read32(messagePointer + 20); const u32 attributes = mem.read32(messagePointer + 24); @@ -367,7 +367,7 @@ void FSService::createFile(u32 messagePointer) { } void FSService::deleteFile(u32 messagePointer) { - const Handle archiveHandle = mem.read64(messagePointer + 8); + const Handle archiveHandle = Handle(mem.read64(messagePointer + 8)); const u32 filePathType = mem.read32(messagePointer + 16); const u32 filePathSize = mem.read32(messagePointer + 20); const u32 filePathPointer = mem.read32(messagePointer + 28); @@ -478,7 +478,7 @@ void FSService::formatThisUserSaveData(u32 messagePointer) { } void FSService::controlArchive(u32 messagePointer) { - const Handle archiveHandle = mem.read64(messagePointer + 4); + const Handle archiveHandle = Handle(mem.read64(messagePointer + 4)); const u32 action = mem.read32(messagePointer + 12); const u32 inputSize = mem.read32(messagePointer + 16); const u32 outputSize = mem.read32(messagePointer + 20); diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index f27688a2..5179aec8 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -33,6 +33,7 @@ namespace GXCommands { void GPUService::reset() { privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle interruptEvent = std::nullopt; + gspThreadCount = 0; sharedMem = nullptr; } @@ -77,9 +78,10 @@ void GPUService::acquireRight(u32 messagePointer) { // How does the shared memory handle thing work? void GPUService::registerInterruptRelayQueue(u32 messagePointer) { // Detect if this function is called a 2nd time because we'll likely need to impl threads properly for the GSP - static bool beenHere = false; - if (beenHere) Helpers::panic("RegisterInterruptRelayQueue called a second time. Need to implement GSP threads properly"); - beenHere = true; + if (gspThreadCount >= 1) { + Helpers::panic("RegisterInterruptRelayQueue called a second time. Need to implement GSP threads properly"); + } + gspThreadCount += 1; const u32 flags = mem.read32(messagePointer + 4); const u32 eventHandle = mem.read32(messagePointer + 12); diff --git a/src/emulator.cpp b/src/emulator.cpp index f7140756..d755b89d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,5 +1,5 @@ #include "emulator.hpp" -#define STB_IMAGE_WRITE_IMPLEMENTATION + #include #ifdef _WIN32 @@ -53,13 +53,12 @@ Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory } config.load(std::filesystem::current_path() / "config.toml"); - - reset(); + reset(ReloadOption::NoReload); } Emulator::~Emulator() { config.save(std::filesystem::current_path() / "config.toml"); } -void Emulator::reset() { +void Emulator::reset(ReloadOption reload) { cpu.reset(); gpu.reset(); memory.reset(); @@ -70,8 +69,9 @@ void Emulator::reset() { // Otherwise resetting the kernel or cpu might nuke them cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP - // If a ROM is active and we reset, reload it. This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again - if (romType != ROMType::None && romPath.has_value()) { + // If a ROM is active and we reset, with the reload option enabled then reload it. + // This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again + if (reload == ReloadOption::Reload && romType != ROMType::None && romPath.has_value()) { bool success = loadROM(romPath.value()); if (!success) { romType = ROMType::None; @@ -89,18 +89,21 @@ void Emulator::run() { #ifdef PANDA3DS_ENABLE_HTTP_SERVER httpServer.startHttpServer(); #endif - while (running) { -#ifdef PANDA3DS_ENABLE_HTTP_SERVER - pollHttpServer(); -#endif - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics + while (running) { ServiceManager& srv = kernel.getServiceManager(); - // Send VBlank interrupts - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + if (romType != ROMType::None) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + pollHttpServer(); +#endif + runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics + + // Send VBlank interrupts + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + } SDL_Event event; while (SDL_PollEvent(&event)) { @@ -113,6 +116,8 @@ void Emulator::run() { return; case SDL_KEYDOWN: + if (romType == ROMType::None) break; + switch (event.key.keysym.sym) { case SDLK_l: srv.pressKey(Keys::A); break; case SDLK_k: srv.pressKey(Keys::B); break; @@ -153,6 +158,8 @@ void Emulator::run() { break; case SDL_KEYUP: + if (romType == ROMType::None) break; + switch (event.key.keysym.sym) { case SDLK_l: srv.releaseKey(Keys::A); break; case SDLK_k: srv.releaseKey(Keys::B); break; @@ -185,7 +192,9 @@ void Emulator::run() { } break; - case SDL_MOUSEBUTTONDOWN: { + case SDL_MOUSEBUTTONDOWN: + if (romType == ROMType::None) break; + if (event.button.button == SDL_BUTTON_LEFT) { const s32 x = event.button.x; const s32 y = event.button.y; @@ -203,10 +212,12 @@ void Emulator::run() { } else if (event.button.button == SDL_BUTTON_RIGHT) { holdingRightClick = true; } + break; - } case SDL_MOUSEBUTTONUP: + if (romType == ROMType::None) break; + if (event.button.button == SDL_BUTTON_LEFT) { srv.releaseTouchScreen(); } else if (event.button.button == SDL_BUTTON_RIGHT) { @@ -231,6 +242,7 @@ void Emulator::run() { case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERBUTTONDOWN: { + if (romType == ROMType::None) break; u32 key = 0; switch (event.cbutton.button) { @@ -255,12 +267,13 @@ void Emulator::run() { srv.releaseKey(key); } } + break; } // Detect mouse motion events for gyroscope emulation case SDL_MOUSEMOTION: { // We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation - if (!holdingRightClick) break; + if (romType == ROMType::None || !holdingRightClick) break; // Relative motion since last mouse motion event const s32 motionX = event.motion.xrel; @@ -274,32 +287,46 @@ void Emulator::run() { srv.setPitch(pitch); break; } + + case SDL_DROPFILE: { + char* droppedDir = event.drop.file; + + if (droppedDir) { + loadROM(droppedDir); + SDL_free(droppedDir); + } + break; + } } } - if (gameController != nullptr) { - const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); - const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); - constexpr s16 deadzone = 3276; - constexpr s16 maxValue = 0x9C; - constexpr s16 div = 0x8000 / maxValue; + // Update controller analog sticks and HID service + if (romType != ROMType::None) { + if (gameController != nullptr) { + const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + constexpr s16 deadzone = 3276; + constexpr s16 maxValue = 0x9C; + constexpr s16 div = 0x8000 / maxValue; - // Avoid overriding the keyboard's circlepad input - if (abs(stickX) < deadzone && !keyboardAnalogX) { - srv.setCirclepadX(0); - } else { - srv.setCirclepadX(stickX / div); + // Avoid overriding the keyboard's circlepad input + if (abs(stickX) < deadzone && !keyboardAnalogX) { + srv.setCirclepadX(0); + } else { + srv.setCirclepadX(stickX / div); + } + + if (abs(stickY) < deadzone && !keyboardAnalogY) { + srv.setCirclepadY(0); + } else { + srv.setCirclepadY(-(stickY / div)); + } } - if (abs(stickY) < deadzone && !keyboardAnalogY) { - srv.setCirclepadY(0); - } else { - srv.setCirclepadY(-(stickY / div)); - } + srv.updateInputs(cpu.getTicks()); } // Update inputs in the HID module - srv.updateInputs(cpu.getTicks()); SDL_GL_SwapWindow(window); } } @@ -307,6 +334,11 @@ void Emulator::run() { void Emulator::runFrame() { cpu.runFrame(); } bool Emulator::loadROM(const std::filesystem::path& path) { + // Reset the emulator if we've already loaded a ROM + if (romType != ROMType::None) { + reset(ReloadOption::NoReload); + } + // 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 @@ -326,7 +358,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) { kernel.initializeFS(); auto extension = path.extension(); - bool success; // Tracks if we loaded the ROM successfully + bool success; // Tracks if we loaded the ROM successfully if (extension == ".elf" || extension == ".axf") success = loadELF(path); @@ -390,26 +422,24 @@ bool Emulator::loadELF(std::ifstream& file) { if (entrypoint.value() & 1) { Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode"); } + return true; } // Reset our graphics context and initialize the GPU's graphics context void Emulator::initGraphicsContext() { - gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL + gl.reset(); // TODO (For when we have multiple backends): Only do this if we are using OpenGL gpu.initGraphicsContext(); } #ifdef PANDA3DS_ENABLE_HTTP_SERVER void Emulator::pollHttpServer() { std::scoped_lock lock(httpServer.actionMutex); - ServiceManager& srv = kernel.getServiceManager(); - + if (httpServer.pendingAction) { switch (httpServer.action) { - case HttpAction::Screenshot: - gpu.screenshot(HttpServer::httpServerScreenshotPath); - break; + case HttpAction::Screenshot: gpu.screenshot(HttpServer::httpServerScreenshotPath); break; case HttpAction::PressKey: if (httpServer.pendingKey != 0) { diff --git a/src/httpserver.cpp b/src/httpserver.cpp index fa7ad763..6ae7af66 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -2,41 +2,30 @@ #include "httpserver.hpp" #include -#include #include #include #include +#include #include "httplib.h" #include "services/hid.hpp" -u32 stringToKey(const std::string& key_name) { - namespace Keys = HID::Keys; - static std::map keyMap = { - {"A", Keys::A}, - {"B", Keys::B}, - {"Select", Keys::Select}, - {"Start", Keys::Start}, - {"Right", Keys::Right}, - {"Left", Keys::Left}, - {"Up", Keys::Up}, - {"Down", Keys::Down}, - {"R", Keys::R}, - {"L", Keys::L}, - {"X", Keys::X}, - {"Y", Keys::Y}, - {"CirclePadRight", Keys::CirclePadRight}, - {"CirclePadLeft", Keys::CirclePadLeft}, - {"CirclePadUp", Keys::CirclePadUp}, - {"CirclePadDown", Keys::CirclePadDown}, - }; - - if (keyMap.find(key_name) != keyMap.end()) { - return keyMap[key_name]; +HttpServer::HttpServer() : keyMap( + { + {"A", { HID::Keys::A, false } }, + {"B", { HID::Keys::B, false } }, + {"Select", { HID::Keys::Select, false } }, + {"Start", { HID::Keys::Start, false } }, + {"Right", { HID::Keys::Right, false } }, + {"Left", { HID::Keys::Left, false } }, + {"Up", { HID::Keys::Up, false } }, + {"Down", { HID::Keys::Down, false } }, + {"R", { HID::Keys::R, false } }, + {"L", { HID::Keys::L, false } }, + {"X", { HID::Keys::X, false } }, + {"Y", { HID::Keys::Y, false } }, } - - return 0; -} +) {} void HttpServer::startHttpServer() { std::thread http_thread([this]() { @@ -70,8 +59,10 @@ void HttpServer::startHttpServer() { ok = true; if (value == "1") { action = HttpAction::PressKey; + setKeyState(keyStr, true); } else if (value == "0") { action = HttpAction::ReleaseKey; + setKeyState(keyStr, false); } else { // Should not happen but just in case pendingAction = false; @@ -92,6 +83,10 @@ void HttpServer::startHttpServer() { response.set_content("ok", "text/plain"); }); + server.Get("/status", [this](const httplib::Request&, httplib::Response& response) { + response.set_content(status(), "text/plain"); + }); + // TODO: ability to specify host and port printf("Starting HTTP server on port 1234\n"); server.listen("localhost", 1234); @@ -100,4 +95,38 @@ void HttpServer::startHttpServer() { http_thread.detach(); } +std::string HttpServer::status() { + std::stringstream stringStream; + + stringStream << "Panda3DS\n"; + stringStream << "Status: " << (paused ? "Paused" : "Running") << "\n"; + for (auto& [keyStr, value] : keyMap) { + stringStream << keyStr << ": " << value.second << "\n"; + } + + return stringStream.str(); +} + +u32 HttpServer::stringToKey(const std::string& key_name) { + if (keyMap.find(key_name) != keyMap.end()) { + return keyMap[key_name].first; + } + + return 0; +} + +bool HttpServer::getKeyState(const std::string& key_name) { + if (keyMap.find(key_name) != keyMap.end()) { + return keyMap[key_name].second; + } + + return false; +} + +void HttpServer::setKeyState(const std::string& key_name, bool state) { + if (keyMap.find(key_name) != keyMap.end()) { + keyMap[key_name].second = state; + } +} + #endif // PANDA3DS_ENABLE_HTTP_SERVER \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9637e83f..1559565a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,11 +5,15 @@ int main (int argc, char *argv[]) { emu.initGraphicsContext(); - auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "teapot.elf"); - if (!emu.loadROM(romPath)) { - // For some reason just .c_str() doesn't show the proper path - Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); - } + if (argc > 1) { + auto romPath = std::filesystem::current_path() / argv[1]; + if (!emu.loadROM(romPath)) { + // For some reason just .c_str() doesn't show the proper path + Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str()); + } + } else { + printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n"); + } - emu.run(); + emu.run(); } \ No newline at end of file diff --git a/src/stb_image_write.c b/src/stb_image_write.c new file mode 100644 index 00000000..2f540c3c --- /dev/null +++ b/src/stb_image_write.c @@ -0,0 +1,2 @@ +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include \ No newline at end of file