From 53873c75cc2226d579cd6828edcd93f206866416 Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Mon, 10 Jul 2023 20:48:13 +0200 Subject: [PATCH 1/5] Don't force users to load rom from terminal or by dragging and dropping onto executable file. Instead, open a blank window and use SDL's drag&drop feature --- include/emulator.hpp | 2 +- src/emulator.cpp | 296 +++++++++++++++++++++++-------------------- src/main.cpp | 14 +- 3 files changed, 169 insertions(+), 143 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 3985c613..fca2240f 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -47,7 +47,7 @@ class Emulator { static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; - bool running = true; + bool running = true, romLoaded = false; #ifdef PANDA3DS_ENABLE_HTTP_SERVER HttpServer httpServer; diff --git a/src/emulator.cpp b/src/emulator.cpp index 1e30e073..3d3678df 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -90,18 +90,19 @@ void Emulator::run() { httpServer.startHttpServer(); #endif while (running) { + if(romLoaded) { #ifdef PANDA3DS_ENABLE_HTTP_SERVER - pollHttpServer(); + pollHttpServer(); #endif - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics + runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics - ServiceManager& srv = kernel.getServiceManager(); - - // Send VBlank interrupts - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + ServiceManager& srv = kernel.getServiceManager(); + // Send VBlank interrupts + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + } SDL_Event event; while (SDL_PollEvent(&event)) { namespace Keys = HID::Keys; @@ -113,104 +114,111 @@ void Emulator::run() { return; case SDL_KEYDOWN: - switch (event.key.keysym.sym) { - case SDLK_l: srv.pressKey(Keys::A); break; - case SDLK_k: srv.pressKey(Keys::B); break; - case SDLK_o: srv.pressKey(Keys::X); break; - case SDLK_i: srv.pressKey(Keys::Y); break; + if(romLoaded) { + switch (event.key.keysym.sym) { + case SDLK_l: srv.pressKey(Keys::A); break; + case SDLK_k: srv.pressKey(Keys::B); break; + case SDLK_o: srv.pressKey(Keys::X); break; + case SDLK_i: srv.pressKey(Keys::Y); break; - case SDLK_q: srv.pressKey(Keys::L); break; - case SDLK_p: srv.pressKey(Keys::R); break; + case SDLK_q: srv.pressKey(Keys::L); break; + case SDLK_p: srv.pressKey(Keys::R); break; - case SDLK_RIGHT: srv.pressKey(Keys::Right); break; - case SDLK_LEFT: srv.pressKey(Keys::Left); break; - case SDLK_UP: srv.pressKey(Keys::Up); break; - case SDLK_DOWN: srv.pressKey(Keys::Down); break; + case SDLK_RIGHT: srv.pressKey(Keys::Right); break; + case SDLK_LEFT: srv.pressKey(Keys::Left); break; + case SDLK_UP: srv.pressKey(Keys::Up); break; + case SDLK_DOWN: srv.pressKey(Keys::Down); break; - case SDLK_w: - srv.setCirclepadY(0x9C); - keyboardAnalogY = true; - break; + case SDLK_w: + srv.setCirclepadY(0x9C); + keyboardAnalogY = true; + break; - case SDLK_a: - srv.setCirclepadX(-0x9C); - keyboardAnalogX = true; - break; + case SDLK_a: + srv.setCirclepadX(-0x9C); + keyboardAnalogX = true; + break; - case SDLK_s: - srv.setCirclepadY(-0x9C); - keyboardAnalogY = true; - break; + case SDLK_s: + srv.setCirclepadY(-0x9C); + keyboardAnalogY = true; + break; - case SDLK_d: - srv.setCirclepadX(0x9C); - keyboardAnalogX = true; - break; + case SDLK_d: + srv.setCirclepadX(0x9C); + keyboardAnalogX = true; + break; - case SDLK_RETURN: srv.pressKey(Keys::Start); break; - case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break; + case SDLK_RETURN: srv.pressKey(Keys::Start); break; + case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break; + } } break; case SDL_KEYUP: - switch (event.key.keysym.sym) { - case SDLK_l: srv.releaseKey(Keys::A); break; - case SDLK_k: srv.releaseKey(Keys::B); break; - case SDLK_o: srv.releaseKey(Keys::X); break; - case SDLK_i: srv.releaseKey(Keys::Y); break; + if(romLoaded) { + switch (event.key.keysym.sym) { + case SDLK_l: srv.releaseKey(Keys::A); break; + case SDLK_k: srv.releaseKey(Keys::B); break; + case SDLK_o: srv.releaseKey(Keys::X); break; + case SDLK_i: srv.releaseKey(Keys::Y); break; - case SDLK_q: srv.releaseKey(Keys::L); break; - case SDLK_p: srv.releaseKey(Keys::R); break; + case SDLK_q: srv.releaseKey(Keys::L); break; + case SDLK_p: srv.releaseKey(Keys::R); break; - case SDLK_RIGHT: srv.releaseKey(Keys::Right); break; - case SDLK_LEFT: srv.releaseKey(Keys::Left); break; - case SDLK_UP: srv.releaseKey(Keys::Up); break; - case SDLK_DOWN: srv.releaseKey(Keys::Down); break; + case SDLK_RIGHT: srv.releaseKey(Keys::Right); break; + case SDLK_LEFT: srv.releaseKey(Keys::Left); break; + case SDLK_UP: srv.releaseKey(Keys::Up); break; + case SDLK_DOWN: srv.releaseKey(Keys::Down); break; - // Err this is probably not ideal - case SDLK_w: - case SDLK_s: - srv.setCirclepadY(0); - keyboardAnalogY = false; - break; + // Err this is probably not ideal + case SDLK_w: + case SDLK_s: + srv.setCirclepadY(0); + keyboardAnalogY = false; + break; - case SDLK_a: - case SDLK_d: - srv.setCirclepadX(0); - keyboardAnalogX = false; - break; + case SDLK_a: + case SDLK_d: + srv.setCirclepadX(0); + keyboardAnalogX = false; + break; - case SDLK_RETURN: srv.releaseKey(Keys::Start); break; - case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break; - } - break; - - case SDL_MOUSEBUTTONDOWN: { - if (event.button.button == SDL_BUTTON_LEFT) { - const s32 x = event.button.x; - const s32 y = event.button.y; - - // Check if touch falls in the touch screen area - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - srv.setTouchScreenPress(x_converted, y_converted); - } else { - srv.releaseTouchScreen(); + case SDLK_RETURN: srv.releaseKey(Keys::Start); break; + case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break; + } + } + break; + + case SDL_MOUSEBUTTONDOWN: + if(romLoaded) { + if (event.button.button == SDL_BUTTON_LEFT) { + const s32 x = event.button.x; + const s32 y = event.button.y; + + // Check if touch falls in the touch screen area + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; + + srv.setTouchScreenPress(x_converted, y_converted); + } else { + srv.releaseTouchScreen(); + } + } else if (event.button.button == SDL_BUTTON_RIGHT) { + holdingRightClick = true; } - } else if (event.button.button == SDL_BUTTON_RIGHT) { - holdingRightClick = true; } break; - } case SDL_MOUSEBUTTONUP: - if (event.button.button == SDL_BUTTON_LEFT) { - srv.releaseTouchScreen(); - } else if (event.button.button == SDL_BUTTON_RIGHT) { - holdingRightClick = false; + if(romLoaded) { + if (event.button.button == SDL_BUTTON_LEFT) { + srv.releaseTouchScreen(); + } else if (event.button.button == SDL_BUTTON_RIGHT) { + holdingRightClick = false; + } } break; @@ -230,76 +238,90 @@ void Emulator::run() { break; case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERBUTTONDOWN: { - u32 key = 0; + case SDL_CONTROLLERBUTTONDOWN: + if(romLoaded) { + u32 key = 0; - switch (event.cbutton.button) { - case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; - case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; - case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; - case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; - case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; - case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; - case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; - } + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; + case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; + case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; + case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; + case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; + case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; + } - if (key != 0) { - if (event.cbutton.state == SDL_PRESSED) { - srv.pressKey(key); - } else { - srv.releaseKey(key); + if (key != 0) { + if (event.cbutton.state == SDL_PRESSED) { + srv.pressKey(key); + } else { + 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; + case SDL_MOUSEMOTION: + if(romLoaded) { + // 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; - // Relative motion since last mouse motion event - const s32 motionX = event.motion.xrel; - const s32 motionY = event.motion.yrel; + // Relative motion since last mouse motion event + const s32 motionX = event.motion.xrel; + const s32 motionY = event.motion.yrel; - // The gyroscope involves lots of weird math I don't want to bother with atm - // So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion - const s32 roll = motionX > 0 ? 0x7f : -0x7f; - const s32 pitch = motionY > 0 ? 0x7f : -0x7f; - srv.setRoll(roll); - srv.setPitch(pitch); + // The gyroscope involves lots of weird math I don't want to bother with atm + // So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion + const s32 roll = motionX > 0 ? 0x7f : -0x7f; + const s32 pitch = motionY > 0 ? 0x7f : -0x7f; + srv.setRoll(roll); + srv.setPitch(pitch); + } break; + + case SDL_DROPFILE: { + char *droppedDir = event.drop.file; + if(droppedDir) { + loadROM(droppedDir); + 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; + if(romLoaded) { + 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); } } @@ -346,6 +368,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) { romType = ROMType::None; } + romLoaded = success; return success; } @@ -390,6 +413,7 @@ 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; } diff --git a/src/main.cpp b/src/main.cpp index 9637e83f..e0f803ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,11 +5,13 @@ 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()); + } + } - emu.run(); + emu.run(); } \ No newline at end of file From 340c18b87bfa681efee9417f3ed1782970e48064 Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Mon, 10 Jul 2023 20:53:51 +0200 Subject: [PATCH 2/5] Oops small mistake --- src/emulator.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 3d3678df..67510fea 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -90,6 +90,7 @@ void Emulator::run() { httpServer.startHttpServer(); #endif while (running) { + ServiceManager& srv = kernel.getServiceManager(); if(romLoaded) { #ifdef PANDA3DS_ENABLE_HTTP_SERVER pollHttpServer(); @@ -97,8 +98,6 @@ void Emulator::run() { runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics - ServiceManager& srv = kernel.getServiceManager(); - // Send VBlank interrupts srv.sendGPUInterrupt(GPUInterrupt::VBlank0); srv.sendGPUInterrupt(GPUInterrupt::VBlank1); From a7a908658ab2fc90478cf2621b28ed610fc68d74 Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Mon, 10 Jul 2023 21:20:02 +0200 Subject: [PATCH 3/5] Should use SDL_free for drag and drop char* --- src/emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index 67510fea..4ce93ef1 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -289,7 +289,7 @@ void Emulator::run() { char *droppedDir = event.drop.file; if(droppedDir) { loadROM(droppedDir); - free(droppedDir); + SDL_free(droppedDir); } } break; From 7ad47875b6a8bcba1fa512bfcd9c44e106516e12 Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Mon, 10 Jul 2023 21:27:18 +0200 Subject: [PATCH 4/5] Should reset the state every time a rom is loaded so the user can keep dragging and dropping roms and it works --- include/emulator.hpp | 1 + src/emulator.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index fca2240f..e90a00a5 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -64,6 +64,7 @@ class Emulator { Emulator(); ~Emulator(); + void stop(); void step(); void render(); void reset(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 4ce93ef1..7bd0a038 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -59,7 +59,7 @@ Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory Emulator::~Emulator() { config.save(std::filesystem::current_path() / "config.toml"); } -void Emulator::reset() { +void Emulator::stop() { cpu.reset(); gpu.reset(); memory.reset(); @@ -69,7 +69,9 @@ void Emulator::reset() { // Reloading r13 and r15 needs to happen after everything has been reset // Otherwise resetting the kernel or cpu might nuke them cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP +} +void Emulator::reset() { // 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()) { bool success = loadROM(romPath.value()); @@ -328,6 +330,7 @@ void Emulator::run() { void Emulator::runFrame() { cpu.runFrame(); } bool Emulator::loadROM(const std::filesystem::path& path) { + stop(); // 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 From 49101037b8a8bccf89be1af57e312bd51dad0b4b Mon Sep 17 00:00:00 2001 From: SimoneN64 Date: Mon, 10 Jul 2023 23:04:07 +0200 Subject: [PATCH 5/5] Should reset this flag in the GSP (needs better name) --- include/services/gsp_gpu.hpp | 1 + src/core/services/gsp_gpu.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index f687532e..214a001e 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -22,6 +22,7 @@ enum class GPUInterrupt : u8 { class Kernel; class GPUService { + bool registerInterruptRelayQueueBeenHere = false; Handle handle = KernelHandles::GPU; Memory& mem; GPU& gpu; diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index f27688a2..e3dea8f6 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -34,6 +34,7 @@ void GPUService::reset() { privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle interruptEvent = std::nullopt; sharedMem = nullptr; + registerInterruptRelayQueueBeenHere = false; } void GPUService::handleSyncRequest(u32 messagePointer) { @@ -77,9 +78,8 @@ 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 (registerInterruptRelayQueueBeenHere) Helpers::panic("RegisterInterruptRelayQueue called a second time. Need to implement GSP threads properly"); + registerInterruptRelayQueueBeenHere = true; const u32 flags = mem.read32(messagePointer + 4); const u32 eventHandle = mem.read32(messagePointer + 12);