mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
"Copy Global Settings" does not copy the settings you can see. It runs the whole configuration through a wrapper that writes every key unconditionally, so the file it leaves behind holds roughly seven hundred of them — network adapters, the debugger, trace logging, memory cards, sections no settings page ever shows. That was untidy and no worse, until a key present in a per-game file started meaning the player claimed it. Now one press of a button whose dialog promises only that "the configuration for this game will be replaced by the current global settings" turns off every automatic fix that game had, permanently and silently. A value is worth writing down only if it decides something, and there are two ways it can fail to. It can be the stock default, in which case the file carries it as noise. Or it can be what the game database is going to set anyway, in which case writing it can only become a claim that suppresses the fix it agrees with. So the copy now excludes both, and what lands is what the player actually chose. The comparison goes through the string form rather than the typed value, so a float or an enum name compares the way it will be stored rather than the way it happens to sit in memory. That is why the references are built with the same interface class: same formatting on both sides, exact comparison, one path for every type. The database reference is a default configuration with the entry applied, not this one with the entry applied. The question is what the database wants, not where it would leave the source. It matters for the handful of fixes that clamp rather than assign, and it errs towards writing the player's value — never towards dropping a fix, since a value is only skipped when it already equals what the fix would set. Working the reference out means running the apply functions for an outcome nobody is going to run with, so they take an apply mode. A hypothetical apply says nothing to the log, raises none of the recommendation messages, and does not allocate the four megabyte lookup table that the Goemon TLB fix asks for. The tests cover the precedence rule and the filter, but the ones that matter are the drift guards: they assert every gamefix, speedhack and clamp mode has a settings key, and that the only graphics fixes without one are the six that genuinely have no setting behind them — three renderer routine selectors and three that only raise a recommendation. A knob nobody maps is a setting that goes quietly back to being overridden, with no warning and no failure, and that is what these are here to catch.
185 lines
9.6 KiB
C++
185 lines
9.6 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#pragma once
|
|
|
|
#include "MemorySettingsInterface.h"
|
|
#include "SettingsInterface.h"
|
|
|
|
#include "common/EnumOps.h"
|
|
#include "common/SmallString.h"
|
|
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
// Helper class which loads or saves depending on the derived class.
|
|
class SettingsWrapper
|
|
{
|
|
public:
|
|
SettingsWrapper(SettingsInterface& si);
|
|
|
|
virtual bool IsLoading() const = 0;
|
|
virtual bool IsSaving() const = 0;
|
|
|
|
virtual void Entry(const char* section, const char* var, int& value, const int defvalue = 0) = 0;
|
|
virtual void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) = 0;
|
|
virtual void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) = 0;
|
|
virtual void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) = 0;
|
|
virtual void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) = 0;
|
|
virtual void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) = 0;
|
|
|
|
// This special form of Entry is provided for bitfields, which cannot be passed by reference.
|
|
virtual bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) = 0;
|
|
virtual int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) = 0;
|
|
|
|
template <typename T>
|
|
void EnumEntry(const char* section, const char* var, T& value, const char* const* enumArray = nullptr, const T defvalue = (T)0)
|
|
{
|
|
int tstore = (int)value;
|
|
auto defaultvalue = enum_cast(defvalue);
|
|
if (enumArray == NULL)
|
|
Entry(section, var, tstore, defaultvalue);
|
|
else
|
|
_EnumEntry(section, var, tstore, enumArray, defaultvalue);
|
|
value = (T)tstore;
|
|
}
|
|
|
|
template <typename T>
|
|
void EnumEntry(const char* section, const char* var, T& value, std::optional<T> (*parse_function)(const char*),
|
|
const char*(name_function)(T value), T default_value)
|
|
{
|
|
TinyString str_value(name_function(value));
|
|
Entry(section, var, str_value, name_function(default_value));
|
|
if (std::optional<T> parsed_value = parse_function(str_value); parsed_value.has_value())
|
|
value = parsed_value.value();
|
|
}
|
|
|
|
protected:
|
|
virtual void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) = 0;
|
|
|
|
SettingsInterface& m_si;
|
|
};
|
|
|
|
class SettingsLoadWrapper final : public SettingsWrapper
|
|
{
|
|
public:
|
|
SettingsLoadWrapper(SettingsInterface& si);
|
|
|
|
bool IsLoading() const override;
|
|
bool IsSaving() const override;
|
|
|
|
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
|
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
|
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
|
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
|
|
|
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
|
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
|
|
|
protected:
|
|
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
|
};
|
|
|
|
class SettingsSaveWrapper final : public SettingsWrapper
|
|
{
|
|
public:
|
|
SettingsSaveWrapper(SettingsInterface& si);
|
|
|
|
bool IsLoading() const override;
|
|
bool IsSaving() const override;
|
|
|
|
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
|
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
|
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
|
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
|
|
|
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
|
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
|
|
|
protected:
|
|
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
|
};
|
|
|
|
/// Saves only the values that differ from every reference, and deletes the rest.
|
|
///
|
|
/// A per-game settings file is a record of decisions. Copying a whole configuration
|
|
/// into one verbatim writes hundreds of keys the player never chose and never saw,
|
|
/// and each of those then reads back as a deliberate choice — which is exactly how
|
|
/// a copy of the global settings ends up suppressing every automatic fix a game had.
|
|
/// Give this the values that would apply anyway and only the real deviations land.
|
|
///
|
|
/// Comparison goes through the string form rather than the typed value, so a float
|
|
/// or an enum name compares the way it will actually be stored. That is why the
|
|
/// references must be MemorySettingsInterfaces built the same way: same class, same
|
|
/// formatting, exact comparison.
|
|
class SettingsSaveDeviationsWrapper final : public SettingsWrapper
|
|
{
|
|
public:
|
|
SettingsSaveDeviationsWrapper(SettingsInterface& si, std::vector<const MemorySettingsInterface*> references);
|
|
~SettingsSaveDeviationsWrapper();
|
|
|
|
bool IsLoading() const override;
|
|
bool IsSaving() const override;
|
|
|
|
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
|
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
|
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
|
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
|
|
|
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
|
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
|
|
|
protected:
|
|
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
|
|
|
private:
|
|
/// Stages the value with `write`, then keeps it only if no reference already says
|
|
/// the same thing.
|
|
template <typename WriteFn>
|
|
void Keep(const char* section, const char* var, const WriteFn& write);
|
|
|
|
std::vector<const MemorySettingsInterface*> m_references;
|
|
/// Holds the candidate long enough to be rendered the same way a reference is.
|
|
MemorySettingsInterface m_staging;
|
|
};
|
|
|
|
class SettingsClearWrapper final : public SettingsWrapper
|
|
{
|
|
public:
|
|
SettingsClearWrapper(SettingsInterface& si);
|
|
|
|
bool IsLoading() const override;
|
|
bool IsSaving() const override;
|
|
|
|
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
|
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
|
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
|
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
|
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
|
|
|
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
|
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
|
|
|
protected:
|
|
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
|
};
|
|
|
|
#define SettingsWrapSection(section) const char* CURRENT_SETTINGS_SECTION = section;
|
|
#define SettingsWrapEntry(var) wrap.Entry(CURRENT_SETTINGS_SECTION, #var, var, var)
|
|
#define SettingsWrapEntryEx(var, name) wrap.Entry(CURRENT_SETTINGS_SECTION, name, var, var)
|
|
#define SettingsWrapBitfield(varname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, #varname, varname, varname)
|
|
#define SettingsWrapBitBool(varname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, #varname, !!varname, varname)
|
|
#define SettingsWrapBitfieldEx(varname, textname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, varname, varname)
|
|
#define SettingsWrapBitBoolEx(varname, textname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, textname, !!varname, varname)
|
|
#define SettingsWrapEnumEx(varname, textname, names) wrap.EnumEntry(CURRENT_SETTINGS_SECTION, textname, varname, names, varname)
|
|
#define SettingsWrapParsedEnum(varname, textname, parse_func, name_func) wrap.EnumEntry(CURRENT_SETTINGS_SECTION, textname, varname, parse_func, name_func, varname)
|
|
#define SettingsWrapIntEnumEx(varname, textname) varname = static_cast<decltype(varname)>(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, static_cast<int>(varname), static_cast<int>(varname)))
|
|
|