From 228068901b5ebe7713d3a3b36e23f47e453f4009 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:02:10 +0300 Subject: [PATCH] Add system call intercepts to Lua --- include/kernel/kernel.hpp | 3 +- include/lua_manager.hpp | 4 +- include/services/service_manager.hpp | 30 +++++++- src/core/kernel/kernel.cpp | 6 +- src/core/services/service_manager.cpp | 28 ++++++- src/emulator.cpp | 5 +- src/lua.cpp | 105 ++++++++++++++++++++------ 7 files changed, 147 insertions(+), 34 deletions(-) diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 3f20b5e1..fcfe3b39 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 { @@ -199,7 +200,7 @@ public: void readDirectory(u32 messagePointer, Handle directory); public: - Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config); + 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); diff --git a/include/lua_manager.hpp b/include/lua_manager.hpp index 46fd553a..3c838c5c 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,5 @@ 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/services/service_manager.hpp b/include/services/service_manager.hpp index 14f3f397..fd3a658f 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" @@ -86,6 +89,28 @@ 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; + 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; } + }; + + struct InterceptedServiceHash { + usize operator()(const InterceptedService& s) const noexcept { + usize h1 = std::hash{}(s.serviceName); + usize h2 = std::hash{}(s.function); + return h1 ^ (h2 << 1); + } + }; + + std::unordered_set interceptedServices = {}; + // "srv:" commands void enableNotification(u32 messagePointer); void getServiceHandle(u32 messagePointer); @@ -96,7 +121,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 +141,7 @@ 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)); } + void clearServiceIntercepts() { interceptedServices.clear(); } }; 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/services/service_manager.cpp b/src/core/services/service_manager.cpp index be12eb8e..bb187bce 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,28 @@ void ServiceManager::publishToSubscriber(u32 messagePointer) { } void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) { + if (interceptedServices.size() != 0) [[unlikely]] { + // Check if there's a Lua handler for this function and call it + u32 function = mem.read32(messagePointer); + + for (auto [serviceName, serviceHandle] : serviceMap) { + if (serviceHandle == handle) { + auto intercept = InterceptedService(std::string(serviceName), function); + if (interceptedServices.contains(intercept)) { + printf("Call to intercepted service\n"); + + // If the Lua handler returns true, it means the service is handled entirely + // From Lua, and we shouldn't do anything else here. + if (lua.signalInterceptedService(intercept.serviceName, function, messagePointer)) { + return; + } + } + + break; + } + } + } + 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; diff --git a/src/emulator.cpp b/src/emulator.cpp index e7556e4e..a2afaa5c 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) diff --git a/src/lua.cpp b/src/lua.cpp index a5d497fd..e20cb7e4 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -1,4 +1,5 @@ #ifdef PANDA3DS_ENABLE_LUA +#include #include #include @@ -94,13 +95,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 ? err : "(unknown error)"); + lua_pop(L, 1); // Remove 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 +140,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 +160,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 +187,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 +221,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 +236,31 @@ static int loadROMThunk(lua_State* L) { return 1; } +static int interceptServiceThunk(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); + + fmt::print("Intercepting call to {} (Function id: {:08X})\n", serviceName, function); + return 2; +} + static int getButtonsThunk(lua_State* L) { auto buttons = LuaManager::g_emulator->getServiceManager().getHID().getOldButtons(); lua_pushinteger(L, static_cast(buttons)); @@ -262,7 +317,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 +347,7 @@ static constexpr luaL_Reg functions[] = { { "__getButton", getButtonThunk }, { "__disassembleARM", disassembleARMThunk }, { "__disassembleTeak", disassembleTeakThunk }, + {"__interceptService", interceptServiceThunk}, { nullptr, nullptr }, }; // clang-format on @@ -332,6 +388,7 @@ 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, + interceptService = function(service, func) return GLOBALS.__interceptService(service, func) end, Frame = __Frame, ButtonA = __ButtonA,