Run module_start of plugins before starting boot module
The idea of this change is to allow plugins to perform patching/hooking before game boot
I don't think I fully understood how hle threading works so I hope this isn't introducing errors
This looks fine .. I think :)
Do you have some example plugin that uses this, for testing?
The idea is to bring plugin loading one small step closer to how cfw did it
I have a plugin chainloader here, for testing if some plugins would start working if sctrlHENSetStartModuleHandler is implemented
https://github.com/Kethen/ppsspp_plugin_loader/actions/runs/15644415241
ppsspp_plugin_loader_04a2d1a4.zip
Once active, it loads and starts plugins from ms0:/ppsspp_plugin_loader/<game id>/*, then run the handler registered with sctrlHENSetStartModuleHandler with a slight delay, within the plugin's own module_start
I've tried to chainload https://github.com/codestation/prxpatch/tree/master/mhp3loader , managed to run the handler registered with sctrlHENSetStartModuleHandler, but then it wants to sceKernelQuerySystemCall during the handler and rewrites the game's stubs, so it just crashes the game after running the handler.
I've also tried to chainload a custom version of https://github.com/Kethen/RemasteredControls_GTpsp that uses sctrlHENSetStartModuleHandler on PPSSPP. It works, but the plugin doesn't require early patching.
https://github.com/Kethen/RemasteredControls_GTpsp/actions/runs/15644814096
Finally actually looked at this. The approach seems good. I'm happy to merge, ready?
With sceKernelQuerySystemCall, there seems to be a problem that the encoding of API functions into numeric syscall parameters does not match our syscall encoding, and changing that would be a hugely invasive change (due to invalidating all save states, or requiring a very dodgy fixup pass when loading old states if we were to change our encoding).
I mean of course *(unsigned int *)(address + 4) = (((sceKernelQuerySystemCall(function)) << 6) | 12); - while we can implement sceKernelQuerySystemCall however we want, that shift by 6 and or by 12 is not gonna match our numbers.
Wait hang on, I'm stupid. It's perfectly fine. The 12 and the shift is just the encoding of the actual syscall instruction, which has 0 as upper 6 bits, 12 as lower 6 bits and the syscall number in the middle.
So we can probably correctly implement it.
Although wait a minute, I still don't quite get how it works. So sceKernelQuerySystemCall actually takes a function pointer, and registers a syscall argument ID to point to it? Oof. Well, it's totally doable to implement, we'll just invent a special module ID and add a dynamic table for that one...
Finally actually looked at this. The approach seems good. I'm happy to merge, ready?
Go for it, if it makes sense to you (don't think surrounding code has changed since?)
Although wait a minute, I still don't quite get how it works. So sceKernelQuerySystemCall actually takes a function pointer, and registers a syscall argument ID to point to it? Oof. Well, it's totally doable to implement, we'll just invent a special module ID and add a dynamic table for that one...
On real devices, each kernel module export gets an associated syscall id, for crossing the user/kernel barrier on user module imports. sceKernelQuerySystemCall can be used to fetch the syscall id for exported kernel functions. With the id, one can then use it to make syscall instruction and inject detours/overwrite existing nid imports on user modules (mostly the game). In the case of emulation, it can either be a dynamic table that creates entries on sceKernelQuerySystemCall call, or have syscall IDs generated on plugin load per function export, having sceKernelQuerySystemCall fetch those IDs.
~~I have to admit I'm still struggling to understand the detouring mechanism.~~
So, hook_import_bynid calls find_import_bynid to find the import stub to hook.
api_hook_import_syscall is called with the desired target function. That's just a random local function in the plugin. sceKernelQuerySystemCall is called on that, which should result in nothing since it doesn't know about it? Oh wait, hang on - I guess it's declared as an export in the plugin, in exports.exp? And thus has a stub and will be returned actually? Ahh!
I understand now. Never mind :)
So, yeah, we need to extend the syscall mechanism to handle this type of import, which is quite different from our current static tables.