Add system call intercepts to Lua

This commit is contained in:
wheremyfoodat 2025-07-03 13:02:10 +03:00
parent 620e3699ec
commit 228068901b
7 changed files with 147 additions and 34 deletions

View file

@ -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);

View file

@ -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

View file

@ -2,9 +2,12 @@
#include <array>
#include <optional>
#include <span>
#include <string>
#include <unordered_set>
#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<std::string>{}(s.serviceName);
usize h2 = std::hash<u32>{}(s.function);
return h1 ^ (h2 << 1);
}
};
std::unordered_set<InterceptedService, InterceptedServiceHash> 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<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
ServiceManager(std::span<u32, 16> 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(); }
};

View file

@ -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);

View file

@ -5,8 +5,10 @@
#include "ipc.hpp"
#include "kernel.hpp"
ServiceManager::ServiceManager(std::span<u32, 16> 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<u32, 16> 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;

View file

@ -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)

View file

@ -1,4 +1,5 @@
#ifdef PANDA3DS_ENABLE_LUA
#include <fmt/format.h>
#include <teakra/disassembler.h>
#include <array>
@ -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<int>(e)); // Push event type
lua_getglobal(L, "eventHandler"); // We want to call the event handler
lua_pushinteger(L, static_cast<int>(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<float, u32>(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<u32, float>(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<double, u64>(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<u64, double>(value));
return 0;
@ -158,12 +187,12 @@ static int writeDoubleThunk(lua_State* L) {
static int getAppIDThunk(lua_State* L) {
std::optional<u64> 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<lua_Integer>(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,