diff --git a/generated/OpenBIOS.emitter.sha b/generated/OpenBIOS.emitter.sha index 5f71563e..5b4ab659 100644 --- a/generated/OpenBIOS.emitter.sha +++ b/generated/OpenBIOS.emitter.sha @@ -1 +1 @@ -69641913e358f4c3128a93dadf5824c7929afeaf8df6c5b9a6cc645c0f43d0f5 +01cfc201a7c71e2555e5828c67aeaa29f295bf4ebfb4e182a3c606e2a4812857 diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index ccf1e466..f6214cfc 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -618,16 +618,16 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& // guest clock a fixed cost per instruction, and scale N charges 1/N of // it, so N times as much game code fits between two VBlanks while // VBlank, timers, CD, SPU and DMA keep hardware time. 1 = faithful; - // clamped to 1..64. guest_cycle_scale_gated: the scale applies only + // values outside 1..64 are rejected. guest_cycle_scale_gated: the scale applies only // while the mod gate is open (psx_mod_set_guest_cycle_scale_gate); see // guest_cycle_scale_gate below for the declarative RAM gate. if (cfg.contains("timing")) { const toml::value& tm = toml::find(cfg, "timing"); if (tm.contains("guest_cycle_scale")) { - long long n = toml::find(tm, "guest_cycle_scale"); - if (n < 1) n = 1; - if (n > 64) n = 64; - rt.guest_cycle_scale = (int)n; + const long long n = toml::find(tm, "guest_cycle_scale"); + if (n < 1 || n > 64) + throw std::runtime_error("[timing] guest_cycle_scale must be 1..64"); + rt.guest_cycle_scale = static_cast(n); } if (tm.contains("guest_cycle_scale_gated")) rt.guest_cycle_scale_gated = toml::find(tm, "guest_cycle_scale_gated"); @@ -640,12 +640,22 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path& else preds.push_back(g); if (preds.size() > 8) throw std::runtime_error("[timing] guest_cycle_scale_gate: at most 8 predicates"); + // Range-check the signed TOML integer before narrowing, so an + // oversized or negative value cannot wrap into a valid one. + auto u32_field = [](const toml::value& pv, const char* key) -> uint32_t { + const long long v = toml::find(pv, key); + if (v < 0 || v > 0xFFFFFFFFLL) + throw std::runtime_error( + std::string("[timing] guest_cycle_scale_gate: ") + key + + " must be 0..0xFFFFFFFF"); + return static_cast(v); + }; for (const toml::value& pv : preds) { RuntimeConfig::GuestCycleScaleGatePred p; - p.addr = (uint32_t)toml::find(pv, "addr"); - p.value = (uint32_t)toml::find(pv, "value"); - if (pv.contains("size")) p.size = (uint32_t)toml::find(pv, "size"); - if (pv.contains("mask")) p.mask = (uint32_t)toml::find(pv, "mask"); + p.addr = u32_field(pv, "addr"); + p.value = u32_field(pv, "value"); + if (pv.contains("size")) p.size = u32_field(pv, "size"); + if (pv.contains("mask")) p.mask = u32_field(pv, "mask"); const uint32_t phys = p.addr & 0x1FFFFFFFu; if ((p.size != 1 && p.size != 2 && p.size != 4) || (phys & (p.size - 1u)) != 0 || phys >= 0x00800000u) diff --git a/recompiler/tests/video_enhancement_settings_test.cpp b/recompiler/tests/video_enhancement_settings_test.cpp index 160ac3f1..21f7d88e 100644 --- a/recompiler/tests/video_enhancement_settings_test.cpp +++ b/recompiler/tests/video_enhancement_settings_test.cpp @@ -499,6 +499,29 @@ static void test_timing_gate() { try { (void)PSXRecompV4::load_game_config(p); } catch (const std::exception&) { rejected = true; } check(rejected, "timing: non-RAM gate address rejected"); fs::remove(p); + + /* Oversized and negative values must be rejected, not wrapped to 32 bits. */ + const struct { const char* body; const char* what; } bad[] = { + { "guest_cycle_scale_gate = { addr = 0x100010000, value = 1 }", "oversized addr" }, + { "guest_cycle_scale_gate = { addr = -4, value = 1 }", "negative addr" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = 0x100000001 }", "oversized value" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = -1 }", "negative value" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, size = 0x100000004 }", "oversized size" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, size = -4 }", "negative size" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, mask = 0x1FFFFFFFF }", "oversized mask" }, + { "guest_cycle_scale_gate = { addr = 0x80010000, value = 1, mask = -1 }", "negative mask" }, + { "guest_cycle_scale = 0", "scale 0" }, + { "guest_cycle_scale = 65", "scale 65" }, + { "guest_cycle_scale = -8", "negative scale" }, + { "guest_cycle_scale = 0x100000008", "oversized scale" }, + }; + for (const auto& b : bad) { + p = write_game_toml("ves_timing_range.toml", std::string("[timing]\n") + b.body + "\n"); + rejected = false; + try { (void)PSXRecompV4::load_game_config(p); } catch (const std::exception&) { rejected = true; } + check(rejected, (std::string("timing: rejected ") + b.what).c_str()); + fs::remove(p); + } } int main() {