diff --git a/CMakeLists.txt b/CMakeLists.txt index cc32316f..d8fc54a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -415,7 +415,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp - include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp + include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp ) if(IOS) @@ -732,10 +732,12 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp + src/panda_qt/thread_debugger.cpp ) set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp + include/panda_qt/thread_debugger.hpp ) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) diff --git a/include/emulator.hpp b/include/emulator.hpp index 326eb232..6b7f97fc 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -127,6 +127,7 @@ class Emulator { Scheduler& getScheduler() { return scheduler; } Memory& getMemory() { return memory; } AudioDeviceInterface& getAudioDevice() { return audioDevice; } + Kernel& getKernel() { return kernel; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 3f20b5e1..3d801c7a 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -15,6 +15,7 @@ #include "services/service_manager.hpp" class CPU; +class LuaManager; struct Scheduler; class Kernel { @@ -47,12 +48,12 @@ class Kernel { Handle currentProcess; Handle mainThread; int currentThreadIndex; - Handle srvHandle; // Handle for the special service manager port "srv:" - Handle errorPortHandle; // Handle for the err:f port used for displaying errors + Handle srvHandle; // Handle for the special service manager port "srv:" + Handle errorPortHandle; // Handle for the err:f port used for displaying errors u32 arbiterCount; - u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32) - u32 aliveThreadCount; // How many of these threads are actually alive? + u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32) + u32 aliveThreadCount; // How many of these threads are actually alive? ServiceManager serviceManager; // Top 8 bits are the major version, bottom 8 are the minor version @@ -65,10 +66,10 @@ class Kernel { Handle makeProcess(u32 id); Handle makePort(const char* name); Handle makeSession(Handle port); - Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant); + Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status = ThreadStatus::Dormant); Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission); -public: + public: // Needs to be public to be accessible to the APT/HID services Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None); // Needs to be public to be accessible to the APT/DSP services @@ -198,8 +199,8 @@ public: void closeDirectory(u32 messagePointer, Handle directory); void readDirectory(u32 messagePointer, Handle directory); -public: - Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config); + public: + Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua); void initializeFS() { return serviceManager.initializeFS(); } void setVersion(u8 major, u8 minor); void serviceSVC(u32 svc); @@ -224,9 +225,7 @@ public: return handleCounter++; } - std::vector& getObjects() { - return objects; - } + std::vector& getObjects() { return objects; } // Get pointer to the object with the specified handle KernelObject* getObject(Handle handle) { @@ -254,4 +253,14 @@ public: void clearInstructionCache(); void clearInstructionCacheRange(u32 start, u32 size); u32 getSharedFontVaddr(); + + // For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display + std::vector getMainProcessThreads(); + + // For debuggers: Sets the entrypoint and initial SP for the main thread (thread 0) so that the debugger can display them + void setMainThreadEntrypointAndSP(u32 entrypoint, u32 initialSP) { + auto& t = threads[0]; + t.entrypoint = entrypoint; + t.initialSP = initialSP; + } }; \ No newline at end of file diff --git a/include/lua_manager.hpp b/include/lua_manager.hpp index 46fd553a..7a79fa60 100644 --- a/include/lua_manager.hpp +++ b/include/lua_manager.hpp @@ -47,6 +47,8 @@ class LuaManager { signalEventInternal(e); } } + + bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer); }; #else // Lua not enabled, Lua manager does nothing @@ -60,5 +62,6 @@ class LuaManager { void loadString(const std::string& code) {} void reset() {} void signalEvent(LuaEvent e) {} + bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer) { return false; } }; #endif diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 70e3ef75..715f73fb 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -21,6 +21,7 @@ #include "panda_qt/screen.hpp" #include "panda_qt/shader_editor.hpp" #include "panda_qt/text_editor.hpp" +#include "panda_qt/thread_debugger.hpp" #include "services/hid.hpp" struct CheatMessage { @@ -109,6 +110,7 @@ class MainWindow : public QMainWindow { TextEditorWindow* luaEditor; PatchWindow* patchWindow; ShaderEditorWindow* shaderEditor; + ThreadDebugger* threadDebugger; // We use SDL's game controller API since it's the sanest API that supports as many controllers as possible SDL_GameController* gameController = nullptr; @@ -157,4 +159,7 @@ class MainWindow : public QMainWindow { void handleScreenResize(u32 width, u32 height); void handleTouchscreenPress(QMouseEvent* event); + + signals: + void emulatorPaused(); }; diff --git a/include/panda_qt/thread_debugger.hpp b/include/panda_qt/thread_debugger.hpp new file mode 100644 index 00000000..76fe3509 --- /dev/null +++ b/include/panda_qt/thread_debugger.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "emulator.hpp" + +class ThreadDebugger : public QWidget { + Q_OBJECT + Emulator* emu; + + QVBoxLayout* mainLayout; + QTableWidget* threadTable; + + public: + ThreadDebugger(Emulator* emu, QWidget* parent = nullptr); + void update(); + + private: + void setListItem(int row, int column, const QString& str); + void setListItem(int row, int column, const std::string& str); +}; \ No newline at end of file diff --git a/include/services/service_intercept.hpp b/include/services/service_intercept.hpp new file mode 100644 index 00000000..b8e19c88 --- /dev/null +++ b/include/services/service_intercept.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include + +#include "helpers.hpp" + +// We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests +// For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP" +// and the "function" field would be 0x000E00C0 +struct InterceptedService { + std::string serviceName; // Name of the service whose function + u32 function; // Header of the function to intercept + + InterceptedService(const std::string& name, u32 header) : serviceName(name), function(header) {} + bool operator==(const InterceptedService& other) const { return serviceName == other.serviceName && function == other.function; } +}; + +// Define hashing function for InterceptedService to use it with unordered_map +namespace std { + template <> + struct hash { + usize operator()(const InterceptedService& s) const noexcept { + const usize hash1 = std::hash{}(s.serviceName); + const usize hash2 = std::hash{}(s.function); + return hash1 ^ (hash2 << 1); + } + }; +} // namespace std diff --git a/include/services/service_manager.hpp b/include/services/service_manager.hpp index 14f3f397..315e1163 100644 --- a/include/services/service_manager.hpp +++ b/include/services/service_manager.hpp @@ -2,9 +2,12 @@ #include #include #include +#include +#include #include "kernel_types.hpp" #include "logger.hpp" +#include "lua_manager.hpp" #include "memory.hpp" #include "services/ac.hpp" #include "services/act.hpp" @@ -34,6 +37,7 @@ #include "services/ns.hpp" #include "services/nwm_uds.hpp" #include "services/ptm.hpp" +#include "services/service_intercept.hpp" #include "services/soc.hpp" #include "services/ssl.hpp" #include "services/y2r.hpp" @@ -86,6 +90,19 @@ class ServiceManager { MCU::HWCService mcu_hwc; + // We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests + // For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP" + // and the "function" field would be 0x000E00C0 + LuaManager& lua; + std::unordered_set interceptedServices = {}; + // Calling std::unordered_set::size() compiles to a fairly non-trivial function call on Clang, so we store this + // separately and check it on service calls, for performance reasons + bool haveServiceIntercepts = false; + + // Checks for whether a service call is intercepted by Lua and handles it. Returns true if Lua told us not to handle the function, + // or false if we should handle it as normal + bool checkForIntercept(u32 messagePointer, Handle handle); + // "srv:" commands void enableNotification(u32 messagePointer); void getServiceHandle(u32 messagePointer); @@ -96,7 +113,7 @@ class ServiceManager { void unsubscribe(u32 messagePointer); public: - ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config); + ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config, LuaManager& lua); void reset(); void initializeFS() { fs.initializeFilesystem(); } void handleSyncRequest(u32 messagePointer); @@ -116,4 +133,14 @@ class ServiceManager { DSPService& getDSP() { return dsp; } Y2RService& getY2R() { return y2r; } IRUserService& getIRUser() { return ir_user; } + + void addServiceIntercept(const std::string& service, u32 function) { + interceptedServices.insert(InterceptedService(service, function)); + haveServiceIntercepts = true; + } + + void clearServiceIntercepts() { + interceptedServices.clear(); + haveServiceIntercepts = false; + } }; diff --git a/src/core/kernel/idle_thread.cpp b/src/core/kernel/idle_thread.cpp index d666968b..d6f79360 100644 --- a/src/core/kernel/idle_thread.cpp +++ b/src/core/kernel/idle_thread.cpp @@ -1,4 +1,5 @@ #include + #include "arm_defs.hpp" #include "kernel.hpp" @@ -35,13 +36,14 @@ void Kernel::setupIdleThread() { if (!vaddr.has_value() || vaddr.value() != codeAddress) { Helpers::panic("Failed to setup idle thread"); } - + // Copy idle thread code to the allocated FCRAM std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode)); t.entrypoint = codeAddress; + t.initialSP = 0; t.tlsBase = 0; - t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe + t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe t.gprs[14] = 0; t.gprs[15] = codeAddress; t.cpsr = CPSR::UserMode; diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index e097fab6..3554e186 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -3,9 +3,9 @@ #include "kernel_types.hpp" #include "cpu.hpp" -Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config) - : cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) { - objects.reserve(512); // Make room for a few objects to avoid further memory allocs later +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) { + objects.reserve(512); // Make room for a few objects to avoid further memory allocs later mutexHandles.reserve(8); portHandles.reserve(32); threadIndices.reserve(appResourceLimits.maxThreads); diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 9eb7a197..0e667f58 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -697,3 +698,18 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) { return true; } } + +std::vector Kernel::getMainProcessThreads() { + // Sort the thread indices so that they appear nicer in the debugger + auto indices = threadIndices; + std::sort(indices.begin(), indices.end()); + + std::vector ret; + ret.reserve(indices.size()); + + for (const auto& index : indices) { + ret.push_back(threads[index]); + } + + return ret; +} \ No newline at end of file diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index be12eb8e..2c959493 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -5,8 +5,10 @@ #include "ipc.hpp" #include "kernel.hpp" -ServiceManager::ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config) - : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), +ServiceManager::ServiceManager( + std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config, LuaManager& lua +) + : regs(regs), mem(mem), kernel(kernel), lua(lua), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem, config), csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel, config), hid(mem, kernel), http(mem), ir_user(mem, hid, config, kernel), frd(mem), fs(mem, kernel, config), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem, kernel), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), ns(mem), nwm_uds(mem, kernel), ptm(mem, config), soc(mem), ssl(mem), @@ -214,6 +216,12 @@ void ServiceManager::publishToSubscriber(u32 messagePointer) { } void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) { + if (haveServiceIntercepts) [[unlikely]] { + if (checkForIntercept(messagePointer, handle)) [[unlikely]] { + return; + } + } + switch (handle) { // Breaking alphabetical order a bit to place the ones I think are most common at the top case KernelHandles::GPU: [[likely]] gsp_gpu.handleSyncRequest(messagePointer); break; @@ -257,4 +265,25 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) { case KernelHandles::Y2R: y2r.handleSyncRequest(messagePointer); break; default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer)); } +} + +bool ServiceManager::checkForIntercept(u32 messagePointer, Handle handle) { + // Check if there's a Lua handler for this function and call it + const u32 function = mem.read32(messagePointer); + + for (auto [serviceName, serviceHandle] : serviceMap) { + if (serviceHandle == handle) { + auto intercept = InterceptedService(std::string(serviceName), function); + if (interceptedServices.contains(intercept)) { + // If the Lua handler returns true, it means the service is handled entirely + // From Lua, and we shouldn't do anything else here. + return lua.signalInterceptedService(intercept.serviceName, function, messagePointer); + } + + break; + } + } + + // Lua did not intercept the service, so emulate it normally + return false; } \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e7556e4e..1079fc38 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -19,8 +19,9 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; #endif Emulator::Emulator() - : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), - cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this), running(false) + : 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) #ifdef PANDA3DS_ENABLE_HTTP_SERVER , httpServer(this) @@ -271,6 +272,11 @@ bool Emulator::loadROM(const std::filesystem::path& path) { romType = ROMType::None; } + if (success) { + // Update the main thread entrypoint and SP so that the thread debugger can display them. + kernel.setMainThreadEntrypointAndSP(cpu.getReg(15), cpu.getReg(13)); + } + resume(); // Start the emulator return success; } diff --git a/src/lua.cpp b/src/lua.cpp index a5d497fd..c23cfbc6 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -94,13 +94,41 @@ void LuaManager::loadString(const std::string& code) { } void LuaManager::signalEventInternal(LuaEvent e) { - lua_getglobal(L, "eventHandler"); // We want to call the event handler - lua_pushnumber(L, static_cast(e)); // Push event type + lua_getglobal(L, "eventHandler"); // We want to call the event handler + lua_pushinteger(L, static_cast(e)); // Push event type // Call the function with 1 argument and 0 outputs, without an error handler lua_pcall(L, 1, 0, 0); } +// Calls the "interceptService" function, if it exists, when a service call is intercepted +// It passes the service name, the function header, and a pointer to the call's TLS buffer as parameters +// interceptService is expected to return a bool, which indicates whether the C++ code should proceed to handle the service call +// or if the Lua code handles it entirely. +// If the bool is true, the Lua code handles the service call entirely and the C++ code doesn't do anything extra +// Otherwise, then the C++ code calls its service call handling code as usual. +bool LuaManager::signalInterceptedService(const std::string& service, u32 function, u32 messagePointer) { + lua_getglobal(L, "interceptService"); + lua_pushstring(L, service.c_str()); // Push service name + lua_pushinteger(L, function); // Push function header + lua_pushinteger(L, messagePointer); // Push pointer to TLS buffer + + // Call the function with 3 arguments and 1 output, without an error handler + const int status = lua_pcall(L, 3, 1, 0); + + if (status != LUA_OK) { + const char* err = lua_tostring(L, -1); + fprintf(stderr, "Lua: Error in interceptService: %s\n", err); + lua_pop(L, 1); // Pop error message from stack + return false; // Have the C++ handle the service call + } + + // Return the value interceptService returned + const bool ret = lua_toboolean(L, -1); + lua_pop(L, 1); + return ret; +} + void LuaManager::reset() { // Reset scripts haveScript = false; @@ -111,17 +139,17 @@ void LuaManager::reset() { Emulator* LuaManager::g_emulator = nullptr; -#define MAKE_MEMORY_FUNCTIONS(size) \ - static int read##size##Thunk(lua_State* L) { \ - const u32 vaddr = (u32)lua_tonumber(L, 1); \ - lua_pushnumber(L, LuaManager::g_emulator->getMemory().read##size(vaddr)); \ - return 1; \ - } \ - static int write##size##Thunk(lua_State* L) { \ - const u32 vaddr = (u32)lua_tonumber(L, 1); \ - const u##size value = (u##size)lua_tonumber(L, 2); \ - LuaManager::g_emulator->getMemory().write##size(vaddr, value); \ - return 0; \ +#define MAKE_MEMORY_FUNCTIONS(size) \ + static int read##size##Thunk(lua_State* L) { \ + const u32 vaddr = (u32)lua_tointeger(L, 1); \ + lua_pushinteger(L, LuaManager::g_emulator->getMemory().read##size(vaddr)); \ + return 1; \ + } \ + static int write##size##Thunk(lua_State* L) { \ + const u32 vaddr = (u32)lua_tointeger(L, 1); \ + const u##size value = (u##size)lua_tointeger(L, 2); \ + LuaManager::g_emulator->getMemory().write##size(vaddr, value); \ + return 0; \ } MAKE_MEMORY_FUNCTIONS(8) @@ -131,26 +159,26 @@ MAKE_MEMORY_FUNCTIONS(64) #undef MAKE_MEMORY_FUNCTIONS static int readFloatThunk(lua_State* L) { - const u32 vaddr = (u32)lua_tonumber(L, 1); + const u32 vaddr = (u32)lua_tointeger(L, 1); lua_pushnumber(L, (lua_Number)Helpers::bit_cast(LuaManager::g_emulator->getMemory().read32(vaddr))); return 1; } static int writeFloatThunk(lua_State* L) { - const u32 vaddr = (u32)lua_tonumber(L, 1); + const u32 vaddr = (u32)lua_tointeger(L, 1); const float value = (float)lua_tonumber(L, 2); LuaManager::g_emulator->getMemory().write32(vaddr, Helpers::bit_cast(value)); return 0; } static int readDoubleThunk(lua_State* L) { - const u32 vaddr = (u32)lua_tonumber(L, 1); + const u32 vaddr = (u32)lua_tointeger(L, 1); lua_pushnumber(L, (lua_Number)Helpers::bit_cast(LuaManager::g_emulator->getMemory().read64(vaddr))); return 1; } static int writeDoubleThunk(lua_State* L) { - const u32 vaddr = (u32)lua_tonumber(L, 1); + const u32 vaddr = (u32)lua_tointeger(L, 1); const double value = (double)lua_tonumber(L, 2); LuaManager::g_emulator->getMemory().write64(vaddr, Helpers::bit_cast(value)); return 0; @@ -158,12 +186,12 @@ static int writeDoubleThunk(lua_State* L) { static int getAppIDThunk(lua_State* L) { std::optional id = LuaManager::g_emulator->getMemory().getProgramID(); - + // If the app has an ID, return true + its ID // Otherwise return false and 0 as the ID if (id.has_value()) { - lua_pushboolean(L, 1); // Return true - lua_pushnumber(L, u32(*id)); // Return bottom 32 bits + lua_pushboolean(L, 1); // Return true + lua_pushnumber(L, u32(*id)); // Return bottom 32 bits lua_pushnumber(L, u32(*id >> 32)); // Return top 32 bits } else { lua_pushboolean(L, 0); // Return false @@ -192,13 +220,14 @@ static int resetThunk(lua_State* L) { static int loadROMThunk(lua_State* L) { // Path argument is invalid, report that loading failed and exit - if (lua_type(L, -1) != LUA_TSTRING) { + if (lua_type(L, 1) != LUA_TSTRING) { lua_pushboolean(L, 0); + lua_error(L); return 1; } - size_t pathLength; - const char* const str = lua_tolstring(L, -1, &pathLength); + usize pathLength; + const char* const str = lua_tolstring(L, 1, &pathLength); const auto path = std::filesystem::path(std::string(str, pathLength)); // Load ROM and reply if it succeeded or not @@ -206,6 +235,34 @@ static int loadROMThunk(lua_State* L) { return 1; } +static int addServiceInterceptThunk(lua_State* L) { + // Service name argument is invalid, report that loading failed and exit + if (lua_type(L, 1) != LUA_TSTRING) { + lua_pushboolean(L, 0); + lua_error(L); + return 2; + } + + if (lua_type(L, 2) != LUA_TNUMBER) { + lua_pushboolean(L, 0); + lua_error(L); + return 2; + } + + // Get the name of the service we want to intercept, as well as the header of the function to intercept + usize nameLength; + const char* const str = lua_tolstring(L, 1, &nameLength); + const u32 function = (u32)lua_tointeger(L, 2); + const auto serviceName = std::string(str, nameLength); + LuaManager::g_emulator->getServiceManager().addServiceIntercept(serviceName, function); + return 2; +} + +static int clearServiceInterceptsThunk(lua_State* L) { + LuaManager::g_emulator->getServiceManager().clearServiceIntercepts(); + return 0; +} + static int getButtonsThunk(lua_State* L) { auto buttons = LuaManager::g_emulator->getServiceManager().getHID().getOldButtons(); lua_pushinteger(L, static_cast(buttons)); @@ -262,7 +319,7 @@ static int disassembleARMThunk(lua_State* L) { static int disassembleTeakThunk(lua_State* L) { const u16 instruction = u16(lua_tonumber(L, 1)); const u16 expansion = u16(lua_tonumber(L, 2)); - + std::string disassembly = Teakra::Disassembler::Do(instruction, expansion); lua_pushstring(L, disassembly.c_str()); return 1; @@ -292,6 +349,8 @@ static constexpr luaL_Reg functions[] = { { "__getButton", getButtonThunk }, { "__disassembleARM", disassembleARMThunk }, { "__disassembleTeak", disassembleTeakThunk }, + {"__addServiceIntercept", addServiceInterceptThunk }, + {"__clearServiceIntercepts", clearServiceInterceptsThunk }, { nullptr, nullptr }, }; // clang-format on @@ -332,6 +391,8 @@ void LuaManager::initializeThunks() { disassembleARM = function(pc, instruction) return GLOBALS.__disassembleARM(pc, instruction) end, disassembleTeak = function(opcode, exp) return GLOBALS.__disassembleTeak(opcode, exp or 0) end, + addServiceIntercept = function(service, func) return GLOBALS.__addServiceIntercept(service, func) end, + clearServiceIntercepts = function() return GLOBALS.__clearServiceIntercepts() end, Frame = __Frame, ButtonA = __ButtonA, @@ -340,6 +401,8 @@ void LuaManager::initializeThunks() { ButtonY = __ButtonY, ButtonL = __ButtonL, ButtonR = __ButtonR, + ButtonZL = __ButtonZL, + ButtonZR = __ButtonZR, ButtonUp = __ButtonUp, ButtonDown = __ButtonDown, ButtonLeft = __ButtonLeft, diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index dbdbdec8..a55684e0 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -66,6 +66,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window")); auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor")); + auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger")); auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); @@ -73,8 +74,11 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); }); connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); }); connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); }); + connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); }); connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware); + connect(this, &MainWindow::emulatorPaused, this, [this]() { threadDebugger->update(); }, Qt::BlockingQueuedConnection); + auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); aboutAction->setMenuRole(QAction::AboutRole); connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); @@ -89,6 +93,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) patchWindow = new PatchWindow(this); luaEditor = new TextEditorWindow(this, "script.lua", ""); shaderEditor = new ShaderEditorWindow(this, "shader.glsl", ""); + threadDebugger = new ThreadDebugger(emu, this); shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload()); if (shaderEditor->supported) { @@ -381,9 +386,19 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { delete message.cheat.c; } break; - case MessageType::Pause: emu->pause(); break; case MessageType::Resume: emu->resume(); break; - case MessageType::TogglePause: emu->togglePause(); break; + case MessageType::Pause: + emu->pause(); + emit emulatorPaused(); + break; + + case MessageType::TogglePause: + emu->togglePause(); + if (!emu->running) { + emit emulatorPaused(); + }; + break; + case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break; case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; diff --git a/src/panda_qt/thread_debugger.cpp b/src/panda_qt/thread_debugger.cpp new file mode 100644 index 00000000..04dc495c --- /dev/null +++ b/src/panda_qt/thread_debugger.cpp @@ -0,0 +1,76 @@ +#include "panda_qt/thread_debugger.hpp" + +#include + +static QString threadStatusToQString(ThreadStatus status) { + switch (status) { + case ThreadStatus::Running: return QObject::tr("Running"); + case ThreadStatus::Ready: return QObject::tr("Ready"); + case ThreadStatus::WaitArbiter: return QObject::tr("Waiting on arbiter"); + case ThreadStatus::WaitSleep: return QObject::tr("Sleeping"); + case ThreadStatus::WaitSync1: return QObject::tr("WaitSync (1)"); + case ThreadStatus::WaitSyncAny: return QObject::tr("WaitSync (Any)"); + case ThreadStatus::WaitSyncAll: return QObject::tr("WaitSync (All)"); + case ThreadStatus::WaitIPC: return QObject::tr("Waiting for IPC"); + case ThreadStatus::Dormant: return QObject::tr("Dormant"); + case ThreadStatus::Dead: return QObject::tr("Dead"); + default: return QObject::tr("Unknown thread status"); + } +} + +ThreadDebugger::ThreadDebugger(Emulator* emu, QWidget* parent) : emu(emu), QWidget(parent, Qt::Window) { + setWindowTitle(tr("Thread Debugger")); + resize(700, 600); + + mainLayout = new QVBoxLayout(this); + threadTable = new QTableWidget(this); + threadTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + mainLayout->addWidget(threadTable); +} + +void ThreadDebugger::update() { + threadTable->clear(); + + threadTable->setSelectionMode(QAbstractItemView::NoSelection); + threadTable->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + threadTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + threadTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + threadTable->setColumnCount(5); + threadTable->verticalHeader()->setVisible(false); + threadTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + threadTable->setHorizontalHeaderLabels(QStringList({"ID", "Status", "PC", "Entrypoint", "Stack Top"})); + + const auto threads = emu->getKernel().getMainProcessThreads(); + const usize count = threads.size(); + threadTable->setRowCount(count); + + for (int i = 0; i < count; i++) { + const auto& thread = threads[i]; + setListItem(i, 0, fmt::format("{}", thread.index)); + setListItem(i, 1, threadStatusToQString(thread.status)); + setListItem(i, 2, fmt::format("{:08X}", thread.gprs[15])); + setListItem(i, 3, fmt::format("{:08X}", thread.entrypoint)); + setListItem(i, 4, fmt::format("{:08X}", thread.initialSP)); + } +} + +void ThreadDebugger::setListItem(int row, int column, const std::string& str) { setListItem(row, column, QString::fromStdString(str)); } + +void ThreadDebugger::setListItem(int row, int column, const QString& str) { + QTableWidgetItem* item = new QTableWidgetItem(); + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(widget); + + layout->setAlignment(Qt::AlignVCenter); + layout->setContentsMargins(5, 0, 5, 0); + + label->setText(str); + layout->addWidget(label); + widget->setLayout(layout); + + threadTable->setItem(row, column, item); + threadTable->setCellWidget(row, column, widget); +}