mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
Merge e5ad814142 into ed2fe134aa
This commit is contained in:
commit
d62c8c46c9
@ -187,6 +187,14 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
void EnsureMemoryPagesWritable(size_t offset, size_t size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
for (const auto end_offset = offset + size; offset < end_offset; offset += BLOCK_SIZE)
|
||||
EnsureMemoryPageWritable(offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void* m_memory = nullptr;
|
||||
size_t m_size = 0;
|
||||
|
||||
@ -240,7 +240,7 @@ std::unique_ptr<BootParameters> BootParameters::GenerateFromFile(std::vector<std
|
||||
".elf"}};
|
||||
if (disc_image_extensions.contains(extension))
|
||||
{
|
||||
std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDisc(path);
|
||||
std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDiscForCore(path);
|
||||
if (disc)
|
||||
{
|
||||
return std::make_unique<BootParameters>(Disc{std::move(path), std::move(disc), paths},
|
||||
@ -469,7 +469,7 @@ static void SetDefaultDisc(DVD::DVDInterface& dvd_interface)
|
||||
{
|
||||
const std::string default_iso = Config::Get(Config::MAIN_DEFAULT_ISO);
|
||||
if (!default_iso.empty())
|
||||
SetDisc(dvd_interface, DiscIO::CreateDisc(default_iso));
|
||||
SetDisc(dvd_interface, DiscIO::CreateDiscForCore(default_iso));
|
||||
}
|
||||
|
||||
static void CopyDefaultExceptionHandlers(Core::System& system)
|
||||
@ -629,7 +629,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
|
||||
if (ipl.disc)
|
||||
{
|
||||
NOTICE_LOG_FMT(BOOT, "Inserting disc: {}", ipl.disc->path);
|
||||
SetDisc(system.GetDVDInterface(), DiscIO::CreateDisc(ipl.disc->path),
|
||||
SetDisc(system.GetDVDInterface(), DiscIO::CreateDiscForCore(ipl.disc->path),
|
||||
ipl.disc->auto_disc_change_paths);
|
||||
}
|
||||
else
|
||||
|
||||
@ -58,6 +58,7 @@ constexpr bool DEFAULT_CPU_THREAD = true;
|
||||
constexpr bool DEFAULT_CPU_THREAD = false;
|
||||
#endif
|
||||
const Info<bool> MAIN_CPU_THREAD{{System::Main, "Core", "CPUThread"}, DEFAULT_CPU_THREAD};
|
||||
const Info<bool> MAIN_LOAD_GAME_INTO_MEMORY{{System::Main, "Core", "LoadGameIntoMemory"}, false};
|
||||
const Info<bool> MAIN_SYNC_ON_SKIP_IDLE{{System::Main, "Core", "SyncOnSkipIdle"}, true};
|
||||
const Info<std::string> MAIN_DEFAULT_ISO{{System::Main, "Core", "DefaultISO"}, ""};
|
||||
const Info<bool> MAIN_ENABLE_CHEATS{{System::Main, "Core", "EnableCheats"}, false};
|
||||
|
||||
@ -68,6 +68,7 @@ extern const Info<bool> MAIN_CORRECT_TIME_DRIFT;
|
||||
extern const Info<bool> MAIN_RUSH_FRAME_PRESENTATION;
|
||||
extern const Info<bool> MAIN_SMOOTH_EARLY_PRESENTATION;
|
||||
extern const Info<bool> MAIN_CPU_THREAD;
|
||||
extern const Info<bool> MAIN_LOAD_GAME_INTO_MEMORY;
|
||||
extern const Info<bool> MAIN_SYNC_ON_SKIP_IDLE;
|
||||
extern const Info<std::string> MAIN_DEFAULT_ISO;
|
||||
extern const Info<bool> MAIN_ENABLE_CHEATS;
|
||||
|
||||
@ -428,7 +428,8 @@ void DVDInterface::EjectDiscCallback(Core::System& system, u64 userdata, s64 cyc
|
||||
void DVDInterface::InsertDiscCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||
{
|
||||
auto& di = system.GetDVDInterface();
|
||||
std::unique_ptr<DiscIO::VolumeDisc> new_disc = DiscIO::CreateDisc(di.m_disc_path_to_insert);
|
||||
std::unique_ptr<DiscIO::VolumeDisc> new_disc =
|
||||
DiscIO::CreateDiscForCore(di.m_disc_path_to_insert);
|
||||
|
||||
if (new_disc)
|
||||
di.SetDisc(std::move(new_disc), {});
|
||||
|
||||
@ -3,6 +3,8 @@ add_library(discio
|
||||
Blob.h
|
||||
CISOBlob.cpp
|
||||
CISOBlob.h
|
||||
CachedBlob.cpp
|
||||
CachedBlob.h
|
||||
CompressedBlob.cpp
|
||||
CompressedBlob.h
|
||||
DirectoryBlob.cpp
|
||||
|
||||
239
Source/Core/DiscIO/CachedBlob.cpp
Normal file
239
Source/Core/DiscIO/CachedBlob.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "DiscIO/CachedBlob.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MemArena.h"
|
||||
|
||||
#include "DiscIO/DiscScrubber.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class CacheFiller final
|
||||
{
|
||||
public:
|
||||
explicit CacheFiller(std::unique_ptr<BlobReader> reader, bool attempt_to_scrub)
|
||||
: m_thread{&CacheFiller::ThreadFunc, this, std::move(reader), attempt_to_scrub}
|
||||
{
|
||||
}
|
||||
|
||||
~CacheFiller()
|
||||
{
|
||||
m_stop_thread.store(true, std::memory_order_relaxed);
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
bool Read(u64 offset, u64 size, u8* out_ptr)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
// Just return early to safely handle a read of zero if that were to happen.
|
||||
// This avoids a m_memory_region_data initialization race.
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (GetCacheState(offset, size))
|
||||
{
|
||||
case CacheState::Cached:
|
||||
std::memcpy(out_ptr, m_memory_region_data + offset, size);
|
||||
return true;
|
||||
|
||||
case CacheState::Scrubbed:
|
||||
WARN_LOG_FMT(DISCIO,
|
||||
"CachedBlobReader: Read({}, {}) hits a scrubbed cluster which is not cached.",
|
||||
offset, size);
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class CacheState
|
||||
{
|
||||
Cached,
|
||||
NotCached,
|
||||
Scrubbed,
|
||||
};
|
||||
|
||||
CacheState GetCacheState(u64 offset, u64 size)
|
||||
{
|
||||
const auto cache_pos = m_cache_filled_pos.load(std::memory_order_acquire);
|
||||
|
||||
const auto end_pos = offset + size;
|
||||
if (end_pos > cache_pos)
|
||||
return CacheState::NotCached;
|
||||
|
||||
for (u64 i = offset; i < end_pos; i += DiscScrubber::CLUSTER_SIZE)
|
||||
{
|
||||
if (m_scrubber.CanBlockBeScrubbed(i))
|
||||
return CacheState::Scrubbed;
|
||||
}
|
||||
|
||||
return CacheState::Cached;
|
||||
}
|
||||
|
||||
void ThreadFunc(std::unique_ptr<BlobReader> reader, bool attempt_to_scrub)
|
||||
{
|
||||
static constexpr auto PERIODIC_LOG_TIME = std::chrono::seconds{1};
|
||||
|
||||
const auto start_time = Clock::now();
|
||||
const u64 total_size = reader->GetDataSize();
|
||||
|
||||
m_memory_region_data = static_cast<u8*>(m_memory_region.Create(total_size));
|
||||
if (m_memory_region_data == nullptr)
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "CachedBlobReader: Failed to create memory region.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Returns CLUSTER_SIZE or smaller at the end of the file.
|
||||
const auto get_read_size = [&](u64 pos) {
|
||||
return std::min<u64>(total_size - pos, DiscScrubber::CLUSTER_SIZE);
|
||||
};
|
||||
|
||||
// Used for periodic progress logging.
|
||||
u64 total_bytes_to_commit = total_size;
|
||||
|
||||
if (attempt_to_scrub)
|
||||
{
|
||||
const auto volume = CreateVolume(reader->CopyReader());
|
||||
if (volume != nullptr && m_scrubber.SetupScrub(*volume))
|
||||
{
|
||||
for (u64 i = 0; i < total_size; i += DiscScrubber::CLUSTER_SIZE)
|
||||
{
|
||||
if (m_scrubber.CanBlockBeScrubbed(i))
|
||||
total_bytes_to_commit -= get_read_size(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(DISCIO, "CachedBlobReader: Failed to scrub. The entire file will be cached.");
|
||||
}
|
||||
}
|
||||
|
||||
auto next_log_time = start_time + PERIODIC_LOG_TIME;
|
||||
u64 read_offset = 0;
|
||||
u64 committed_count = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (m_stop_thread.load(std::memory_order_relaxed))
|
||||
{
|
||||
INFO_LOG_FMT(DISCIO, "CachedBlobReader: Stopped");
|
||||
break;
|
||||
}
|
||||
|
||||
const auto read_size = get_read_size(read_offset);
|
||||
if (read_size == 0)
|
||||
{
|
||||
const auto total_time = DT_s{Clock::now() - start_time}.count();
|
||||
|
||||
static constexpr auto mib_scale = double(1 << 20);
|
||||
|
||||
NOTICE_LOG_FMT(
|
||||
DISCIO, "CachedBlobReader: Completed. Cached {:.2f} of {:.2f} MiB in {:.2f} seconds.",
|
||||
committed_count / mib_scale, total_size / mib_scale, total_time);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_scrubber.CanBlockBeScrubbed(read_offset))
|
||||
{
|
||||
m_memory_region.EnsureMemoryPagesWritable(read_offset, read_size);
|
||||
|
||||
if (!reader->Read(read_offset, read_size, m_memory_region_data + read_offset))
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "CachedBlobReader: Read({}, {}) failed.", read_offset, read_size);
|
||||
break;
|
||||
}
|
||||
|
||||
committed_count += read_size;
|
||||
}
|
||||
|
||||
read_offset += read_size;
|
||||
m_cache_filled_pos.store(read_offset, std::memory_order_release);
|
||||
|
||||
if (const auto now = Clock::now(); now >= next_log_time)
|
||||
{
|
||||
INFO_LOG_FMT(DISCIO, "CachedBlobReader: Progress: {}%",
|
||||
committed_count * 100 / total_bytes_to_commit);
|
||||
next_log_time = now + PERIODIC_LOG_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The thread has read non-scrubbed bytes into memory up to this point.
|
||||
std::atomic<u64> m_cache_filled_pos{};
|
||||
|
||||
Common::LazyMemoryRegion m_memory_region;
|
||||
u8* m_memory_region_data{};
|
||||
|
||||
DiscScrubber m_scrubber;
|
||||
|
||||
std::atomic_bool m_stop_thread{};
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
class CachedBlobReader final : public BlobReader
|
||||
{
|
||||
public:
|
||||
explicit CachedBlobReader(std::unique_ptr<BlobReader> reader, bool attempt_to_scrub)
|
||||
: m_cache_filler{std::make_shared<CacheFiller>(reader->CopyReader(), attempt_to_scrub)},
|
||||
m_reader{std::move(reader)}
|
||||
|
||||
{
|
||||
INFO_LOG_FMT(DISCIO, "CachedBlobReader: Created");
|
||||
}
|
||||
|
||||
CachedBlobReader(std::shared_ptr<CacheFiller> cache_filler, std::unique_ptr<BlobReader> reader)
|
||||
: m_cache_filler{std::move(cache_filler)}, m_reader{std::move(reader)}
|
||||
{
|
||||
INFO_LOG_FMT(DISCIO, "CachedBlobReader: Copied");
|
||||
}
|
||||
|
||||
std::unique_ptr<BlobReader> CopyReader() const override
|
||||
{
|
||||
return std::make_unique<CachedBlobReader>(m_cache_filler, m_reader->CopyReader());
|
||||
}
|
||||
|
||||
BlobType GetBlobType() const override { return m_reader->GetBlobType(); }
|
||||
u64 GetRawSize() const override { return GetDataSize(); }
|
||||
u64 GetDataSize() const override { return m_reader->GetDataSize(); }
|
||||
DataSizeType GetDataSizeType() const override { return m_reader->GetDataSizeType(); }
|
||||
|
||||
u64 GetBlockSize() const override { return 0; }
|
||||
bool HasFastRandomAccessInBlock() const override { return true; }
|
||||
std::string GetCompressionMethod() const override { return {}; }
|
||||
std::optional<int> GetCompressionLevel() const override { return std::nullopt; }
|
||||
|
||||
bool Read(u64 offset, u64 size, u8* out_ptr) override
|
||||
{
|
||||
return m_cache_filler->Read(offset, size, out_ptr) || m_reader->Read(offset, size, out_ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
// A shared object does the cache filling for sensible CopyReader behavior.
|
||||
const std::shared_ptr<CacheFiller> m_cache_filler;
|
||||
|
||||
const std::unique_ptr<BlobReader> m_reader;
|
||||
};
|
||||
|
||||
std::unique_ptr<BlobReader> CreateCachedBlobReader(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
return std::make_unique<CachedBlobReader>(std::move(reader), false);
|
||||
}
|
||||
|
||||
std::unique_ptr<BlobReader> CreateScrubbingCachedBlobReader(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
return std::make_unique<CachedBlobReader>(std::move(reader), true);
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
||||
16
Source/Core/DiscIO/CachedBlob.h
Normal file
16
Source/Core/DiscIO/CachedBlob.h
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
||||
std::unique_ptr<BlobReader> CreateCachedBlobReader(std::unique_ptr<BlobReader> reader);
|
||||
std::unique_ptr<BlobReader> CreateScrubbingCachedBlobReader(std::unique_ptr<BlobReader> reader);
|
||||
|
||||
} // namespace DiscIO
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@ -16,8 +16,11 @@
|
||||
#include "Common/Crypto/SHA1.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/CachedBlob.h"
|
||||
#include "DiscIO/DiscUtils.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
@ -84,16 +87,21 @@ std::map<Language, std::string> Volume::ReadWiiNames(const std::vector<char16_t>
|
||||
return names;
|
||||
}
|
||||
|
||||
static std::unique_ptr<VolumeDisc> TryCreateDisc(std::unique_ptr<BlobReader>& reader)
|
||||
template <typename T = std::identity>
|
||||
static std::unique_ptr<VolumeDisc> TryCreateDisc(std::unique_ptr<BlobReader> reader,
|
||||
const T& reader_adapter_factory = {})
|
||||
{
|
||||
if (!reader)
|
||||
return nullptr;
|
||||
|
||||
// `reader_adapter_factory` is used *after* successful magic word read.
|
||||
// This prevents `CachedBlobReader` from showing warnings when failing to scrub a .dol file.
|
||||
|
||||
if (reader->ReadSwapped<u32>(0x18) == WII_DISC_MAGIC)
|
||||
return std::make_unique<VolumeWii>(std::move(reader));
|
||||
return std::make_unique<VolumeWii>(reader_adapter_factory(std::move(reader)));
|
||||
|
||||
if (reader->ReadSwapped<u32>(0x1C) == GAMECUBE_DISC_MAGIC)
|
||||
return std::make_unique<VolumeGC>(std::move(reader));
|
||||
return std::make_unique<VolumeGC>(reader_adapter_factory(std::move(reader)));
|
||||
|
||||
// No known magic words found
|
||||
return nullptr;
|
||||
@ -101,7 +109,7 @@ static std::unique_ptr<VolumeDisc> TryCreateDisc(std::unique_ptr<BlobReader>& re
|
||||
|
||||
std::unique_ptr<VolumeDisc> CreateDisc(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
return TryCreateDisc(reader);
|
||||
return TryCreateDisc(std::move(reader));
|
||||
}
|
||||
|
||||
std::unique_ptr<VolumeDisc> CreateDisc(const std::string& path)
|
||||
@ -109,7 +117,15 @@ std::unique_ptr<VolumeDisc> CreateDisc(const std::string& path)
|
||||
return CreateDisc(CreateBlobReader(path));
|
||||
}
|
||||
|
||||
static std::unique_ptr<VolumeWAD> TryCreateWAD(std::unique_ptr<BlobReader>& reader)
|
||||
std::unique_ptr<VolumeDisc> CreateDiscForCore(const std::string& path)
|
||||
{
|
||||
if (Config::Get(Config::MAIN_LOAD_GAME_INTO_MEMORY))
|
||||
return TryCreateDisc(CreateBlobReader(path), CreateScrubbingCachedBlobReader);
|
||||
|
||||
return CreateDisc(path);
|
||||
}
|
||||
|
||||
static std::unique_ptr<VolumeWAD> TryCreateWAD(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
if (!reader)
|
||||
return nullptr;
|
||||
@ -126,7 +142,7 @@ static std::unique_ptr<VolumeWAD> TryCreateWAD(std::unique_ptr<BlobReader>& read
|
||||
|
||||
std::unique_ptr<VolumeWAD> CreateWAD(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
return TryCreateWAD(reader);
|
||||
return TryCreateWAD(std::move(reader));
|
||||
}
|
||||
|
||||
std::unique_ptr<VolumeWAD> CreateWAD(const std::string& path)
|
||||
@ -136,11 +152,11 @@ std::unique_ptr<VolumeWAD> CreateWAD(const std::string& path)
|
||||
|
||||
std::unique_ptr<Volume> CreateVolume(std::unique_ptr<BlobReader> reader)
|
||||
{
|
||||
std::unique_ptr<VolumeDisc> disc = TryCreateDisc(reader);
|
||||
std::unique_ptr<VolumeDisc> disc = TryCreateDisc(std::move(reader));
|
||||
if (disc)
|
||||
return disc;
|
||||
|
||||
std::unique_ptr<VolumeWAD> wad = TryCreateWAD(reader);
|
||||
std::unique_ptr<VolumeWAD> wad = TryCreateWAD(std::move(reader));
|
||||
if (wad)
|
||||
return wad;
|
||||
|
||||
|
||||
@ -176,6 +176,9 @@ protected:
|
||||
|
||||
std::unique_ptr<VolumeDisc> CreateDisc(std::unique_ptr<BlobReader> reader);
|
||||
std::unique_ptr<VolumeDisc> CreateDisc(const std::string& path);
|
||||
// This version enables caching when the "Load Games into Memory" setting is enabled.
|
||||
std::unique_ptr<VolumeDisc> CreateDiscForCore(const std::string& path);
|
||||
|
||||
std::unique_ptr<VolumeWAD> CreateWAD(std::unique_ptr<BlobReader> reader);
|
||||
std::unique_ptr<VolumeWAD> CreateWAD(const std::string& path);
|
||||
std::unique_ptr<Volume> CreateVolume(std::unique_ptr<BlobReader> reader);
|
||||
|
||||
@ -485,6 +485,7 @@
|
||||
<ClInclude Include="Core\WiiUtils.h" />
|
||||
<ClInclude Include="DiscIO\Blob.h" />
|
||||
<ClInclude Include="DiscIO\CISOBlob.h" />
|
||||
<ClInclude Include="DiscIO\CachedBlob.h" />
|
||||
<ClInclude Include="DiscIO\CompressedBlob.h" />
|
||||
<ClInclude Include="DiscIO\DirectoryBlob.h" />
|
||||
<ClInclude Include="DiscIO\DiscExtractor.h" />
|
||||
@ -1170,6 +1171,7 @@
|
||||
<ClCompile Include="Core\WC24PatchEngine.cpp" />
|
||||
<ClCompile Include="DiscIO\Blob.cpp" />
|
||||
<ClCompile Include="DiscIO\CISOBlob.cpp" />
|
||||
<ClCompile Include="DiscIO\CachedBlob.cpp" />
|
||||
<ClCompile Include="DiscIO\CompressedBlob.cpp" />
|
||||
<ClCompile Include="DiscIO\DirectoryBlob.cpp" />
|
||||
<ClCompile Include="DiscIO\DiscExtractor.cpp" />
|
||||
|
||||
@ -90,6 +90,7 @@ void GeneralPane::OnEmulationStateChanged(Core::State state)
|
||||
|
||||
m_checkbox_dualcore->setEnabled(!running);
|
||||
m_checkbox_cheats->setEnabled(!running);
|
||||
m_checkbox_load_games_into_memory->setEnabled(!running);
|
||||
m_checkbox_override_region_settings->setEnabled(!running);
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
m_checkbox_discord_presence->setEnabled(!running);
|
||||
@ -147,6 +148,15 @@ void GeneralPane::CreateBasic()
|
||||
m_checkbox_cheats = new ConfigBool(tr("Enable Cheats"), Config::MAIN_ENABLE_CHEATS);
|
||||
basic_group_layout->addWidget(m_checkbox_cheats);
|
||||
|
||||
m_checkbox_load_games_into_memory =
|
||||
new ConfigBool(tr("Load Whole Game Into Memory"), Config::MAIN_LOAD_GAME_INTO_MEMORY);
|
||||
basic_group_layout->addWidget(m_checkbox_load_games_into_memory);
|
||||
m_checkbox_load_games_into_memory->SetDescription(
|
||||
tr("Loads the running game into memory in the background."
|
||||
"<br><br>This may improve performance with slow or high-latency storage."
|
||||
"<br>System memory requirements will be much higher with this setting enabled."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
|
||||
m_checkbox_override_region_settings =
|
||||
new ConfigBool(tr("Allow Mismatched Region Settings"), Config::MAIN_OVERRIDE_REGION_SETTINGS);
|
||||
basic_group_layout->addWidget(m_checkbox_override_region_settings);
|
||||
|
||||
@ -48,6 +48,7 @@ private:
|
||||
ToolTipComboBox* m_combobox_fallback_region;
|
||||
ConfigBool* m_checkbox_dualcore;
|
||||
ConfigBool* m_checkbox_cheats;
|
||||
ConfigBool* m_checkbox_load_games_into_memory;
|
||||
ConfigBool* m_checkbox_override_region_settings;
|
||||
ConfigBool* m_checkbox_auto_disc_change;
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
|
||||
Loading…
Reference in New Issue
Block a user