From 24ad5628717df1184fcbac027cd27618891afc40 Mon Sep 17 00:00:00 2001 From: cristian64 Date: Fri, 27 Mar 2026 11:30:52 +0000 Subject: [PATCH 1/6] Core: Add `Volume::GetSimulatedMemorySize()`. The data structure of the `bi2.bin` file in which the simulated memory size is set: > **13.2 Disk header Information** > > this is loaded to the Address in `0x800000f4` when a disc is initialized by the IPL > > | offset | end | size | Description | > | :------: | --- | :--: | --------------------- | > | `0x0000` | | 4 | Debug-monitor Size | > | `0x0004` | | 4 | Simulated Memory Size | > | `0x0008` | | 4 | Argument offset | > | `0x000c` | | 4 | Debug flag | > | `0x0010` | | 4 | Track Location | > | `0x0014` | | 4 | Track size | > | `0x0018` | | 4 | Countrycode | > | `0x001c` | | 4 | ? | See https://hitmen.c02.at/files/yagcd/yagcd/chap13.html#sec13.2. Most GameCube games set the value to `24 MiB`, which matches the physical memory size. Most Wii games set the value to `0 MiB` (unset). The debug build of _Mario Kart: Double Dash!!_ is one game that sets the value to `48 MiB`. --- Source/Core/DiscIO/Volume.h | 1 + Source/Core/DiscIO/VolumeDisc.cpp | 8 ++++++++ Source/Core/DiscIO/VolumeDisc.h | 1 + Source/Core/DiscIO/VolumeWad.h | 1 + 4 files changed, 11 insertions(+) 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 VolumeDisc::GetDiscNumber(const Partition& partition) const return ReadSwapped(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(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 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; From ebcbb79f70a25fa75cbd30da39a65eac8dd9eddd Mon Sep 17 00:00:00 2001 From: cristian64 Date: Fri, 27 Mar 2026 11:48:18 +0000 Subject: [PATCH 2/6] Core: Add `SConfig::GetSimulatedMemorySize()`. This makes the simulated memory size of the running game accessible via the `SConfig` singleton. --- Source/Core/Core/ConfigManager.cpp | 25 ++++++++++++++++++------- Source/Core/Core/ConfigManager.h | 6 +++++- 2 files changed, 23 insertions(+), 8 deletions(-) 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 lock(m_metadata_lock); + return m_simulated_memory_size; +} + void SConfig::ResetRunningGameMetadata() { std::lock_guard 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 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 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 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; }; From 03b1f2148b080adc9ed15ecc40e3948404388b61 Mon Sep 17 00:00:00 2001 From: cristian64 Date: Mon, 29 Jun 2026 22:29:37 +0100 Subject: [PATCH 3/6] DolphinAnalytics: Add game quirk for game simulated memory size. This is the 32-bit unsigned integer that is seen at offset `0x04` in the `bi2.bin` file. The intent is to study what games define values that departure from the norm. Common values are 0 MiB (Wii games) and 24 MiB (GameCube games), with some debug and demo builds featuring 48 MiB (e.g. the debug build of _Mario Kart: Double Dash!!_). --- Source/Core/Core/DolphinAnalytics.cpp | 3 +++ 1 file changed, 3 insertions(+) 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)); From 0cab1731d61ecb87c7bc388dbd176a4150afbbe3 Mon Sep 17 00:00:00 2001 From: cristian64 Date: Fri, 3 Apr 2026 10:58:54 +0100 Subject: [PATCH 4/6] Core: Adjust emulated memory size automatically. Dolphin now uses the simulated memory size defined in `bi2.bin` to adjust the memory size in the emulated console automatically. If **Enable Emulated Memory Size Override** has been enabled by the user, the fixed memory size specified for **MEM1** and **MEM2** are still used as they were before. Most retail games do not define the simulated memory size (Wii or Triforce games), or define it to a value that matches the default 24 MiB (GameCube games), so, in the general case, there is no behavior change. One game that sets the simulated memory to a non-default value is the debug build of _Mario Kart: Double Dash!!_, where the value is set to 48 MiB. This enhancement is focused mainly to the modding community. Prior to these changes, modded games with extended memory requirements would fail to launch in Dolphin [with no indication of what the problem is] if the user failed to set the emulated memory override to the correct value. Now, modding tools can specify the simulated memory size in the `bi2.bin` file to produce extended games that _just work_ in Dolphin, without cumbersome instructions that can be overlooked by the user. --- Source/Core/Core/FifoPlayer/FifoDataFile.cpp | 3 ++- Source/Core/Core/HW/Memmap.cpp | 13 +++++++++++-- Source/Core/Core/HW/Memmap.h | 2 ++ Source/Core/Core/IOS/IOS.cpp | 5 ++++- Source/Core/Core/System.cpp | 1 + Source/Core/Core/System.h | 4 ++++ .../Core/PowerPC/PageTableHostMappingTest.cpp | 5 +++++ 7 files changed, 29 insertions(+), 4 deletions(-) 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 +#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/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) From 594e5d2b56e441760888d56b2d7633520f7770f5 Mon Sep 17 00:00:00 2001 From: cristian64 Date: Sat, 30 May 2026 13:09:58 +0100 Subject: [PATCH 5/6] DolphinQt: Update tool tip for Enable Emulated Memory Size Override. --- Source/Core/DolphinQt/Settings/AdvancedPane.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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.

" - "WARNING: Enabling this will completely break many games.
Only a small " - "number " - "of games can benefit from this." + tr("Sets the amount of RAM in the emulated console to the values provided.

" + "WARNING: Enabling this will completely break many games. By default, Dolphin " + "determines what value is required based on the game information." "

If unsure, leave this unchecked.")); auto* rtc_options = new QGroupBox(tr("Custom RTC Options")); From f9ef6c2151d48ef9b5dbe9ee1c2278e5d60b5ed9 Mon Sep 17 00:00:00 2001 From: cristian64 Date: Sat, 30 May 2026 13:12:24 +0100 Subject: [PATCH 6/6] Android: Update description for Enable Emulated Memory Size Override. --- Source/Android/app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ Adjusts the emulated CPU\'s clock rate if \"Override Emulated CPU Clock Speed\" is enabled. Memory Override Enable Emulated Memory Size Override - 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. + 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. MEM1 Size MEM2 Size GPU Options