mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-03-22 01:55:25 -05:00
The functions SaveToSYSCONF and LoadFromSYSCONF contain checks for whether emulation is running. The intent of this is that when we're emulating a Wii, the emulated system may write to SYSCONF whenever it likes and does not expect anything else to write to SYSCONF, so the host code shouldn't access SYSCONF while emulation is ongoing. However, Core::IsRunning is an imperfect proxy for whether we've handed over control of SYSCONF to the emulated system yet, as the actual handover happens at a slightly different point in time than when the emulation state is changed. This usually isn't a problem, but in theory it could be a determinism problem if a setting is changed right as emulation is starting, or it could cause the emulated software to briefly misbehave if a setting is changed right as emulation is stopping. Things got worse in72cf2bdb87when I replaced the Core::IsRunning calls with !Core::IsUninitialized. With IsRunning, there was be a period of time where SYSCONF should have been protected but wasn't. With !IsUninitialized, there was a period of time where SYSCONF shouldn't have been protected but was, and crucially, this period of time included the moments where we do setup and teardown of the emulated NAND, which broke transferring SYSCONF settings between the host and the guest.72cf2bdb87was reverted because of this. This commit adds a flag that we explicitly flip when control is handed over to or from the emulated system. This protects the SYSCONF file for exactly as long as is needed.
281 lines
9.0 KiB
C++
281 lines
9.0 KiB
C++
// Copyright 2016 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "Core/ConfigLoaders/BaseConfigLoader.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <variant>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Config/Config.h"
|
|
#include "Common/Config/Layer.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/IniFile.h"
|
|
#include "Common/Logging/Log.h"
|
|
|
|
#include "Core/Config/MainSettings.h"
|
|
#include "Core/Config/SYSCONFSettings.h"
|
|
#include "Core/ConfigLoaders/IsSettingSaveable.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/IOS/IOS.h"
|
|
#include "Core/IOS/USB/Bluetooth/BTBase.h"
|
|
#include "Core/SysConf.h"
|
|
#include "Core/System.h"
|
|
|
|
static bool s_sysconf_controlled_by_guest = false;
|
|
static std::recursive_mutex s_sysconf_lock;
|
|
|
|
namespace ConfigLoaders
|
|
{
|
|
void TransferSYSCONFControlToGuest()
|
|
{
|
|
std::lock_guard lock(s_sysconf_lock);
|
|
|
|
ASSERT(!s_sysconf_controlled_by_guest);
|
|
s_sysconf_controlled_by_guest = true;
|
|
}
|
|
|
|
void TransferSYSCONFControlFromGuest(WriteBackChangedValues write_back_changed_values)
|
|
{
|
|
std::lock_guard lock(s_sysconf_lock);
|
|
|
|
ASSERT(s_sysconf_controlled_by_guest);
|
|
s_sysconf_controlled_by_guest = false;
|
|
|
|
if (write_back_changed_values == WriteBackChangedValues::No)
|
|
return;
|
|
|
|
// SYSCONF can be modified during emulation by the user and internally, which makes it
|
|
// a bad idea to just always overwrite it with the settings from the base layer.
|
|
//
|
|
// Conversely, we also shouldn't just accept any changes to SYSCONF, as it may cause
|
|
// temporary settings (from Movie, Netplay, game INIs, etc.) to stick around.
|
|
//
|
|
// To avoid inconveniences in most cases, we accept changes that aren't being overridden by a
|
|
// non-base layer, and restore only the overridden settings.
|
|
|
|
// This layer contains the new SYSCONF settings (including any temporary settings).
|
|
Config::Layer temp_layer(Config::LayerType::Base);
|
|
// Use a separate loader so the temp layer doesn't automatically save
|
|
GenerateBaseConfigLoader()->Load(&temp_layer);
|
|
|
|
for (const auto& setting : Config::SYSCONF_SETTINGS)
|
|
{
|
|
std::visit(
|
|
[&](auto* info) {
|
|
// If this setting was overridden, then we copy the base layer value back to the
|
|
// SYSCONF. Otherwise we leave the new value in the SYSCONF.
|
|
if (Config::GetActiveLayerForConfig(*info) == Config::LayerType::Base)
|
|
Config::SetBase(*info, temp_layer.Get(*info));
|
|
},
|
|
setting.config_info);
|
|
}
|
|
|
|
SaveToSYSCONF(Config::LayerType::Base, SkipIfControlledByGuest::No);
|
|
}
|
|
|
|
void SaveToSYSCONF(Config::LayerType layer, SkipIfControlledByGuest skip,
|
|
std::function<bool(const Config::Location&)> predicate)
|
|
{
|
|
std::lock_guard lock(s_sysconf_lock);
|
|
if (skip == SkipIfControlledByGuest::Yes && s_sysconf_controlled_by_guest)
|
|
return;
|
|
|
|
IOS::HLE::Kernel ios;
|
|
SysConf sysconf{ios.GetFS()};
|
|
|
|
for (const Config::SYSCONFSetting& setting : Config::SYSCONF_SETTINGS)
|
|
{
|
|
std::visit(
|
|
[&](auto* info) {
|
|
if (predicate && !predicate(info->GetLocation()))
|
|
return;
|
|
|
|
const std::string key = info->GetLocation().section + "." + info->GetLocation().key;
|
|
|
|
if (setting.type == SysConf::Entry::Type::Long)
|
|
{
|
|
sysconf.SetData<u32>(key, setting.type, Config::Get(layer, *info));
|
|
}
|
|
else if (setting.type == SysConf::Entry::Type::Byte)
|
|
{
|
|
sysconf.SetData<u8>(key, setting.type, static_cast<u8>(Config::Get(layer, *info)));
|
|
}
|
|
else if (setting.type == SysConf::Entry::Type::BigArray)
|
|
{
|
|
// Somewhat hacky support for IPL.SADR. The setting only stores the
|
|
// first 4 bytes even thought the SYSCONF entry is much bigger.
|
|
SysConf::Entry* entry = sysconf.GetOrAddEntry(key, setting.type);
|
|
if (entry->bytes.size() < 0x1007 + 1)
|
|
entry->bytes.resize(0x1007 + 1);
|
|
*reinterpret_cast<u32*>(entry->bytes.data()) = Config::Get(layer, *info);
|
|
}
|
|
},
|
|
setting.config_info);
|
|
}
|
|
|
|
sysconf.SetData<u32>("IPL.CB", SysConf::Entry::Type::Long, 0);
|
|
IOS::HLE::RestoreBTInfoSection(&sysconf);
|
|
sysconf.Save();
|
|
}
|
|
|
|
const std::map<Config::System, int> system_to_ini = {
|
|
{Config::System::Main, F_DOLPHINCONFIG_IDX},
|
|
{Config::System::GCPad, F_GCPADCONFIG_IDX},
|
|
{Config::System::WiiPad, F_WIIPADCONFIG_IDX},
|
|
{Config::System::GCKeyboard, F_GCKEYBOARDCONFIG_IDX},
|
|
{Config::System::GFX, F_GFXCONFIG_IDX},
|
|
{Config::System::Logger, F_LOGGERCONFIG_IDX},
|
|
{Config::System::DualShockUDPClient, F_DUALSHOCKUDPCLIENTCONFIG_IDX},
|
|
{Config::System::FreeLook, F_FREELOOKCONFIG_IDX},
|
|
{Config::System::Achievements, F_RETROACHIEVEMENTSCONFIG_IDX},
|
|
// Config::System::Session should not be added to this list
|
|
};
|
|
|
|
// INI layer configuration loader
|
|
class BaseConfigLayerLoader final : public Config::ConfigLayerLoader
|
|
{
|
|
public:
|
|
BaseConfigLayerLoader() : ConfigLayerLoader(Config::LayerType::Base) {}
|
|
void Load(Config::Layer* layer) override
|
|
{
|
|
// List of settings that under no circumstances should be loaded from the global config INI.
|
|
static const auto s_setting_disallowed = {
|
|
&Config::MAIN_MEMORY_CARD_SIZE.GetLocation(),
|
|
};
|
|
|
|
LoadFromSYSCONF(layer);
|
|
for (const auto& system : system_to_ini)
|
|
{
|
|
Common::IniFile ini;
|
|
ini.Load(File::GetUserPath(system.second));
|
|
const auto& system_sections = ini.GetSections();
|
|
|
|
for (const auto& section : system_sections)
|
|
{
|
|
const std::string section_name = section.GetName();
|
|
const auto& section_map = section.GetValues();
|
|
|
|
for (const auto& value : section_map)
|
|
{
|
|
const Config::Location location{system.first, section_name, value.first};
|
|
const bool load_disallowed = std::ranges::any_of(
|
|
s_setting_disallowed, [&location](const auto* l) { return *l == location; });
|
|
if (load_disallowed)
|
|
continue;
|
|
|
|
layer->Set(location, value.second);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Save(Config::Layer* layer) override
|
|
{
|
|
SaveToSYSCONF(layer->GetLayer(), SkipIfControlledByGuest::Yes);
|
|
|
|
std::map<Config::System, Common::IniFile> inis;
|
|
|
|
for (const auto& system : system_to_ini)
|
|
{
|
|
inis[system.first].Load(File::GetUserPath(system.second));
|
|
}
|
|
|
|
for (const auto& config : layer->GetLayerMap())
|
|
{
|
|
const Config::Location& location = config.first;
|
|
const std::optional<std::string>& value = config.second;
|
|
|
|
// Done by SaveToSYSCONF
|
|
if (location.system == Config::System::SYSCONF)
|
|
continue;
|
|
|
|
if (location.system == Config::System::Session)
|
|
continue;
|
|
|
|
if (location.system == Config::System::GameSettingsOnly)
|
|
continue;
|
|
|
|
auto ini = inis.find(location.system);
|
|
if (ini == inis.end())
|
|
{
|
|
ERROR_LOG_FMT(COMMON, "Config can't map system '{}' to an INI file!",
|
|
Config::GetSystemName(location.system));
|
|
continue;
|
|
}
|
|
|
|
if (!IsSettingSaveable(location))
|
|
continue;
|
|
|
|
if (value)
|
|
{
|
|
auto* ini_section = ini->second.GetOrCreateSection(location.section);
|
|
ini_section->Set(location.key, *value);
|
|
}
|
|
else
|
|
{
|
|
ini->second.DeleteKey(location.section, location.key);
|
|
}
|
|
}
|
|
|
|
for (const auto& system : system_to_ini)
|
|
{
|
|
inis[system.first].Save(File::GetUserPath(system.second));
|
|
}
|
|
}
|
|
|
|
private:
|
|
void LoadFromSYSCONF(Config::Layer* layer)
|
|
{
|
|
std::lock_guard lock(s_sysconf_lock);
|
|
if (s_sysconf_controlled_by_guest)
|
|
return;
|
|
|
|
IOS::HLE::Kernel ios;
|
|
SysConf sysconf{ios.GetFS()};
|
|
for (const Config::SYSCONFSetting& setting : Config::SYSCONF_SETTINGS)
|
|
{
|
|
std::visit(
|
|
[&](auto* info) {
|
|
const Config::Location location = info->GetLocation();
|
|
const std::string key = location.section + "." + location.key;
|
|
if (setting.type == SysConf::Entry::Type::Long)
|
|
{
|
|
layer->Set(location, sysconf.GetData<u32>(key, info->GetDefaultValue()));
|
|
}
|
|
else if (setting.type == SysConf::Entry::Type::Byte)
|
|
{
|
|
layer->Set(location, sysconf.GetData<u8>(key, info->GetDefaultValue()));
|
|
}
|
|
else if (setting.type == SysConf::Entry::Type::BigArray)
|
|
{
|
|
// Somewhat hacky support for IPL.SADR. The setting only stores the
|
|
// first 4 bytes even thought the SYSCONF entry is much bigger.
|
|
u32 value = info->GetDefaultValue();
|
|
SysConf::Entry* entry = sysconf.GetEntry(key);
|
|
if (entry)
|
|
{
|
|
std::memcpy(&value, entry->bytes.data(),
|
|
std::min(entry->bytes.size(), sizeof(u32)));
|
|
}
|
|
layer->Set(location, value);
|
|
}
|
|
},
|
|
setting.config_info);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Loader generation
|
|
std::unique_ptr<Config::ConfigLayerLoader> GenerateBaseConfigLoader()
|
|
{
|
|
return std::make_unique<BaseConfigLayerLoader>();
|
|
}
|
|
} // namespace ConfigLoaders
|