mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-13 06:53:24 -05:00
202 lines
9.4 KiB
Lua
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 |