mods: flat function-entry hook table built at activation; interpreter skips it when empty

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-09-26 07:34:20 -07:00
parent b0aa7e108d
commit 199cfe01bc
9 changed files with 180 additions and 28 deletions
+61 -5
View File
@@ -50,6 +50,9 @@ extern "C" void gpu_ws_set_adaptive_backdrop_preload(int enabled);
extern "C" void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len);
extern "C" int fntrace_is_game_started(void);
/* Declared in mod_plugins.h; see active_function_entry_hooks() below. */
uint32_t g_psx_mod_function_entry_hooks = 0;
namespace PSXRecompV4 {
namespace {
@@ -80,6 +83,47 @@ RuntimeMods& state() {
return value;
}
/* Function-entry hooks of the ACTIVE plan, flattened at plugin activation into
* one table sorted by code key (address with the segment bits stripped, so a
* KUSEG or KSEG1 PC reaches the same hook as KSEG0). The interpreter consults
* this on every entry it dispatches, so the lookup is a binary search over
* integers, never a per-plugin string map, and an empty table short-circuits
* in the caller via g_psx_mod_function_entry_hooks. Plugin pointers refer into
* RuntimeMods::plan; every plan replacement clears the table first. */
struct ActiveFunctionEntryHook {
uint32_t key = 0;
PSXModFunctionEntryCallback callback = nullptr;
const ModResolution::Plugin* plugin = nullptr;
};
std::vector<ActiveFunctionEntryHook>& active_function_entry_hooks() {
static std::vector<ActiveFunctionEntryHook> value;
return value;
}
inline uint32_t function_entry_key(uint32_t address) {
return address & 0x1FFFFFFFu;
}
void clear_function_entry_hooks() {
active_function_entry_hooks().clear();
g_psx_mod_function_entry_hooks = 0;
}
void build_function_entry_hooks(const RuntimeMods& s) {
clear_function_entry_hooks();
if (!s.initialized || !s.plan.ok) return;
auto& table = active_function_entry_hooks();
for (const ModResolution::Plugin& plugin : s.plan.plugins)
for (const ModFunctionEntryHook& hook : mod_function_entry_hooks(plugin.id))
table.push_back({function_entry_key(hook.address), hook.callback, &plugin});
/* Stable: hooks sharing an address keep plan (plugin order) order. */
std::stable_sort(table.begin(), table.end(),
[](const ActiveFunctionEntryHook& a,
const ActiveFunctionEntryHook& b) { return a.key < b.key; });
g_psx_mod_function_entry_hooks = (uint32_t)table.size();
}
const ModPackage* selected_package(const std::string& id) {
return state().manager.selected_package(id);
}
@@ -1092,6 +1136,7 @@ bool mod_runtime_initialize(const std::filesystem::path& root,
std::string* error) {
RuntimeMods& s = state();
s.manager.set_root({});
clear_function_entry_hooks();
s.plan = {};
s.validation = {};
s.raw_disc_index.clear();
@@ -1137,6 +1182,7 @@ bool mod_runtime_clear_for_netplay(std::string* error) {
if (error) error->clear();
return true;
}
clear_function_entry_hooks();
s.plan = {};
s.validation = {};
s.raw_disc_index.clear();
@@ -1198,6 +1244,9 @@ bool mod_runtime_commit(const std::filesystem::path& disc_path, std::string* err
if (error) *error = s.error;
return false;
}
/* Hooks follow activation, never a bare commit: a new plan runs none of
* its function-entry hooks until mod_runtime_activate_plugins(). */
clear_function_entry_hooks();
s.plan = std::move(plan);
build_disc_index(s);
s.effective_disc_path = std::move(effective_disc);
@@ -1332,6 +1381,7 @@ extern "C" void mod_runtime_activate_plugins(void) {
mod_invoke_activation_plugin(plugin.id);
s.current_plugin = nullptr;
}
build_function_entry_hooks(s);
}
extern "C" void mod_runtime_on_vblank(void) {
@@ -1477,12 +1527,18 @@ extern "C" int psx_mod_register_function_entry_plugin(
extern "C" void psx_mod_function_entry(CPUState* cpu, uint32_t address) {
using namespace PSXRecompV4;
if (!g_psx_mod_function_entry_hooks || !cpu) return;
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;
const auto& table = active_function_entry_hooks();
const uint32_t key = function_entry_key(address);
auto it = std::lower_bound(
table.begin(), table.end(), key,
[](const ActiveFunctionEntryHook& hook, uint32_t k) { return hook.key < k; });
for (; it != table.end() && it->key == key; ++it) {
const ModResolution::Plugin* previous = s.current_plugin;
s.current_plugin = it->plugin;
it->callback(cpu, address);
s.current_plugin = previous;
}
}