From 276d56ca9b2f69cdc71f2d80653922e2cca253ff Mon Sep 17 00:00:00 2001
From: James Rowe <jroweboy@gmail.com>
Date: Sun, 15 Dec 2019 22:04:33 -0700
Subject: [PATCH 1/2] Add CPU Clock Frequency slider

This slider affects the number of cycles that the guest cpu emulation
reports that have passed since the last time slice. This option scales
the result returned by a percentage that the user selects. In some games
underclocking the CPU can give a major speedup. Exposing this as an
option will give users something to toy with for performance, while also
potentially enhancing games that experience lag on the real console
---
 src/citra/config.cpp                          |   2 +
 src/citra/default_ini.h                       |   6 +
 src/citra_qt/configuration/config.cpp         |   4 +
 .../configuration/configure_general.ui        |  16 +--
 .../configuration/configure_system.cpp        | 133 ++++++++++--------
 .../configuration/configure_system.ui         |  74 +++++++++-
 src/core/core.cpp                             |   2 +-
 src/core/core_timing.cpp                      |  14 +-
 src/core/core_timing.h                        |  19 ++-
 src/core/settings.cpp                         |   1 +
 src/core/settings.h                           |   1 +
 src/tests/core/arm/arm_test_common.cpp        |   2 +-
 src/tests/core/core_timing.cpp                |   8 +-
 src/tests/core/hle/kernel/hle_ipc.cpp         |   4 +-
 src/tests/core/memory/memory.cpp              |   2 +-
 15 files changed, 204 insertions(+), 84 deletions(-)

diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index b2c878ddf..daaacd6d4 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -104,6 +104,8 @@ void Config::ReadValues() {
 
     // Core
     Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
+    Settings::values.cpu_clock_percentage =
+        sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
 
     // Renderer
     Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 9c441e354..0a0be12f3 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -91,6 +91,12 @@ udp_pad_index=
 # 0: Interpreter (slow), 1 (default): JIT (fast)
 use_cpu_jit =
 
+# Change the Clock Frequency of the emulated 3DS CPU.
+# Underclocking can increase the performance of the game at the risk of freezing.
+# Overclocking may fix lag that happens on console, but also comes with the risk of freezing.
+# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100
+cpu_clock_percentage =
+
 [Renderer]
 # Whether to render using GLES or OpenGL
 # 0 (default): OpenGL, 1: GLES
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 82274bff0..368be2b31 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -253,6 +253,8 @@ void Config::ReadCoreValues() {
     qt_config->beginGroup(QStringLiteral("Core"));
 
     Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
+    Settings::values.cpu_clock_percentage =
+        ReadSetting(QStringLiteral("cpu_clock_percentage"), 100).toInt();
 
     qt_config->endGroup();
 }
@@ -730,6 +732,8 @@ void Config::SaveCoreValues() {
     qt_config->beginGroup(QStringLiteral("Core"));
 
     WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
+    WriteSetting(QStringLiteral("cpu_clock_percentage"), Settings::values.cpu_clock_percentage,
+                 100);
 
     qt_config->endGroup();
 }
diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui
index 2a461a05d..181455a64 100644
--- a/src/citra_qt/configuration/configure_general.ui
+++ b/src/citra_qt/configuration/configure_general.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>345</width>
-    <height>357</height>
+    <height>358</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -68,6 +68,13 @@
         <string>Emulation</string>
        </property>
        <layout class="QGridLayout" name="gridLayout">
+        <item row="1" column="0">
+         <widget class="QCheckBox" name="toggle_frame_limit">
+          <property name="text">
+           <string>Limit Speed Percent</string>
+          </property>
+         </widget>
+        </item>
         <item row="0" column="0">
          <widget class="QLabel" name="label">
           <property name="text">
@@ -119,13 +126,6 @@
           </item>
          </widget>
         </item>
-        <item row="1" column="0">
-         <widget class="QCheckBox" name="toggle_frame_limit">
-          <property name="text">
-           <string>Limit Speed Percent</string>
-          </property>
-         </widget>
-        </item>
         <item row="1" column="1">
          <widget class="QSpinBox" name="frame_limit">
           <property name="suffix">
diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp
index 775bc1eeb..139d92f7b 100644
--- a/src/citra_qt/configuration/configure_system.cpp
+++ b/src/citra_qt/configuration/configure_system.cpp
@@ -217,6 +217,17 @@ static const std::array<const char*, 187> country_names = {
     QT_TRANSLATE_NOOP("ConfigureSystem", "Bermuda"), // 180-186
 };
 
+// The QSlider doesn't have an easy way to set a custom step amount,
+// so we can just convert from the sliders range (0 - 79) to the expected
+// settings range (5 - 400) with simple math.
+static constexpr int SliderToSettings(int value) {
+    return 5 * value + 5;
+}
+
+static constexpr int SettingsToSlider(int value) {
+    return (value - 5) / 5;
+}
+
 ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
     ui->setupUi(this);
     connect(ui->combo_birthmonth,
@@ -233,6 +244,10 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
         }
     }
 
