summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Mansell <phiren@gmail.com>2026-07-12 11:34:44 +1200
committerGitHub <noreply@github.com>2026-07-12 11:34:44 +1200
commitfc9fd6952a62c4fe48acc00333ce360cb47b57c7 (patch)
tree98c900e645f4e0252968f2812a77fb5f1ece41a1
parent6de526c684ee6dd8b6fc3447dae858ae480ccbb5 (diff)
parentf9ef6c2151d48ef9b5dbe9ee1c2278e5d60b5ed9 (diff)
Merge pull request #14665 from cristian64/simulated_memory_size
Core: Adjust emulated memory size automatically.
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml2
-rw-r--r--Source/Core/Core/ConfigManager.cpp25
-rw-r--r--Source/Core/Core/ConfigManager.h6
-rw-r--r--Source/Core/Core/DolphinAnalytics.cpp3
-rw-r--r--Source/Core/Core/FifoPlayer/FifoDataFile.cpp3
-rw-r--r--Source/Core/Core/HW/Memmap.cpp13
-rw-r--r--Source/Core/Core/HW/Memmap.h2
-rw-r--r--Source/Core/Core/IOS/IOS.cpp5
-rw-r--r--Source/Core/Core/System.cpp1
-rw-r--r--Source/Core/Core/System.h4
-rw-r--r--Source/Core/DiscIO/Volume.h1
-rw-r--r--Source/Core/DiscIO/VolumeDisc.cpp8
-rw-r--r--Source/Core/DiscIO/VolumeDisc.h1
-rw-r--r--Source/Core/DiscIO/VolumeWad.h1
-rw-r--r--Source/Core/DolphinQt/Settings/AdvancedPane.cpp7
-rw-r--r--Source/UnitTests/Core/PowerPC/PageTableHostMappingTest.cpp5
16 files changed, 70 insertions, 17 deletions
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index 07c5e7ebc0..9d97169d95 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -400,7 +400,7 @@
<string name="overclock_title_description">Adjusts the emulated CPU\'s clock rate if \"Override Emulated CPU Clock Speed\" is enabled.</string>
<string name="memory_override">Memory Override</string>
<string name="enable_memory_size_override">Enable Emulated Memory Size Override</string>
- <string name="enable_memory_size_override_description">Adjusts the amount of RAM in the emulated console.\n\nWARNING: Enabling this will completely break many games. Only a small number of games can benefit from this.</string>
+ <string name="enable_memory_size_override_description">Sets the amount of RAM in the emulated console to the values provided.\n\nWARNING: Enabling this will completely break many games. By default, Dolphin determines what value is required based on the game information.</string>
<string name="main_mem1_size">MEM1 Size</string>
<string name="main_mem2_size">MEM2 Size</string>
<string name="gpu_options">GPU Options</string>
diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp
index 8404cfff07..460ca2e048 100644
--- a/Source/Core/Core/ConfigManager.cpp
+++ b/Source/Core/Core/ConfigManager.cpp
@@ -154,27 +154,35 @@ u16 SConfig::GetRevision() const
return m_revision;
}
+u32 SConfig::GetSimulatedMemorySize() const
+{
+ std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
+ return m_simulated_memory_size;
+}
+
void SConfig::ResetRunningGameMetadata()
{
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
- SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown);
+ SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown, 0);
}
void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume,
const DiscIO::Partition& partition)
{
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
+
if (partition == volume.GetGamePartition())
{
SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(),
volume.GetTitleID().value_or(0), volume.GetRevision().value_or(0),
- volume.GetRegion());
+ volume.GetRegion(), volume.GetSimulatedMemorySize());
}
else
{
SetRunningGameMetadata(volume.GetGameID(partition), volume.GetGameTDBID(partition),
volume.GetTitleID(partition).value_or(0),
- volume.GetRevision(partition).value_or(0), volume.GetRegion());
+ volume.GetRevision(partition).value_or(0), volume.GetRegion(),
+ volume.GetSimulatedMemorySize());
}
}
@@ -192,26 +200,29 @@ void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Plat
{
// If not launching a disc game, just read everything from the TMD.
SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(),
- tmd.GetRegion());
+ tmd.GetRegion(), 0);
}
}
void SConfig::SetRunningGameMetadata(const std::string& game_id)
{
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
- SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown);
+ SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown, 0);
}
void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id,
- u64 title_id, u16 revision, DiscIO::Region region)
+ u64 title_id, u16 revision, DiscIO::Region region,
+ u32 simulated_memory_size)
{
std::lock_guard<std::recursive_mutex> lock(m_metadata_lock);
const bool was_changed = m_game_id != game_id || m_gametdb_id != gametdb_id ||
- m_title_id != title_id || m_revision != revision;
+ m_title_id != title_id || m_revision != revision ||
+ m_simulated_memory_size != simulated_memory_size;
m_game_id = game_id;
m_gametdb_id = gametdb_id;
m_title_id = title_id;
m_revision = revision;
+ m_simulated_memory_size = simulated_memory_size;
if (game_id.length() == 6)
{
diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h
index 39dbc6d39a..145b002418 100644
--- a/Source/Core/Core/ConfigManager.h
+++ b/Source/Core/Core/ConfigManager.h
@@ -65,6 +65,7 @@ struct SConfig
const std::string GetTitleDescription() const;
u64 GetTitleID() const;
u16 GetRevision() const;
+ u32 GetSimulatedMemorySize() const;
void ResetRunningGameMetadata();
void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition);
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform);
@@ -119,7 +120,8 @@ private:
static void ReloadTextures(Core::System& system);
void SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id,
- u64 title_id, u16 revision, DiscIO::Region region);
+ u64 title_id, u16 revision, DiscIO::Region region,
+ u32 simulated_memory_size);
static SConfig* m_Instance;
mutable std::recursive_mutex m_metadata_lock;
@@ -130,4 +132,6 @@ private:
std::string m_title_description;
u64 m_title_id;
u16 m_revision;
+ // Memory size requested by game's header.
+ u32 m_simulated_memory_size;
};
diff --git a/Source/Core/Core/DolphinAnalytics.cpp b/Source/Core/Core/DolphinAnalytics.cpp
index 1fff6bc5c5..8ae3bf6bcf 100644
--- a/Source/Core/Core/DolphinAnalytics.cpp
+++ b/Source/Core/Core/DolphinAnalytics.cpp
@@ -354,6 +354,9 @@ void DolphinAnalytics::MakePerGameBuilder()
// Unique id bound to the gameid.
builder.AddData("id", MakeUniqueId(SConfig::GetInstance().GetGameID()));
+ // Other game-specific fields.
+ builder.AddData("simulated-memory-size", SConfig::GetInstance().GetSimulatedMemorySize());
+
// Configuration.
builder.AddData("cfg-dsp-hle", Config::Get(Config::MAIN_DSP_HLE));
builder.AddData("cfg-dsp-jit", Config::Get(Config::MAIN_DSP_JIT));
diff --git a/Source/Core/Core/FifoPlayer/FifoDataFile.cpp b/Source/Core/Core/FifoPlayer/FifoDataFile.cpp
index f795d5f08a..0e1e44aef9 100644
--- a/Source/Core/Core/FifoPlayer/FifoDataFile.cpp
+++ b/Source/Core/Core/FifoPlayer/FifoDataFile.cpp
@@ -140,7 +140,8 @@ bool FifoDataFile::Save(const std::string& filename)
header.fileId = FILE_ID;
header.file_version = VERSION_NUMBER;
// Maintain backwards compatibility so long as the RAM sizes aren't overridden.
- if (Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE))
+ if (Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE) ||
+ SConfig::GetInstance().GetSimulatedMemorySize() > Memory::MEM1_SIZE_RETAIL)
header.min_loader_version = MIN_LOADER_VERSION_FOR_RAM_OVERRIDE;
else
header.min_loader_version = MIN_LOADER_VERSION;
diff --git a/Source/Core/Core/HW/Memmap.cpp b/Source/Core/Core/HW/Memmap.cpp
index e6d8fd56a5..054596e3d8 100644
--- a/Source/Core/Core/HW/Memmap.cpp
+++ b/Source/Core/Core/HW/Memmap.cpp
@@ -25,6 +25,7 @@
#include "Common/MsgHandler.h"
#include "Common/Swap.h"
#include "Core/Config/MainSettings.h"
+#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/AudioInterface.h"
#include "Core/HW/DSP.h"
@@ -87,10 +88,18 @@ void MemoryManager::InitMMIO(Core::System& system)
void MemoryManager::Init()
{
- const auto get_mem1_size = [] {
+ const auto get_mem1_size = [this] {
if (Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE))
return Config::Get(Config::MAIN_MEM1_SIZE);
- return Memory::MEM1_SIZE_RETAIL;
+ // The simulated memory size in the game's header was originally used to ask the Apploader to
+ // simulate a smaller memory size than that in development systems. That is, it could make the
+ // 48 MiB devkit act like a 24 MiB retail console during testing, or any other config.
+ //
+ // Dolphin is reusing this as a signal to automatically adjust the emulated memory size. The
+ // upper bound is clamped to 64 MiB (maximum supported by Flipper's memory controller),
+ // reflecting Apploader behaviour of clamping the simulated size to physical memory size. It is
+ // never desired to emulate less than a retail console, so the lower bound is clapped to 24 MiB.
+ return std::clamp(m_system.GetSimulatedMemorySize(), MEM1_SIZE_RETAIL, MEM1_SIZE_MAX);
};
const auto get_mem2_size = [] {
if (Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE))
diff --git a/Source/Core/Core/HW/Memmap.h b/Source/Core/Core/HW/Memmap.h
index baa910cc70..e9253272f6 100644
--- a/Source/Core/Core/HW/Memmap.h
+++ b/Source/Core/Core/HW/Memmap.h
@@ -34,8 +34,10 @@ constexpr u32 MEM1_BASE_ADDR = 0x80000000U;
constexpr u32 MEM2_BASE_ADDR = 0x90000000U;
constexpr u32 MEM1_SIZE_RETAIL = 0x01800000U;
constexpr u32 MEM1_SIZE_GDEV = 0x04000000U;
+constexpr u32 MEM1_SIZE_MAX = 0x04000000U;
constexpr u32 MEM2_SIZE_RETAIL = 0x04000000U;
constexpr u32 MEM2_SIZE_NDEV = 0x08000000U;
+constexpr u32 MEM2_SIZE_MAX = 0x08000000U;
struct PhysicalMemoryRegion
{
diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp
index d3986775b6..49c0b2bbd3 100644
--- a/Source/Core/Core/IOS/IOS.cpp
+++ b/Source/Core/Core/IOS/IOS.cpp
@@ -221,8 +221,11 @@ static void ReleasePPCAncast(Core::System& system)
void RAMOverrideForIOSMemoryValues(Memory::MemoryManager& memory, MemorySetupType setup_type)
{
// Don't touch anything if the feature isn't enabled.
- if (!Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE))
+ if (!Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE) ||
+ SConfig::GetInstance().GetSimulatedMemorySize() <= Memory::MEM1_SIZE_RETAIL)
+ {
return;
+ }
// Some unstated constants that can be inferred.
const u32 ipc_buffer_size =
diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp
index 686d1a3264..12da7dcaea 100644
--- a/Source/Core/Core/System.cpp
+++ b/Source/Core/Core/System.cpp
@@ -119,6 +119,7 @@ void System::Initialize()
m_separate_cpu_and_gpu_threads = Config::Get(Config::MAIN_CPU_THREAD);
m_mmu_enabled = Config::Get(Config::MAIN_MMU);
m_pause_on_panic_enabled = Config::Get(Config::MAIN_PAUSE_ON_PANIC);
+ m_simulated_memory_size = SConfig::GetInstance().GetSimulatedMemorySize();
}
SoundStream* System::GetSoundStream() const
diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h
index a170c608ee..8524003400 100644
--- a/Source/Core/Core/System.h
+++ b/Source/Core/Core/System.h
@@ -5,6 +5,8 @@
#include <memory>
+#include "Common/CommonTypes.h"
+
class GeometryShaderManager;
class Interpreter;
class JitInterface;
@@ -147,6 +149,7 @@ public:
bool IsTriforce() const { return m_is_triforce; }
bool IsWii() const { return m_is_wii; }
bool IsBranchWatchIgnoreApploader() { return m_branch_watch_ignore_apploader; }
+ u32 GetSimulatedMemorySize() const { return m_simulated_memory_size; }
void SetIsMIOS(bool is_mios) { m_is_mios = is_mios; }
void SetIsTriforce(bool is_triforce) { m_is_triforce = is_triforce; }
@@ -216,5 +219,6 @@ private:
bool m_is_triforce = false;
bool m_is_wii = false;
bool m_branch_watch_ignore_apploader = false;
+ u32 m_simulated_memory_size{0};
};
} // namespace Core
diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h
index 7dfabb3e97..61df7e032f 100644
--- a/Source/Core/DiscIO/Volume.h
+++ b/Source/Core/DiscIO/Volume.h
@@ -126,6 +126,7 @@ public:
}
virtual Region GetRegion() const = 0;
virtual Country GetCountry(const Partition& partition = PARTITION_NONE) const = 0;
+ virtual u32 GetSimulatedMemorySize() const = 0;
virtual BlobType GetBlobType() const = 0;
// Size of virtual disc (may be inaccurate depending on the blob type)
virtual u64 GetDataSize() const = 0;
diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp
index e842540376..4dd16c4cef 100644
--- a/Source/Core/DiscIO/VolumeDisc.cpp
+++ b/Source/Core/DiscIO/VolumeDisc.cpp
@@ -179,6 +179,14 @@ std::optional<u8> VolumeDisc::GetDiscNumber(const Partition& partition) const
return ReadSwapped<u8>(6, partition);
}
+u32 VolumeDisc::GetSimulatedMemorySize() const
+{
+ // Simulated memory size is a 32-bit unsigned integer in the `bi2.bin` file at offset `0x04`.
+ // Apploader reads this and "simulates" a smaller memory size. Originally useful for simulating
+ // 24 MiB of memory on devkits with 48 MiB.
+ return ReadSwapped<u32>(BI2_ADDRESS + 0x04, GetGamePartition()).value_or(0);
+}
+
bool VolumeDisc::IsNKit() const
{
constexpr u32 NKIT_MAGIC = 0x4E4B4954; // "NKIT"
diff --git a/Source/Core/DiscIO/VolumeDisc.h b/Source/Core/DiscIO/VolumeDisc.h
index f630522e68..1b3774a87d 100644
--- a/Source/Core/DiscIO/VolumeDisc.h
+++ b/Source/Core/DiscIO/VolumeDisc.h
@@ -47,6 +47,7 @@ public:
std::string GetInternalName(const Partition& partition = PARTITION_NONE) const override;
std::string GetApploaderDate(const Partition& partition) const override;
std::optional<u8> GetDiscNumber(const Partition& partition = PARTITION_NONE) const override;
+ u32 GetSimulatedMemorySize() const override;
bool IsNKit() const override;
protected:
diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h
index 2c383c0254..88a0847f80 100644
--- a/Source/Core/DiscIO/VolumeWad.h
+++ b/Source/Core/DiscIO/VolumeWad.h
@@ -61,6 +61,7 @@ public:
bool IsNKit() const override;
Region GetRegion() const override;
Country GetCountry(const Partition& partition = PARTITION_NONE) const override;
+ u32 GetSimulatedMemorySize() const override { return 0; }
BlobType GetBlobType() const override;
u64 GetDataSize() const override;
diff --git a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp
index a526e8c7cb..a2fc9c5410 100644
--- a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp
+++ b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp
@@ -276,10 +276,9 @@ void AdvancedPane::CreateLayout()
});
m_ram_override_checkbox->SetDescription(
- tr("Adjusts the amount of RAM in the emulated console.<br><br>"
- "<b>WARNING</b>: Enabling this will completely break many games.<br>Only a small "
- "number "
- "of games can benefit from this."
+ tr("Sets the amount of RAM in the emulated console to the values provided.<br><br>"
+ "<b>WARNING</b>: Enabling this will completely break many games. By default, Dolphin "
+ "determines what value is required based on the game information."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
auto* rtc_options = new QGroupBox(tr("Custom RTC Options"));
diff --git a/Source/UnitTests/Core/PowerPC/PageTableHostMappingTest.cpp b/Source/UnitTests/Core/PowerPC/PageTableHostMappingTest.cpp
index 2cfb4b8c8f..c2622cee37 100644
--- a/Source/UnitTests/Core/PowerPC/PageTableHostMappingTest.cpp
+++ b/Source/UnitTests/Core/PowerPC/PageTableHostMappingTest.cpp
@@ -10,6 +10,7 @@
#include "Common/Align.h"
#include "Common/CommonTypes.h"
#include "Common/Swap.h"
+#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/MemTools.h"
#include "Core/PowerPC/BreakPoints.h"
@@ -131,6 +132,8 @@ public:
if (!EMM::IsExceptionHandlerSupported())
GTEST_SKIP() << "Skipping PageTableHostMappingTest because exception handler is unsupported.";
+ SConfig::Init();
+
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
const u32 host_page_size = memory.GetHostPageSize();
@@ -177,6 +180,8 @@ public:
EMM::UninstallExceptionHandler();
Core::UndeclareAsCPUThread();
system.GetMemory().Shutdown();
+
+ SConfig::Shutdown();
}
static void SetSR(size_t index, u32 vsid)