Merge pull request #14665 from cristian64/simulated_memory_size

Core: Adjust emulated memory size automatically.
This commit is contained in:
Scott Mansell
2026-07-12 11:34:44 +12:00
committed by GitHub
16 changed files with 70 additions and 17 deletions

View File

@@ -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>

View File

@@ -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)
{

View File

@@ -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;
};

View File

@@ -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));

View File

@@ -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;

View File

@@ -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))

View File

@@ -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
{

View File

@@ -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 =

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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"

View File

@@ -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:

View File

@@ -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;

View File

@@ -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"));

View File

@@ -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)