mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
mods: resolve function-entry plugins and run them only for the active plan
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -444,7 +444,11 @@ the same plugin id. Active plugin identities and owners participate in the
|
||||
canonical plan fingerprint.
|
||||
|
||||
An implementation may register an activation callback, a deterministic
|
||||
guest-VBlank callback, or both under the same id. Activation runs after the
|
||||
guest-VBlank callback, function-entry hooks, or any combination under the same
|
||||
id. A function-entry hook (`psx_mod_register_function_entry_plugin`) runs at the
|
||||
top of a generated function the game config lists in
|
||||
`[recompiler] mod_function_entry_funcs`; like the other kinds it satisfies a
|
||||
manifest `[[plugin]]` and runs only while the resolved plan activates its id. Activation runs after the
|
||||
launcher's final mod-plan commit and before renderer/window initialization; it
|
||||
is appropriate for a game-owned mod that selects a fixed display aspect or
|
||||
another pre-boot host feature. VBlank callbacks run from the emulated GPU
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "mod_plugins.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
@@ -454,9 +456,15 @@ bool mod_register_builtin_resolver(const std::string& id, ModBuiltinResolver res
|
||||
void mod_clear_builtin_resolvers_for_tests();
|
||||
bool mod_register_activation_plugin(const std::string& id, void (*callback)(void));
|
||||
bool mod_register_vblank_plugin(const std::string& id, void (*callback)(void));
|
||||
/* A function-entry hook is a trusted implementation like the others: manifests
|
||||
* select it by id, and it runs only while a resolved plan activates that id. */
|
||||
bool mod_register_function_entry_plugin(const std::string& id, uint32_t address,
|
||||
PSXModFunctionEntryCallback callback);
|
||||
bool mod_plugin_registered(const std::string& id);
|
||||
void mod_invoke_activation_plugin(const std::string& id);
|
||||
void mod_invoke_vblank_plugin(const std::string& id);
|
||||
void mod_invoke_function_entry_plugin(const std::string& id, ::CPUState* cpu,
|
||||
uint32_t address);
|
||||
void mod_clear_plugins_for_tests();
|
||||
|
||||
} // namespace PSXRecompV4
|
||||
|
||||
@@ -34,6 +34,9 @@ std::map<std::string, ModBuiltinResolver>& builtin_resolvers() {
|
||||
struct RegisteredPlugin {
|
||||
PSXModActivationCallback activation = nullptr;
|
||||
PSXModVBlankCallback vblank = nullptr;
|
||||
/* Generated-function entry hooks, keyed by guest address. One id may
|
||||
* observe several functions. */
|
||||
std::vector<std::pair<uint32_t, PSXModFunctionEntryCallback>> function_entries;
|
||||
};
|
||||
|
||||
std::map<std::string, RegisteredPlugin>& registered_plugins() {
|
||||
@@ -1449,10 +1452,21 @@ bool mod_register_vblank_plugin(const std::string& id, void (*callback)(void)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mod_register_function_entry_plugin(const std::string& id, uint32_t address,
|
||||
PSXModFunctionEntryCallback callback) {
|
||||
if (!valid_id(id) || !address || !callback) return false;
|
||||
RegisteredPlugin& plugin = registered_plugins()[id];
|
||||
for (const auto& hook : plugin.function_entries)
|
||||
if (hook.first == address) return false;
|
||||
plugin.function_entries.emplace_back(address, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mod_plugin_registered(const std::string& id) {
|
||||
const auto found = registered_plugins().find(id);
|
||||
return found != registered_plugins().end() &&
|
||||
(found->second.activation || found->second.vblank);
|
||||
(found->second.activation || found->second.vblank ||
|
||||
!found->second.function_entries.empty());
|
||||
}
|
||||
|
||||
void mod_invoke_activation_plugin(const std::string& id) {
|
||||
@@ -1467,6 +1481,14 @@ void mod_invoke_vblank_plugin(const std::string& id) {
|
||||
found->second.vblank();
|
||||
}
|
||||
|
||||
void mod_invoke_function_entry_plugin(const std::string& id, ::CPUState* cpu,
|
||||
uint32_t address) {
|
||||
const auto found = registered_plugins().find(id);
|
||||
if (found == registered_plugins().end()) return;
|
||||
for (const auto& hook : found->second.function_entries)
|
||||
if (hook.first == address) hook.second(cpu, address);
|
||||
}
|
||||
|
||||
void mod_clear_plugins_for_tests() {
|
||||
registered_plugins().clear();
|
||||
}
|
||||
|
||||
@@ -80,17 +80,6 @@ RuntimeMods& state() {
|
||||
return value;
|
||||
}
|
||||
|
||||
struct FunctionEntryPlugin {
|
||||
std::string id;
|
||||
uint32_t address = 0;
|
||||
PSXModFunctionEntryCallback callback = nullptr;
|
||||
};
|
||||
|
||||
std::vector<FunctionEntryPlugin>& function_entry_plugins() {
|
||||
static std::vector<FunctionEntryPlugin> value;
|
||||
return value;
|
||||
}
|
||||
|
||||
const ModPackage* selected_package(const std::string& id) {
|
||||
return state().manager.selected_package(id);
|
||||
}
|
||||
@@ -1482,22 +1471,18 @@ extern "C" uint32_t psx_mod_display_height(void) {
|
||||
extern "C" int psx_mod_register_function_entry_plugin(
|
||||
const char* id, uint32_t address, PSXModFunctionEntryCallback callback) {
|
||||
using namespace PSXRecompV4;
|
||||
if (!id || !*id || !address || !callback) return 0;
|
||||
auto& plugins = function_entry_plugins();
|
||||
const auto duplicate = std::find_if(
|
||||
plugins.begin(), plugins.end(), [&](const FunctionEntryPlugin& item) {
|
||||
return item.id == id && item.address == address;
|
||||
});
|
||||
if (duplicate != plugins.end()) return 0;
|
||||
plugins.push_back(FunctionEntryPlugin{id, address, callback});
|
||||
return 1;
|
||||
if (!id || !address || !callback) return 0;
|
||||
return mod_register_function_entry_plugin(id, address, callback) ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" void psx_mod_function_entry(CPUState* cpu, uint32_t address) {
|
||||
using namespace PSXRecompV4;
|
||||
if (!cpu) return;
|
||||
for (const FunctionEntryPlugin& plugin : function_entry_plugins()) {
|
||||
if (plugin.address == address) plugin.callback(cpu, address);
|
||||
RuntimeMods& s = state();
|
||||
if (!cpu || !s.initialized || !s.plan.ok) return;
|
||||
for (const ModResolution::Plugin& plugin : s.plan.plugins) {
|
||||
s.current_plugin = &plugin;
|
||||
mod_invoke_function_entry_plugin(plugin.id, cpu, address);
|
||||
s.current_plugin = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -368,6 +368,50 @@ int main() {
|
||||
"is unavailable");
|
||||
check(reload.set_feature_enabled(
|
||||
"plugin.mod", "vblank", false, &error), error.c_str());
|
||||
|
||||
/* A function-entry hook is selected by id exactly like activation and
|
||||
* VBlank plugins; a manifest naming only such an implementation must
|
||||
* resolve, and must still fail closed once it is gone. */
|
||||
static int entry_hits = 0;
|
||||
check(mod_register_function_entry_plugin(
|
||||
"test.entry", 0x80010000u,
|
||||
+[](CPUState*, uint32_t address) {
|
||||
if (address == 0x80010000u) entry_hits++;
|
||||
}),
|
||||
"function-entry plugin must register");
|
||||
check(!mod_register_function_entry_plugin(
|
||||
"test.entry", 0x80010000u, +[](CPUState*, uint32_t) {}),
|
||||
"duplicate function-entry hook must be rejected");
|
||||
write_text(root / "packages/entry.mod/1.0.0/manifest.toml",
|
||||
"format_version = 5\n"
|
||||
"id = \"entry.mod\"\n"
|
||||
"version = \"1.0.0\"\n"
|
||||
"name = \"Entry Mod\"\n"
|
||||
"resolver = \"declarative\"\n"
|
||||
"[[target]]\n"
|
||||
"game_id = \"SLUS-TEST\"\n"
|
||||
"[[feature]]\n"
|
||||
"id = \"entry\"\n"
|
||||
"name = \"Entry Plugin\"\n"
|
||||
"[[plugin]]\n"
|
||||
"feature = \"entry\"\n"
|
||||
"id = \"test.entry\"\n");
|
||||
check(reload.scan(&error), error.c_str());
|
||||
check(reload.set_feature_enabled("entry.mod", "entry", true, &error),
|
||||
error.c_str());
|
||||
ModResolution entry_resolution = reload.resolve("SLUS-TEST");
|
||||
check(entry_resolution.ok && entry_resolution.plugins.size() == 1 &&
|
||||
entry_resolution.plugins[0].id == "test.entry",
|
||||
"enabled function-entry plugin must resolve");
|
||||
mod_invoke_function_entry_plugin("test.entry", nullptr, 0x80010004u);
|
||||
mod_invoke_function_entry_plugin("test.entry", nullptr, 0x80010000u);
|
||||
check(entry_hits == 1, "function-entry hook must fire only for its address");
|
||||
mod_clear_plugins_for_tests();
|
||||
ModResolution entry_unavailable = reload.resolve("SLUS-TEST");
|
||||
check(!entry_unavailable.ok,
|
||||
"enabled function-entry plugin must fail closed when unregistered");
|
||||
check(reload.set_feature_enabled("entry.mod", "entry", false, &error),
|
||||
error.c_str());
|
||||
write_text(root / "packages/features.mod/1.0.0/manifest.toml",
|
||||
manifest("features.mod", "1.0.0",
|
||||
"\n[[feature]]\n"
|
||||
|
||||
Reference in New Issue
Block a user