From fdeb45d188c7c46118ec70ab8fe932cd11b43843 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 17 May 2023 01:17:10 +0300 Subject: [PATCH 1/7] [DSP] HLE DSP state & some of the audio pipe --- include/services/dsp.hpp | 13 ++++++++ src/core/services/dsp.cpp | 69 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/include/services/dsp.hpp b/include/services/dsp.hpp index 5fe42a89..774c25de 100644 --- a/include/services/dsp.hpp +++ b/include/services/dsp.hpp @@ -43,6 +43,12 @@ public: } }; +namespace DSPPipeType { + enum : u32 { + Debug = 0, DMA = 1, Audio = 2, Binary = 3 + }; +} + // Circular dependencies! class Kernel; @@ -52,9 +58,14 @@ class DSPService { Kernel& kernel; MAKE_LOG_FUNCTION(log, dspServiceLogger) + enum class DSPState : u32 { + Off, On, Slep + }; + // Number of DSP pipes static constexpr size_t pipeCount = 8; DSPPipe audioPipe; + DSPState dspState; // DSP service event handles using DSPEvent = std::optional; @@ -78,6 +89,8 @@ class DSPService { void invalidateDCache(u32 messagePointer); void loadComponent(u32 messagePointer); void readPipeIfPossible(u32 messagePointer); + void recvData(u32 messagePointer); + void recvDataIsReady(u32 messagePointer); void registerInterruptEvents(u32 messagePointer); void setSemaphore(u32 messagePointer); void setSemaphoreMask(u32 messagePointer); diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index e94fc92d..a8485794 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -4,6 +4,8 @@ namespace DSPCommands { enum : u32 { + RecvData = 0x00010040, + RecvDataIsReady = 0x00020040, SetSemaphore = 0x00070040, ConvertProcessAddressFromDspDram = 0x000C0040, WriteProcessPipe = 0x000D0082, @@ -29,6 +31,7 @@ namespace Result { void DSPService::reset() { audioPipe.reset(); totalEventCount = 0; + dspState = DSPState::Off; semaphoreEvent = std::nullopt; interrupt0 = std::nullopt; @@ -49,6 +52,8 @@ void DSPService::handleSyncRequest(u32 messagePointer) { case DSPCommands::GetSemaphoreEventHandle: getSemaphoreEventHandle(messagePointer); break; case DSPCommands::LoadComponent: loadComponent(messagePointer); break; case DSPCommands::ReadPipeIfPossible: readPipeIfPossible(messagePointer); break; + case DSPCommands::RecvData: recvData(messagePointer); break; + case DSPCommands::RecvDataIsReady: recvDataIsReady(messagePointer); break; case DSPCommands::RegisterInterruptEvents: registerInterruptEvents(messagePointer); break; case DSPCommands::SetSemaphore: setSemaphore(messagePointer); break; case DSPCommands::SetSemaphoreMask: setSemaphoreMask(messagePointer); break; @@ -107,6 +112,29 @@ void DSPService::readPipeIfPossible(u32 messagePointer) { mem.write16(messagePointer + 8, i); // Number of bytes read } +void DSPService::recvData(u32 messagePointer) { + const u32 registerIndex = mem.read32(messagePointer + 4); + log("DSP::RecvData (register = %d)\n", registerIndex); + if (registerIndex != 0) Helpers::panic("Unknown register in DSP::RecvData"); + + // Return 0 if the DSP is running, otherwise 1 + const u16 ret = dspState == DSPState::On ? 0 : 1; + + mem.write32(messagePointer, IPC::responseHeader(0x01, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write16(messagePointer + 8, ret); // Always return that the DSP is on +} + +void DSPService::recvDataIsReady(u32 messagePointer) { + const u32 registerIndex = mem.read32(messagePointer + 4); + log("DSP::RecvDataIsReady (register = %d)\n", registerIndex); + if (registerIndex != 0) Helpers::panic("Unknown register in DSP::RecvDataIsReady"); + + mem.write32(messagePointer, IPC::responseHeader(0x02, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 1); // Always return that the register is ready for now +} + DSPService::DSPEvent& DSPService::getEventRef(u32 type, u32 pipe) { switch (type) { case 0: return interrupt0; @@ -192,8 +220,47 @@ void DSPService::writeProcessPipe(u32 messagePointer) { const u32 channel = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); const u32 buffer = mem.read32(messagePointer + 16); - log("DSP::writeProcessPipe (channel = %d, size = %X, buffer = %08X)\n", channel, size, buffer); + + enum class StateChange : u8 { + Initialize = 0, + Shutdown = 1, + Wakeup = 2, + Sleep = 3, + }; + + switch (channel) { + case DSPPipeType::Audio: { + if (size != 4) { + printf("Invalid size written to DSP Audio Pipe\n"); + break; + } + + // Get new state + const u8 state = mem.read8(buffer); + if (state > 3) { + log("WriteProcessPipe::Audio: Unknown state change type"); + } else { + switch (static_cast(state)) { + case StateChange::Initialize: + // TODO: Other initialization stuff here + dspState = DSPState::On; + break; + + case StateChange::Shutdown: + dspState = DSPState::Off; + break; + + default: Helpers::panic("Unimplemented DSP audio pipe state change %d", state); + } + } + } + + default: + log("DSP: Wrote to unimplemented pipe %d\n", channel); + break; + } + mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } From 433fcc9b69091ef7831185b696e6081bcb5ad961 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 17 May 2023 01:33:39 +0300 Subject: [PATCH 2/7] [DSP] Add UnloadComponent --- include/services/dsp.hpp | 1 + src/core/services/dsp.cpp | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/services/dsp.hpp b/include/services/dsp.hpp index 774c25de..b9bc7d54 100644 --- a/include/services/dsp.hpp +++ b/include/services/dsp.hpp @@ -94,6 +94,7 @@ class DSPService { void registerInterruptEvents(u32 messagePointer); void setSemaphore(u32 messagePointer); void setSemaphoreMask(u32 messagePointer); + void unloadComponent(u32 messagePointer); void writeProcessPipe(u32 messagePointer); public: diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index a8485794..179b899c 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -11,6 +11,7 @@ namespace DSPCommands { WriteProcessPipe = 0x000D0082, ReadPipeIfPossible = 0x001000C0, LoadComponent = 0x001100C2, + UnloadComponent = 0x00120000, FlushDataCache = 0x00130082, InvalidateDataCache = 0x00140082, RegisterInterruptEvents = 0x00150082, @@ -52,11 +53,12 @@ void DSPService::handleSyncRequest(u32 messagePointer) { case DSPCommands::GetSemaphoreEventHandle: getSemaphoreEventHandle(messagePointer); break; case DSPCommands::LoadComponent: loadComponent(messagePointer); break; case DSPCommands::ReadPipeIfPossible: readPipeIfPossible(messagePointer); break; - case DSPCommands::RecvData: recvData(messagePointer); break; - case DSPCommands::RecvDataIsReady: recvDataIsReady(messagePointer); break; + case DSPCommands::RecvData: [[likely]] recvData(messagePointer); break; + case DSPCommands::RecvDataIsReady: [[likely]] recvDataIsReady(messagePointer); break; case DSPCommands::RegisterInterruptEvents: registerInterruptEvents(messagePointer); break; case DSPCommands::SetSemaphore: setSemaphore(messagePointer); break; case DSPCommands::SetSemaphoreMask: setSemaphoreMask(messagePointer); break; + case DSPCommands::UnloadComponent: unloadComponent(messagePointer); break; case DSPCommands::WriteProcessPipe: [[likely]] writeProcessPipe(messagePointer); break; default: Helpers::panic("DSP service requested. Command: %08X\n", command); } @@ -85,6 +87,12 @@ void DSPService::loadComponent(u32 messagePointer) { mem.write32(messagePointer + 16, mem.read32(messagePointer + 20)); // Component buffer } +void DSPService::unloadComponent(u32 messagePointer) { + log("DSP::UnloadComponent\n"); + mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void DSPService::readPipeIfPossible(u32 messagePointer) { u32 channel = mem.read32(messagePointer + 4); u32 peer = mem.read32(messagePointer + 8); @@ -158,7 +166,11 @@ void DSPService::registerInterruptEvents(u32 messagePointer) { // The event handle being 0 means we're removing an event if (eventHandle == 0) { - Helpers::panic("DSP::DSP::RegisterinterruptEvents Trying to remove a registered interrupt"); + DSPEvent& e = getEventRef(interrupt, channel); // Get event + if (e.has_value()) { // Remove if it exists + totalEventCount--; + e = std::nullopt; + } } else { const KernelObject* object = kernel.getObject(eventHandle, KernelObjectType::Event); if (!object) { From 62936ddfac8627f5909ae7bd2abcdd6b0015a567 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 17 May 2023 02:34:48 +0300 Subject: [PATCH 3/7] [DSP] Annotate pipe 2 addresses --- include/services/dsp.hpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/include/services/dsp.hpp b/include/services/dsp.hpp index b9bc7d54..a63c4e3f 100644 --- a/include/services/dsp.hpp +++ b/include/services/dsp.hpp @@ -8,23 +8,25 @@ // Stub for DSP audio pipe class DSPPipe { // Hardcoded responses for now + // These are DSP DRAM offsets for various variables + // https://www.3dbrew.org/wiki/DSP_Memory_Region static constexpr std::array pipeData = { - 0x000F, //Number of responses - 0xBFFF, - 0x9E8E, - 0x8680, - 0xA78E, - 0x9430, - 0x8400, - 0x8540, - 0x948E, - 0x8710, - 0x8410, - 0xA90E, - 0xAA0E, - 0xAACE, - 0xAC4E, - 0xAC58 + 0x000F, // Number of responses + 0xBFFF, // Frame counter + 0x9E92, // Source configs + 0x8680, // Source statuses + 0xA792, // ADPCM coefficients + 0x9430, // DSP configs + 0x8400, // DSP status + 0x8540, // Final samples + 0x9492, // Intermediate mix samples + 0x8710, // Compressor + 0x8410, // Debug + 0xA912, // ?? + 0xAA12, // ?? + 0xAAD2, // ?? + 0xAC52, // Surround sound biquad filter 1 + 0xAC5C // Surround sound biquad filter 2 }; uint index = 0; From e5646a185ce147a8e4ba4390444a15b24d9b323f Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 18 May 2023 03:36:19 +0300 Subject: [PATCH 4/7] [DSP] Reset audio pipe properly --- src/core/services/dsp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index 179b899c..8b11c6bf 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -257,6 +257,7 @@ void DSPService::writeProcessPipe(u32 messagePointer) { case StateChange::Initialize: // TODO: Other initialization stuff here dspState = DSPState::On; + audioPipe.reset(); break; case StateChange::Shutdown: From b37256e9eab76e5b27104ffd6a3dfd5085107760 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Thu, 18 May 2023 03:44:29 +0300 Subject: [PATCH 5/7] [DSP] Add missing break --- src/core/services/dsp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index 8b11c6bf..3ba962ad 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -267,6 +267,7 @@ void DSPService::writeProcessPipe(u32 messagePointer) { default: Helpers::panic("Unimplemented DSP audio pipe state change %d", state); } } + break; } default: From e3fb364d9554d1fafd401612a11ab93b747ed204 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 27 May 2023 00:32:43 +0300 Subject: [PATCH 6/7] Add DLP::SRVR service --- CMakeLists.txt | 4 ++-- include/kernel/handles.hpp | 6 +++-- include/logger.hpp | 1 + include/services/dlp_srvr.hpp | 21 ++++++++++++++++++ include/services/service_manager.hpp | 2 ++ src/core/services/dlp_srvr.cpp | 32 +++++++++++++++++++++++++++ src/core/services/service_manager.cpp | 7 ++++-- 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 include/services/dlp_srvr.hpp create mode 100644 src/core/services/dlp_srvr.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b78871c3..6fc6fe2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services src/core/services/ac.cpp src/core/services/am.cpp src/core/services/boss.cpp src/core/services/frd.cpp src/core/services/nim.cpp src/core/services/shared_font.cpp src/core/services/y2r.cpp src/core/services/cam.cpp src/core/services/ldr_ro.cpp - src/core/services/act.cpp src/core/services/nfc.cpp + src/core/services/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp ) set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp src/core/PICA/shader_interpreter.cpp @@ -112,7 +112,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/fs/archive_ext_save_data.hpp include/services/shared_font.hpp include/fs/archive_ncch.hpp include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp - include/system_models.hpp + include/system_models.hpp include/services/dlp_srvr.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/kernel/handles.hpp b/include/kernel/handles.hpp index 9b13460a..589b64f5 100644 --- a/include/kernel/handles.hpp +++ b/include/kernel/handles.hpp @@ -18,11 +18,12 @@ namespace KernelHandles { CAM, // Camera service CECD, // More Streetpass stuff? CFG, // CFG service (Console & region info) + DLP_SRVR, // Download Play: Server. Used for network play. + DSP, // DSP service (Used for audio decoding and output) HID, // HID service (Handles everything input-related including gyro) FRD, // Friend service (Miiverse friend service) FS, // Filesystem service GPU, // GPU service - DSP, // DSP service (Used for audio decoding and output) LCD, // LCD service (Used for configuring the displays) LDR_RO, // Loader service. Used for loading CROs. MIC, // MIC service (Controls the microphone) @@ -64,10 +65,11 @@ namespace KernelHandles { case CAM: return "CAM"; case CECD: return "CECD"; case CFG: return "CFG"; + case DSP: return "DSP"; + case DLP_SRVR: return "DLP::SRVR"; case HID: return "HID"; case FRD: return "FRD"; case FS: return "FS"; - case DSP: return "DSP"; case GPU: return "GSP::GPU"; case LCD: return "GSP::LCD"; case LDR_RO: return "LDR:RO"; diff --git a/include/logger.hpp b/include/logger.hpp index 5ffd707b..eec3b400 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -38,6 +38,7 @@ namespace Log { static Logger cecdLogger; static Logger cfgLogger; static Logger dspServiceLogger; + static Logger dlpSrvrLogger; static Logger frdLogger; static Logger fsLogger; static Logger hidLogger; diff --git a/include/services/dlp_srvr.hpp b/include/services/dlp_srvr.hpp new file mode 100644 index 00000000..c4be004f --- /dev/null +++ b/include/services/dlp_srvr.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "helpers.hpp" +#include "kernel_types.hpp" +#include "logger.hpp" +#include "memory.hpp" + +// Please forgive me for how everything in this file is named +// "dlp:SRVR" is not a nice name to work with +class DlpSrvrService { + Handle handle = KernelHandles::DLP_SRVR; + Memory& mem; + MAKE_LOG_FUNCTION(log, dlpSrvrLogger) + + // Service commands + void isChild(u32 messagePointer); + +public: + DlpSrvrService(Memory& mem) : mem(mem) {} + void reset(); + void handleSyncRequest(u32 messagePointer); +}; \ No newline at end of file diff --git a/include/services/service_manager.hpp b/include/services/service_manager.hpp index 25e4d8f0..2a50ffaf 100644 --- a/include/services/service_manager.hpp +++ b/include/services/service_manager.hpp @@ -12,6 +12,7 @@ #include "services/cam.hpp" #include "services/cecd.hpp" #include "services/cfg.hpp" +#include "services/dlp_srvr.hpp" #include "services/dsp.hpp" #include "services/hid.hpp" #include "services/frd.hpp" @@ -46,6 +47,7 @@ class ServiceManager { CAMService cam; CECDService cecd; CFGService cfg; + DlpSrvrService dlp_srvr; DSPService dsp; HIDService hid; FRDService frd; diff --git a/src/core/services/dlp_srvr.cpp b/src/core/services/dlp_srvr.cpp new file mode 100644 index 00000000..3e465e21 --- /dev/null +++ b/src/core/services/dlp_srvr.cpp @@ -0,0 +1,32 @@ +#include "services/dlp_srvr.hpp" +#include "ipc.hpp" + +namespace DlpSrvrCommands { + enum : u32 { + IsChild = 0x000E0040 + }; +} + +namespace Result { + enum : u32 { + Success = 0, + }; +} + +void DlpSrvrService::reset() {} + +void DlpSrvrService::handleSyncRequest(u32 messagePointer) { + const u32 command = mem.read32(messagePointer); + switch (command) { + case DlpSrvrCommands::IsChild: isChild(messagePointer); break; + default: Helpers::panic("DLP::SRVR service requested. Command: %08X\n", command); + } +} + +void DlpSrvrService::isChild(u32 messagePointer) { + log("DLP::SRVR: IsChild\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x0E, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 0); // We are responsible adults +} \ No newline at end of file diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index 8b14e5e1..27b39af9 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -5,7 +5,7 @@ ServiceManager::ServiceManager(std::array& regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel) : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem), - cecd(mem, kernel), cfg(mem), dsp(mem, kernel), hid(mem, kernel), frd(mem), fs(mem, kernel), + cecd(mem, kernel), cfg(mem), dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), frd(mem), fs(mem, kernel), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem), ptm(mem), y2r(mem, kernel) {} @@ -21,6 +21,7 @@ void ServiceManager::reset() { cam.reset(); cecd.reset(); cfg.reset(); + dlp_srvr.reset(); dsp.reset(); hid.reset(); frd.reset(); @@ -97,6 +98,7 @@ static std::map serviceMap = { { "cam:u", KernelHandles::CAM }, { "cecd:u", KernelHandles::CECD }, { "cfg:u", KernelHandles::CFG }, + { "dlp:SRVR", KernelHandles::DLP_SRVR }, { "dsp::DSP", KernelHandles::DSP }, { "hid:USER", KernelHandles::HID }, { "frd:u", KernelHandles::FRD }, @@ -170,6 +172,7 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) { case KernelHandles::GPU: [[likely]] gsp_gpu.handleSyncRequest(messagePointer); break; case KernelHandles::FS: [[likely]] fs.handleSyncRequest(messagePointer); break; case KernelHandles::APT: [[likely]] apt.handleSyncRequest(messagePointer); break; + case KernelHandles::DSP: [[likely]] dsp.handleSyncRequest(messagePointer); break; case KernelHandles::AC: ac.handleSyncRequest(messagePointer); break; case KernelHandles::ACT: act.handleSyncRequest(messagePointer); break; @@ -178,7 +181,7 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) { case KernelHandles::CAM: cam.handleSyncRequest(messagePointer); break; case KernelHandles::CECD: cecd.handleSyncRequest(messagePointer); break; case KernelHandles::CFG: cfg.handleSyncRequest(messagePointer); break; - case KernelHandles::DSP: dsp.handleSyncRequest(messagePointer); break; + case KernelHandles::DLP_SRVR: dlp_srvr.handleSyncRequest(messagePointer); break; case KernelHandles::HID: hid.handleSyncRequest(messagePointer); break; case KernelHandles::FRD: frd.handleSyncRequest(messagePointer); break; case KernelHandles::LCD: gsp_lcd.handleSyncRequest(messagePointer); break; From e597cc6835962b5d0e9625ee01a59c1522f401be Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Sat, 3 Jun 2023 01:23:26 +0300 Subject: [PATCH 7/7] Create readme.md --- readme.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..34171431 --- /dev/null +++ b/readme.md @@ -0,0 +1,51 @@ +# Panda3DS + +Panda3DS is an HLE, red-panda-themed Nintendo 3DS emulator written in C++ which started out as a fun project out of curiosity, but evolved into something that can sort of play games! + +# Compatibility +Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, performance leaves a bit to be desired mainly thanks to lack of shader acceleration, and most QoL features (including input, or a GUI!) are missing. + +In fact, the screenshots in the repo were created after I hooked the input state to rand() locally. + +In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself. + +# Why? +The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as: + +- Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future. + +- Debugging, reverse engineering and modding tools. While contributing to [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) and collaborating with the other developers, I had the chance to find out how useful tools like these can be. They can serve as indispensable tools for the homebrew devs, modders, reverse engineers, as well as emulator developers themselves. Some tools can even become fun toys the casual user can mess around with. As such, I think they can really improve the experience in a project like this. Of course, I'd like to thank @nicolasnoble and the entire Redux team for helping me learn the value of these tools, as well as making me improve as a programmer. + +- Trying out various other unique features, such as different graphics or audio enhancements, or supporting other niche things such as amiibo. + +- Fun. Writing code is fun and I strongly encourage anyone to do it. + +Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility + +# How to build +Panda3DS compiles on Windows, Linux and MacOS (if you use a compiler other than AppleClang), without needing to download any system dependencies. + +All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project. + +```sh +git clone https://github.com/wheremyfoodat/Panda3DS --recursive + +cd Panda3DS && mkdir build && cd build +cmake . -DCMAKE_BUILD_TYPE=Release # Set up compilers etc here if you'd like + + +``` + +# How to use +Simply drag and drop a ROM to the executable if supported, or invoke the executable from the command line with the path to the ROM as the first argument. + +# Acknowledgements +- [3DBrew](https://www.3dbrew.org/wiki/Main_Page), a wiki full of 3DS information and the main source of documentation used. +- [GBATek](https://www.problemkaputt.de/gbatek.htm#3dsreference), a GBA, DS and 3DS reference which provided insights on some pieces of hardware as well as neatly documenting things like certain file formats used in games. +- [Libctru](https://github.com/devkitPro/libctru), the most well-known 3DS homebrew SDK. Used for developing test ROMs, as well as a source of documentation thanks to its doxygen wiki. + +- [Citra](https://github.com/citra-emu/citra), an HLE 3DS emulator. Very useful as a reference, with some code snippets inspired or adapted from it. +- [3dmoo](https://github.com/plutooo/3dmoo), an HLE 3DS emulator which helped similarly to Citra +- [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an LLE 3DS emulator which both served as an inspiration, as well as a nice source of documentation for some PICA200-related things + +Nintendo 3DS is a registered trademark of Nintendo Co., Ltd. \ No newline at end of file