diff --git a/runtime/src/mod_runtime.cpp b/runtime/src/mod_runtime.cpp index 867fbb07..56a1847e 100644 --- a/runtime/src/mod_runtime.cpp +++ b/runtime/src/mod_runtime.cpp @@ -2874,9 +2874,16 @@ extern "C" int psx_mod_set_option_live(const char* package_id, const char* featu for (const ModResolution::Plugin& plugin : s.plan.plugins) if (plugin.package_id == package_id && plugin.feature_id == feature_id) in_plan = true; if (!in_plan || !psx_mod_option_live_capable(package_id, feature_id)) return 0; - /* Only a feature whose plugin re-reads options live sees the new value; - * anything else keeps its committed value until the next start. */ - live_option_values()[live_option_key(package_id, feature_id, option_id)] = value; + /* The value is visible to the callbacks while they run (a plugin may + * re-read it through psx_mod_current_option_value), but it is kept only + * if one of them accepts. Refused by all = nothing changes this session: + * the previous value (live or committed) is restored. */ + auto& values = live_option_values(); + const std::string key = live_option_key(package_id, feature_id, option_id); + auto prev_it = values.find(key); + const bool had_prev = prev_it != values.end(); + const std::string prev = had_prev ? prev_it->second : std::string(); + values[key] = value; int live = 0; for (const ModResolution::Plugin& plugin : s.plan.plugins) { if (plugin.package_id != package_id || plugin.feature_id != feature_id) continue; @@ -2885,6 +2892,10 @@ extern "C" int psx_mod_set_option_live(const char* package_id, const char* featu PluginCallbackScope scope(s, &plugin); if (it->second(option_id, value)) live = 1; } + if (!live) { + if (had_prev) values[key] = prev; + else values.erase(key); + } return live; } diff --git a/runtime/tests/test_mod_runtime.cpp b/runtime/tests/test_mod_runtime.cpp index 27d7aff6..dab9619c 100644 --- a/runtime/tests/test_mod_runtime.cpp +++ b/runtime/tests/test_mod_runtime.cpp @@ -318,6 +318,7 @@ int iso_read_cdda_sector(void*,uint32_t,uint8_t*,int); static int big_ram_activations; static int option_changed_hits = 0; static std::string option_changed_value; +static int option_changed_refuse = 0; /* 1 = the plugin answers "next start" */ static int test_option_changed(const char* option_id, const char* value) { char cur[32]; option_changed_hits++; @@ -326,7 +327,7 @@ static int test_option_changed(const char* option_id, const char* value) { psx_mod_current_option_value("size", cur, sizeof(cur)) == 1) option_changed_value = cur; (void)value; - return 1; + return option_changed_refuse ? 0 : 1; } static void test_big_ram_activation(void) { big_ram_activations++; @@ -1111,6 +1112,16 @@ int main(int argc, char** argv) { check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && std::string(value) == "four", "a live override must answer later reads"); + /* A callback that refuses ("nothing changes until next start") must + * leave the value the session runs with untouched. */ + option_changed_refuse = 1; + check(psx_mod_set_option_live("runtime.ram", "big-ram", "size", "two") == 0 && + option_changed_hits == 2, + "a refused live set reports refused after asking the plugin"); + check(psx_mod_option_value("runtime.ram", "big-ram", "size", value, sizeof(value)) == 1 && + std::string(value) == "four", + "a refused live set must not change the value later reads see"); + option_changed_refuse = 0; check(psx_mod_set_option_live("runtime.ram", "no-such-feature", "size", "x") == 0, "features outside the plan are not live-settable"); check(psx_mod_save_selection() == 1, "saving the selection mid-session must succeed");