Files
libretro/bios/Other/eka2l1/scripts/eka2l1/events.lua
T

202 lines
9.4 KiB
Lua

--- **Events** module provides abilities to register callbacks at certain events in the emulator.
---
--- The current supported callback types are: breakpoint and IPC (Inter-process communication) callback. For breakpoint callback,
--- it's always preferred to not spend too much time in the callback, which may impact emulated app performance.
---
--- Each script is allowed to have a limit of 2^32 callbacks, but the true number depends on the memory of your system and how much the Lua
--- runner can hold. All callbacks are discarded on script unload.
---
-- @module events
local ipcCtx = require('eka2l1.ipc.context')
local kern = require('eka2l1.kernel')
local common = require('eka2l1.common')
events = {}
--- Passed to **registerIpcHook**, indicating that IPC callback will be invoked when the client process first
--- sends message to the server process.
events.EVENT_IPC_SEND = 0
--- Passed to **registerIpcHook**, indicating that IPC callback will be invoked after the server process finished
--- processing the request and tell the emulator to notify back to the waiting client.
events.EVENT_IPC_COMPLETE = 2
--- Constant indicating an invalid handle returned from callback registering.
events.INVALID_HOOK_HANDLE = 0xFFFFFFFF
local ffi = require("ffi")
pcall(ffi.load, 'native-lib', true)
ffi.cdef([[
void free(void *ptr);
typedef struct ipc_msg ipc_msg;
typedef struct thread thread;
typedef void (__stdcall *breakpoint_hit_lua_func)();
typedef void (__stdcall *ipc_sent_lua_func)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, thread*);
typedef void (__stdcall *ipc_completed_lua_func)(ipc_msg*);
uint32_t eka2l1_cpu_register_lib_hook(const char *lib_name, const uint32_t ord, const uint32_t process_uid, const uint32_t codesegUid3, const uint32_t codesegHash, breakpoint_hit_lua_func func);
uint32_t eka2l1_cpu_register_bkpt_hook(const char *image_name, const uint32_t addr, const uint32_t process_uid, const uint32_t codesegUid3, const uint32_t codesegHash, breakpoint_hit_lua_func func);
uint32_t eka2l1_cpu_register_rom_export_bkpt_hook(const char *image_name, const uint32_t ordinal, const uint32_t methodHash, const uint32_t hookOffset, const uint32_t process_uid, const uint32_t codesegUid3, breakpoint_hit_lua_func func);
uint32_t eka2l1_register_ipc_sent_hook(const char *server_name, const int opcode, ipc_sent_lua_func func);
uint32_t eka2l1_register_ipc_completed_hook(const char *server_name, const int opcode, ipc_completed_lua_func func);
void eka2l1_clear_hook(const uint32_t hook_handle);
]])
--- Register a callback on library function being called.
---
--- This is **registerBreakpointHook** in disguise, but with automated address detection. This is preferred
--- for the callback to work across many emulated devices, because address of the library function may change,
--- but the ordinal is consistent.
---
--- @param libName Name of the library that contains the target library function.
--- @param ord The ordinal of the function inside the library.
--- @param processUid UID3 of the process you want this callback to be triggered on. Use 0 for any active process.
--- @param codesegUid3 UID3 of the library the hook will be registered on. This is a safecheck to hook on the correct library.
--- @param func Callback function with no parameter and no return.
---
--- @return A handle to this callback (> 0), which can later be used for unregistering. `INVALID_HOOK_HANDLE` on failure.
---
--- @see clearHook
--- @see registerBreakpointHook
function events.registerLibraryHook(libName, ord, processUid, codesegUid3, func, codesegHash)
local libNameInC = ffi.new("char[?]", #libName + 1, libName)
local hash = codesegHash
if (hash == nil) then
hash = 0
end
return ffi.C.eka2l1_cpu_register_lib_hook(libNameInC, ord, processUid, codesegUid3, hash, function ()
local ran, errorMsg = pcall(func)
if not ran then
common.log('Error running breakpoint script, ' .. errorMsg)
end
end)
end
--- Register a callback on a breakpoint.
---
--- The address passed to this function should be relative to the original code base address found in
--- disassemble/image explorer program. The emulator will rebase the address relatively to where the library
--- is loaded in memory.
---
--- In case the breakpoint address is constant and should not be related to any libraries or executables, pass the string "constantaddr"
--- to the `libName` variable.
---
--- @param libName Name of the library that contains the target breakpoint. Use "constantaddr" for no library.
--- @param addr The address of the breakpoint, relative to the library's original code base address if libName is not "constantaddr"
--- @param processUid UID3 of the process you want this callback to be triggered on. Use 0 for any active process.
--- @param codesegUid3 UID3 of the library the hook will be registered on. This is a safecheck to hook on the correct library.
--- @param func Callback function with no parameter and no return.
---
--- @return A handle to this callback (> 0), which can later be used for unregistering. `INVALID_HOOK_HANDLE` on failure.
---
--- @see clearHook
--- @see registerLibraryHook
function events.registerBreakpointHook(libName, addr, processUid, codesegUid3, func, codesegHash)
local libNameInC = ffi.new("char[?]", #libName + 1, libName)
local hash = codesegHash
if (hash == nil) then
hash = 0
end
return ffi.C.eka2l1_cpu_register_bkpt_hook(libNameInC, addr, processUid, codesegUid3, hash, function ()
local ran, errorMsg = pcall(func)
if not ran then
common.log('Error running breakpoint script, ' .. errorMsg)
end
end)
end
--- Register a breakpoint at an offset inside a ROM export after verifying only
--- that exported method's code bytes. This avoids firmware-specific absolute
--- addresses and whole-DLL hashes while refusing methods with unknown code.
---
--- @param libName Name of the ROM library containing the method.
--- @param ordinal Export ordinal used to locate the method dynamically.
--- @param methodHash XXH32 fingerprint of the method, up to the next export.
--- @param hookOffset Byte offset from the method entry to the breakpoint.
--- @param processUid UID3 of the process to restrict the callback to, or 0.
--- @param codesegUid3 UID3 of the ROM library.
--- @param func Callback function with no parameter and no return.
function events.registerRomExportBreakpointHook(libName, ordinal, methodHash, hookOffset, processUid, codesegUid3, func)
local libNameInC = ffi.new("char[?]", #libName + 1, libName)
return ffi.C.eka2l1_cpu_register_rom_export_bkpt_hook(libNameInC, ordinal,
methodHash, hookOffset, processUid, codesegUid3, function ()
local ran, errorMsg = pcall(func)
if not ran then
common.log('Error running ROM export breakpoint script, ' .. errorMsg)
end
end)
end
--- Register a callback on IPC request send/complete.
---
--- For IPC send callback, you have the ability to read data the client sent before the request is delivered
--- to the server process.
---
--- For IPC complete callback, you will receive the message context before the request is signaled to be completed to
--- the client process.
---
--- Both the callback will receive an `ipc.context` object as its parameter.
---
---@param serverName Name of the server to intercept IPC messages.
---@param opcode The target message opcode to intercept.
---@param when The moment to receive the callback. Two options are `EVENT_IPC_SEND` and `EVENT_IPC_COMPLETE`
---@param func The callback function, with `ipc.context` object as its only parameter.
---
--- @return A handle to this callback (> 0), which can later be used for unregistering. `INVALID_HOOK_HANDLE` on failure.
---
--- @see clearHook
function events.registerIpcHook(serverName, opcode, when, func)
local serverNameInC = ffi.new("char[?]", #serverName + 1, serverName)
if when == events.EVENT_IPC_SEND then
return ffi.C.eka2l1_register_ipc_sent_hook(serverNameInC, opcode, function (arg0, arg1, arg2, arg3, flags, reqsts, sender)
local ran, retval = pcall(ipcCtx.makeFromValues, opcode, arg0, arg1, arg2, arg3, flags, reqsts, kern.thread(sender))
if not ran then
common.log('Fail to create IPC context, ' .. retval)
return
end
ran, retval = pcall(func, retval)
if not ran then
common.log('IPC sent callback encountered error, '.. retval)
end
end)
else
return ffi.C.eka2l1_register_ipc_completed_hook(serverNameInC, opcode, function (msg)
local ran, retval = pcall(ipcCtx.makeFromMessage, kern.message(msg))
if not ran then
common.log('Fail to create IPC context, ' .. retval)
return
end
ran, retval = pcall(func, retval)
if not ran then
common.log('IPC sent callback encountered error, '.. retval)
end
end)
end
end
--- Unregistering a callback.
---
--- This method is safe to call inside a callback. The callback will still continue to do the rest of its job
--- after the delete.
---
--- Note that failure is silent, and may only be reported on the log channel.
---
---@param handle The handle retrieved from registering.
function events.clearHook(handle)
ffi.C.eka2l1_clear_hook(handle)
end
return events