+    connect(ui->slider_clock_speed, &QSlider::valueChanged, [&](int value) {
+        ui->clock_display_label->setText(QStringLiteral("%1%").arg(SliderToSettings(value)));
+    });
+
     ConfigureTime();
 }
 
@@ -258,6 +273,10 @@ void ConfigureSystem::SetConfiguration() {
 
         ui->label_disable_info->hide();
     }
+
+    ui->slider_clock_speed->setValue(SettingsToSlider(Settings::values.cpu_clock_percentage));
+    ui->clock_display_label->setText(
+        QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage));
 }
 
 void ConfigureSystem::ReadSystemSettings() {
@@ -299,65 +318,65 @@ void ConfigureSystem::ReadSystemSettings() {
 }
 
 void ConfigureSystem::ApplyConfiguration() {
-    if (!enabled) {
-        return;
+    if (enabled) {
+        bool modified = false;
+
+        // apply username
+        // TODO(wwylele): Use this when we move to Qt 5.5
+        // std::u16string new_username = ui->edit_username->text().toStdU16String();
+        std::u16string new_username(
+            reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
+        if (new_username != username) {
+            cfg->SetUsername(new_username);
+            modified = true;
+        }
+
+        // apply birthday
+        int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
+        int new_birthday = ui->combo_birthday->currentIndex() + 1;
+        if (birthmonth != new_birthmonth || birthday != new_birthday) {
+            cfg->SetBirthday(new_birthmonth, new_birthday);
+            modified = true;
+        }
+
+        // apply language
+        int new_language = ui->combo_language->currentIndex();
+        if (language_index != new_language) {
+            cfg->SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
+            modified = true;
+        }
+
+        // apply sound
+        int new_sound = ui->combo_sound->currentIndex();
+        if (sound_index != new_sound) {
+            cfg->SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
+            modified = true;
+        }
+
+        // apply country
+        u8 new_country = static_cast<u8>(ui->combo_country->currentData().toInt());
+        if (country_code != new_country) {
+            cfg->SetCountryCode(new_country);
+            modified = true;
+        }
+
+        // apply play coin
+        u16 new_play_coin = static_cast<u16>(ui->spinBox_play_coins->value());
+        if (play_coin != new_play_coin) {
+            Service::PTM::Module::SetPlayCoins(new_play_coin);
+        }
+
+        // update the config savegame if any item is modified.
+        if (modified) {
+            cfg->UpdateConfigNANDSavegame();
+        }
+
+        Settings::values.init_clock =
+            static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
+        Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
     }
 
-    bool modified = false;
-
-    // apply username
-    // TODO(wwylele): Use this when we move to Qt 5.5
-    // std::u16string new_username = ui->edit_username->text().toStdU16String();
-    std::u16string new_username(
-        reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
-    if (new_username != username) {
-        cfg->SetUsername(new_username);
-        modified = true;
-    }
-
-    // apply birthday
-    int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
-    int new_birthday = ui->combo_birthday->currentIndex() + 1;
-    if (birthmonth != new_birthmonth || birthday != new_birthday) {
-        cfg->SetBirthday(new_birthmonth, new_birthday);
-        modified = true;
-    }
-
-    // apply language
-    int new_language = ui->combo_language->currentIndex();
-    if (language_index != new_language) {
-        cfg->SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
-        modified = true;
-    }
-
-    // apply sound
-    int new_sound = ui->combo_sound->currentIndex();
-    if (sound_index != new_sound) {
-        cfg->SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
-        modified = true;
-    }
-
-    // apply country
-    u8 new_country = static_cast<u8>(ui->combo_country->currentData().toInt());
-    if (country_code != new_country) {
-        cfg->SetCountryCode(new_country);
-        modified = true;
-    }
-
-    // apply play coin
-    u16 new_play_coin = static_cast<u16>(ui->spinBox_play_coins->value());
-    if (play_coin != new_play_coin) {
-        Service::PTM::Module::SetPlayCoins(new_play_coin);
-    }
-
-    // update the config savegame if any item is modified.
-    if (modified) {
-        cfg->UpdateConfigNANDSavegame();
-    }
-
-    Settings::values.init_clock =
-        static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
-    Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
+    Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
     Settings::Apply();
 }
 
diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui
index 51ad7c8ca..554993990 100644
--- a/src/citra_qt/configuration/configure_system.ui
+++ b/src/citra_qt/configuration/configure_system.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>360</width>
-    <height>377</height>
+    <width>471</width>
+    <height>555</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -228,8 +228,7 @@
          </widget>
         </item>
         <item row="4" column="1">
-         <widget class="QComboBox" name="combo_country">
-         </widget>
+         <widget class="QComboBox" name="combo_country"/>
         </item>
         <item row="5" column="0">
          <widget class="QLabel" name="label_init_clock">
@@ -306,6 +305,63 @@
        </layout>
       </widget>
      </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Advanced</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <item row="0" column="0">
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>CPU Clock Speed</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="2">
+         <widget class="QSlider" name="slider_clock_speed">
+          <property name="toolTip">
+           <string>&lt;html&gt;&lt;body&gt;Changes the emulated CPU clock frequency.&lt;br&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/body&gt;&lt;/html&gt;</string>
+          </property>
+          <property name="minimum">
+           <number>0</number>
+          </property>
+          <property name="maximum">
+           <number>79</number>
+          </property>
+          <property name="singleStep">
+           <number>5</number>
+          </property>
+          <property name="pageStep">
+           <number>15</number>
+          </property>
+          <property name="value">
+           <number>25</number>
+          </property>
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="tickPosition">
+           <enum>QSlider::TicksBelow</enum>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QLabel" name="clock_display_label">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_2"/>
+     </item>
      <item>
       <widget class="QLabel" name="label_disable_info">
        <property name="text">
@@ -316,6 +372,16 @@
        </property>
       </widget>
      </item>
+     <item>
+      <widget class="QLabel" name="label_cpu_clock_info">
+       <property name="text">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;CPU Clock Speed Information&lt;br/&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br/&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::RichText</enum>
+       </property>
+      </widget>
+     </item>
      <item>
       <spacer name="verticalSpacer">
        <property name="orientation">
diff --git a/src/core/core.cpp b/src/core/core.cpp
index cd1799e42..7418adf83 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -256,7 +256,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
 
     memory = std::make_unique<Memory::MemorySystem>();
 
-    timing = std::make_unique<Timing>(num_cores);
+    timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage);
 
     kernel = std::make_unique<Kernel::KernelSystem>(
         *memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores);
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 8966bc55b..5dbd5f74c 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -20,14 +20,20 @@ bool Timing::Event::operator<(const Timing::Event& right) const {
     return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order);
 }
 
-Timing::Timing(std::size_t num_cores) {
+Timing::Timing(std::size_t num_cores, u32 cpu_clock_percentage) {
     timers.resize(num_cores);
     for (std::size_t i = 0; i < num_cores; ++i) {
-        timers[i] = std::make_shared<Timer>();
+        timers[i] = std::make_shared<Timer>(100.0 / cpu_clock_percentage);
     }
     current_timer = timers[0];
 }
 
+void Timing::UpdateClockSpeed(u32 cpu_clock_percentage) {
+    for (auto& timer : timers) {
+        timer->cpu_clock_scale = 100.0 / cpu_clock_percentage;
+    }
+}
+
 TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
     // check for existing type with same name.
     // we want event type names to remain unique so that we can use them for serialization.
@@ -117,6 +123,8 @@ std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
     return timers[cpu_id];
 }
 
+Timing::Timer::Timer(double cpu_clock_scale_) : cpu_clock_scale(cpu_clock_scale_) {}
+
 Timing::Timer::~Timer() {
     MoveEvents();
 }
@@ -130,7 +138,7 @@ u64 Timing::Timer::GetTicks() const {
 }
 
 void Timing::Timer::AddTicks(u64 ticks) {
-    downcount -= ticks;
+    downcount -= static_cast<u64>(ticks * cpu_clock_scale);
 }
 
 u64 Timing::Timer::GetIdleTicks() const {
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 30c1106bb..929f39865 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -148,6 +148,7 @@ public:
 
     class Timer {
     public:
+        Timer(double cpu_clock_scale);
         ~Timer();
 
         s64 GetMaxSliceLength() const;
@@ -190,10 +191,13 @@ public:
         s64 slice_length = MAX_SLICE_LENGTH;
         s64 downcount = MAX_SLICE_LENGTH;
         s64 executed_ticks = 0;
-        u64 idled_cycles;
+        u64 idled_cycles = 0;
+        // Stores a scaling for the internal clockspeed. Changing this number results in
+        // under/overclocking the guest cpu
+        double cpu_clock_scale = 1.0;
     };
 
-    explicit Timing(std::size_t num_cores);
+    explicit Timing(std::size_t num_cores, u32 cpu_clock_percentage);
 
     ~Timing(){};
 
@@ -220,6 +224,11 @@ public:
         global_timer += ticks;
     }
 
+    /**
+     * Updates the value of the cpu clock scaling to the new percentage.
+     */
+    void UpdateClockSpeed(u32 cpu_clock_percentage);
+
     std::chrono::microseconds GetGlobalTimeUs() const;
 
     std::shared_ptr<Timer> GetTimer(std::size_t cpu_id);
@@ -229,10 +238,14 @@ private:
 
     // unordered_map stores each element separately as a linked list node so pointers to
     // elements remain stable regardless of rehashes/resizing.
-    std::unordered_map<std::string, TimingEventType> event_types;
+    std::unordered_map<std::string, TimingEventType> event_types = {};
 
     std::vector<std::shared_ptr<Timer>> timers;
     std::shared_ptr<Timer> current_timer;
+
+    // Stores a scaling for the internal clockspeed. Changing this number results in
+    // under/overclocking the guest cpu
+    double cpu_clock_scale = 1.0;
 };
 
 } // namespace Core
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index ec3a36115..90bf101de 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -40,6 +40,7 @@ void Apply() {
 
     auto& system = Core::System::GetInstance();
     if (system.IsPoweredOn()) {
+        system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage);
         Core::DSP().SetSink(values.sink_id, values.audio_device_id);
         Core::DSP().EnableStretching(values.enable_audio_stretching);
 
diff --git a/src/core/settings.h b/src/core/settings.h
index 78b11912c..83ce19223 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -128,6 +128,7 @@ struct Values {
 
     // Core
     bool use_cpu_jit;
+    int cpu_clock_percentage;
 
     // Data Storage
     bool use_virtual_sd;
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 0957c7c97..44e856405 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -15,7 +15,7 @@ static Memory::PageTable* page_table = nullptr;
 TestEnvironment::TestEnvironment(bool mutable_memory_)
     : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
 
-    timing = std::make_unique<Core::Timing>(1);
+    timing = std::make_unique<Core::Timing>(1, 100);
     memory = std::make_unique<Memory::MemorySystem>();
     kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0, 1);
 
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index 850f13bc5..8b34ba9d7 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -43,7 +43,7 @@ static void AdvanceAndCheck(Core::Timing& timing, u32 idx, int downcount, int ex
 }
 
 TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
 
     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@@ -90,7 +90,7 @@ void FifoCallback(u64 userdata, s64 cycles_late) {
 TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
     using namespace SharedSlotTest;
 
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
 
     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", FifoCallback<0>);
     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", FifoCallback<1>);
@@ -118,7 +118,7 @@ TEST_CASE("CoreTiming[SharedSlot]", "[core]") {
 }
 
 TEST_CASE("CoreTiming[PredictableLateness]", "[core]") {
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
 
     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
@@ -149,7 +149,7 @@ static void RescheduleCallback(Core::Timing& timing, u64 userdata, s64 cycles_la
 TEST_CASE("CoreTiming[ChainScheduling]", "[core]") {
     using namespace ChainSchedulingTest;
 
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
 
     Core::TimingEventType* cb_a = timing.RegisterEvent("callbackA", CallbackTemplate<0>);
     Core::TimingEventType* cb_b = timing.RegisterEvent("callbackB", CallbackTemplate<1>);
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
index 59026afd6..52ff3b117 100644
--- a/src/tests/core/hle/kernel/hle_ipc.cpp
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -21,7 +21,7 @@ static std::shared_ptr<Object> MakeObject(Kernel::KernelSystem& kernel) {
 }
 
 TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
     Memory::MemorySystem memory;
     Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
     auto [server, client] = kernel.CreateSessionPair();
@@ -233,7 +233,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
 }
 
 TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
     Memory::MemorySystem memory;
     Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
     auto [server, client] = kernel.CreateSessionPair();
diff --git a/src/tests/core/memory/memory.cpp b/src/tests/core/memory/memory.cpp
index 2e7c71434..b3eed7a9c 100644
--- a/src/tests/core/memory/memory.cpp
+++ b/src/tests/core/memory/memory.cpp
@@ -11,7 +11,7 @@
 #include "core/memory.h"
 
 TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") {
-    Core::Timing timing(1);
+    Core::Timing timing(1, 100);
     Memory::MemorySystem memory;
     Kernel::KernelSystem kernel(memory, timing, [] {}, 0, 1);
     SECTION("these regions should not be mapped on an empty process") {

From 3edc4a3055a6e056f2ebc02c8f57262dd9f762b7 Mon Sep 17 00:00:00 2001
From: Pengfei Zhu <zhupf321@gmail.com>
Date: Sat, 28 Mar 2020 20:26:54 +0800
Subject: [PATCH 2/2] service/ldr_ro: Fix CRO loading when the buffer contained
 multiple VM areas (#5125)

* vm_manager: Handle multiple areas in ChangeMemoryState

It is possible that a few areas have the same permisson and state, but with different backing pointers. Currently, this function assumes that only one continous area is found, but this is not always the case.

* service/ldr_ro: Handle multiple areas in VerifyBufferState

It is possible that the buffer passed from the game is made up of multiple areas with the same permisson and state but different backing pointers. Change the check to allow that.
---
 src/core/hle/kernel/vm_manager.cpp     | 15 +++++++++------
 src/core/hle/service/ldr_ro/ldr_ro.cpp | 15 +++++++++++----
 2 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 3280f99e9..ba2e2bd1a 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -151,13 +151,16 @@ ResultCode VMManager::ChangeMemoryState(VAddr target, u32 size, MemoryState expe
     }
 
     CASCADE_RESULT(auto vma, CarveVMARange(target, size));
-    ASSERT(vma->second.size == size);
 
-    vma->second.permissions = new_perms;
-    vma->second.meminfo_state = new_state;
-    UpdatePageTableForVMA(vma->second);
-
-    MergeAdjacent(vma);
+    const VMAIter end = vma_map.end();
+    // The comparison against the end of the range must be done using addresses since VMAs can be
+    // merged during this process, causing invalidation of the iterators.
+    while (vma != end && vma->second.base < target_end) {
+        vma->second.permissions = new_perms;
+        vma->second.meminfo_state = new_state;
+        UpdatePageTableForVMA(vma->second);
+        vma = std::next(MergeAdjacent(vma));
+    }
 
     return RESULT_SUCCESS;
 }
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp
index 274d36ed5..a435d5e58 100644
--- a/src/core/hle/service/ldr_ro/ldr_ro.cpp
+++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp
@@ -41,10 +41,17 @@ static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D
 
 static bool VerifyBufferState(Kernel::Process& process, VAddr buffer_ptr, u32 size) {
     auto vma = process.vm_manager.FindVMA(buffer_ptr);
-    return vma != process.vm_manager.vma_map.end() &&
-           vma->second.base + vma->second.size >= buffer_ptr + size &&
-           vma->second.permissions == Kernel::VMAPermission::ReadWrite &&
-           vma->second.meminfo_state == Kernel::MemoryState::Private;
+    while (vma != process.vm_manager.vma_map.end()) {
+        if (vma->second.permissions != Kernel::VMAPermission::ReadWrite ||
+            vma->second.meminfo_state != Kernel::MemoryState::Private) {
+            return false;
+        }
+        if (vma->second.base + vma->second.size >= buffer_ptr + size) {
+            return true;
+        }
+        vma = std::next(vma);
+    }
+    return false;
 }
 
 void RO::Initialize(Kernel::HLERequestContext& ctx) {