mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2025-10-30 07:11:05 +00:00
552 lines
14 KiB
C++
552 lines
14 KiB
C++
#include "config.h"
|
|
#include <os/logger.h>
|
|
#include <user/paths.h>
|
|
#include <exports.h>
|
|
|
|
std::vector<IConfigDef*> g_configDefinitions;
|
|
|
|
#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \
|
|
static std::unordered_map<std::string, type> g_##type##_template =
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(ELanguage)
|
|
{
|
|
{ "English", ELanguage::English },
|
|
{ "Japanese", ELanguage::Japanese },
|
|
{ "German", ELanguage::German },
|
|
{ "French", ELanguage::French },
|
|
{ "Spanish", ELanguage::Spanish },
|
|
{ "Italian", ELanguage::Italian }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EUnleashGaugeBehaviour)
|
|
{
|
|
{ "Original", EUnleashGaugeBehaviour::Original },
|
|
{ "Revised", EUnleashGaugeBehaviour::Revised }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(ETimeOfDayTransition)
|
|
{
|
|
{ "Xbox", ETimeOfDayTransition::Xbox },
|
|
{ "PlayStation", ETimeOfDayTransition::PlayStation }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(ECameraRotationMode)
|
|
{
|
|
{ "Normal", ECameraRotationMode::Normal },
|
|
{ "Reverse", ECameraRotationMode::Reverse },
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons)
|
|
{
|
|
{ "Auto", EControllerIcons::Auto },
|
|
{ "Xbox", EControllerIcons::Xbox },
|
|
{ "PlayStation", EControllerIcons::PlayStation }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EChannelConfiguration)
|
|
{
|
|
{ "Stereo", EChannelConfiguration::Stereo },
|
|
{ "Surround", EChannelConfiguration::Surround }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage)
|
|
{
|
|
{ "English", EVoiceLanguage::English },
|
|
{ "Japanese", EVoiceLanguage::Japanese }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI)
|
|
{
|
|
#ifdef UNLEASHED_RECOMP_D3D12
|
|
{ "D3D12", EGraphicsAPI::D3D12 },
|
|
#endif
|
|
{ "Vulkan", EGraphicsAPI::Vulkan }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState)
|
|
{
|
|
{ "Normal", EWindowState::Normal },
|
|
{ "Maximised", EWindowState::Maximised },
|
|
{ "Maximized", EWindowState::Maximised }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio)
|
|
{
|
|
{ "Auto", EAspectRatio::Auto },
|
|
{ "16:9", EAspectRatio::Wide },
|
|
{ "4:3", EAspectRatio::Narrow },
|
|
{ "Original 4:3", EAspectRatio::OriginalNarrow },
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering)
|
|
{
|
|
{ "Auto", ETripleBuffering::Auto },
|
|
{ "On", ETripleBuffering::On },
|
|
{ "Off", ETripleBuffering::Off }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing)
|
|
{
|
|
{ "None", EAntiAliasing::None },
|
|
{ "2x MSAA", EAntiAliasing::MSAA2x },
|
|
{ "4x MSAA", EAntiAliasing::MSAA4x },
|
|
{ "8x MSAA", EAntiAliasing::MSAA8x }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution)
|
|
{
|
|
{ "Original", EShadowResolution::Original },
|
|
{ "512", EShadowResolution::x512 },
|
|
{ "1024", EShadowResolution::x1024 },
|
|
{ "2048", EShadowResolution::x2048 },
|
|
{ "4096", EShadowResolution::x4096 },
|
|
{ "8192", EShadowResolution::x8192 },
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EGITextureFiltering)
|
|
{
|
|
{ "Bilinear", EGITextureFiltering::Bilinear },
|
|
{ "Bicubic", EGITextureFiltering::Bicubic }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EDepthOfFieldQuality)
|
|
{
|
|
{ "Auto", EDepthOfFieldQuality::Auto },
|
|
{ "Low", EDepthOfFieldQuality::Low },
|
|
{ "Medium", EDepthOfFieldQuality::Medium },
|
|
{ "High", EDepthOfFieldQuality::High },
|
|
{ "Ultra", EDepthOfFieldQuality::Ultra }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EMotionBlur)
|
|
{
|
|
{ "Off", EMotionBlur::Off },
|
|
{ "Original", EMotionBlur::Original },
|
|
{ "Enhanced", EMotionBlur::Enhanced }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(ECutsceneAspectRatio)
|
|
{
|
|
{ "Original", ECutsceneAspectRatio::Original },
|
|
{ "Unlocked", ECutsceneAspectRatio::Unlocked }
|
|
};
|
|
|
|
CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode)
|
|
{
|
|
{ "Edge", EUIScaleMode::Edge },
|
|
{ "Centre", EUIScaleMode::Centre },
|
|
{ "Center", EUIScaleMode::Centre }
|
|
};
|
|
|
|
#undef CONFIG_DEFINE
|
|
#define CONFIG_DEFINE(section, type, name, defaultValue) \
|
|
ConfigDef<type> Config::name{section, #name, defaultValue};
|
|
|
|
#undef CONFIG_DEFINE_HIDDEN
|
|
#define CONFIG_DEFINE_HIDDEN(section, type, name, defaultValue) \
|
|
ConfigDef<type, true> Config::name{section, #name, defaultValue};
|
|
|
|
#undef CONFIG_DEFINE_LOCALISED
|
|
#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \
|
|
extern CONFIG_LOCALE g_##name##_locale; \
|
|
ConfigDef<type> Config::name{section, #name, &g_##name##_locale, defaultValue};
|
|
|
|
#undef CONFIG_DEFINE_ENUM
|
|
#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \
|
|
ConfigDef<type> Config::name{section, #name, defaultValue, &g_##type##_template};
|
|
|
|
#undef CONFIG_DEFINE_ENUM_LOCALISED
|
|
#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue) \
|
|
extern CONFIG_LOCALE g_##name##_locale; \
|
|
extern CONFIG_ENUM_LOCALE(type) g_##type##_locale; \
|
|
ConfigDef<type> Config::name{section, #name, &g_##name##_locale, defaultValue, &g_##type##_template, &g_##type##_locale};
|
|
|
|
#undef CONFIG_DEFINE_CALLBACK
|
|
#define CONFIG_DEFINE_CALLBACK(section, type, name, defaultValue, readCallback) \
|
|
extern CONFIG_LOCALE g_##name##_locale; \
|
|
ConfigDef<type> Config::name{section, #name, defaultValue, [](ConfigDef<type>* def) readCallback};
|
|
|
|
#include "config_def.h"
|
|
|
|
// CONFIG_DEFINE
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue)
|
|
{
|
|
g_configDefinitions.emplace_back(this);
|
|
}
|
|
|
|
// CONFIG_DEFINE_LOCALISED
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue)
|
|
{
|
|
g_configDefinitions.emplace_back(this);
|
|
}
|
|
|
|
// CONFIG_DEFINE_ENUM
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T>* enumTemplate) : Section(section), Name(name), DefaultValue(defaultValue), EnumTemplate(enumTemplate)
|
|
{
|
|
for (const auto& pair : *EnumTemplate)
|
|
EnumTemplateReverse[pair.second] = pair.first;
|
|
|
|
g_configDefinitions.emplace_back(this);
|
|
}
|
|
|
|
// CONFIG_DEFINE_ENUM_LOCALISED
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue), EnumTemplate(enumTemplate), EnumLocale(enumLocale)
|
|
{
|
|
for (const auto& pair : *EnumTemplate)
|
|
EnumTemplateReverse[pair.second] = pair.first;
|
|
|
|
g_configDefinitions.emplace_back(this);
|
|
}
|
|
|
|
// CONFIG_DEFINE_CALLBACK
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T, isHidden>*)> callback) : Section(section), Name(name), DefaultValue(defaultValue), Callback(callback)
|
|
{
|
|
g_configDefinitions.emplace_back(this);
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
ConfigDef<T, isHidden>::~ConfigDef() = default;
|
|
|
|
template<typename T, bool isHidden>
|
|
bool ConfigDef<T, isHidden>::IsHidden()
|
|
{
|
|
return isHidden && !IsLoadedFromConfig;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
void ConfigDef<T, isHidden>::ReadValue(toml::v3::ex::parse_result& toml)
|
|
{
|
|
if (auto pSection = toml[Section].as_table())
|
|
{
|
|
const auto& section = *pSection;
|
|
|
|
if constexpr (std::is_same<T, std::string>::value)
|
|
{
|
|
Value = section[Name].value_or<std::string>(DefaultValue);
|
|
}
|
|
else if constexpr (std::is_enum_v<T>)
|
|
{
|
|
auto value = section[Name].value_or(std::string());
|
|
auto it = EnumTemplate->find(value);
|
|
|
|
if (it != EnumTemplate->end())
|
|
{
|
|
Value = it->second;
|
|
}
|
|
else
|
|
{
|
|
Value = DefaultValue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Value = section[Name].value_or(DefaultValue);
|
|
}
|
|
|
|
if (Callback)
|
|
Callback(this);
|
|
|
|
if (pSection->contains(Name))
|
|
IsLoadedFromConfig = true;
|
|
}
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
void ConfigDef<T, isHidden>::MakeDefault()
|
|
{
|
|
Value = DefaultValue;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string_view ConfigDef<T, isHidden>::GetSection() const
|
|
{
|
|
return Section;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string_view ConfigDef<T, isHidden>::GetName() const
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::GetNameLocalised(ELanguage language) const
|
|
{
|
|
if (!Locale)
|
|
return Name;
|
|
|
|
if (!Locale->count(language))
|
|
{
|
|
if (Locale->count(ELanguage::English))
|
|
{
|
|
return std::get<0>(Locale->at(ELanguage::English));
|
|
}
|
|
else
|
|
{
|
|
return Name;
|
|
}
|
|
}
|
|
|
|
return std::get<0>(Locale->at(language));
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::GetDescription(ELanguage language) const
|
|
{
|
|
if (!Locale)
|
|
return "";
|
|
|
|
if (!Locale->count(language))
|
|
{
|
|
if (Locale->count(ELanguage::English))
|
|
{
|
|
return std::get<1>(Locale->at(ELanguage::English));
|
|
}
|
|
else
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
return std::get<1>(Locale->at(language));
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
bool ConfigDef<T, isHidden>::IsDefaultValue() const
|
|
{
|
|
return Value == DefaultValue;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
const void* ConfigDef<T, isHidden>::GetValue() const
|
|
{
|
|
return &Value;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::GetValueLocalised(ELanguage language) const
|
|
{
|
|
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
|
|
|
|
if constexpr (std::is_enum_v<T>)
|
|
{
|
|
locale = EnumLocale;
|
|
}
|
|
else if constexpr (std::is_same_v<T, bool>)
|
|
{
|
|
return Value
|
|
? Localise("Common_On")
|
|
: Localise("Common_Off");
|
|
}
|
|
|
|
if (!locale)
|
|
return ToString(false);
|
|
|
|
if (!locale->count(language))
|
|
{
|
|
if (locale->count(ELanguage::English))
|
|
{
|
|
language = ELanguage::English;
|
|
}
|
|
else
|
|
{
|
|
return ToString(false);
|
|
}
|
|
}
|
|
|
|
auto strings = locale->at(language);
|
|
|
|
if (!strings.count(Value))
|
|
return ToString(false);
|
|
|
|
return std::get<0>(strings.at(Value));
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::GetValueDescription(ELanguage language) const
|
|
{
|
|
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
|
|
|
|
if constexpr (std::is_enum_v<T>)
|
|
{
|
|
locale = EnumLocale;
|
|
}
|
|
else if constexpr (std::is_same_v<T, bool>)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
if (!locale)
|
|
return "";
|
|
|
|
if (!locale->count(language))
|
|
{
|
|
if (locale->count(ELanguage::English))
|
|
{
|
|
language = ELanguage::English;
|
|
}
|
|
else
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
auto strings = locale->at(language);
|
|
|
|
if (!strings.count(Value))
|
|
return "";
|
|
|
|
return std::get<1>(strings.at(Value));
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::GetDefinition(bool withSection) const
|
|
{
|
|
std::string result;
|
|
|
|
if (withSection)
|
|
result += "[" + Section + "]\n";
|
|
|
|
result += Name + " = " + ToString();
|
|
|
|
return result;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
std::string ConfigDef<T, isHidden>::ToString(bool strWithQuotes) const
|
|
{
|
|
std::string result = "N/A";
|
|
|
|
if constexpr (std::is_same_v<T, std::string>)
|
|
{
|
|
result = fmt::format("{}", Value);
|
|
|
|
if (strWithQuotes)
|
|
result = fmt::format("\"{}\"", result);
|
|
}
|
|
else if constexpr (std::is_enum_v<T>)
|
|
{
|
|
auto it = EnumTemplateReverse.find(Value);
|
|
|
|
if (it != EnumTemplateReverse.end())
|
|
result = fmt::format("{}", it->second);
|
|
|
|
if (strWithQuotes)
|
|
result = fmt::format("\"{}\"", result);
|
|
}
|
|
else
|
|
{
|
|
result = fmt::format("{}", Value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
template<typename T, bool isHidden>
|
|
void ConfigDef<T, isHidden>::GetLocaleStrings(std::vector<std::string_view>& localeStrings) const
|
|
{
|
|
if (Locale != nullptr)
|
|
{
|
|
for (auto& [language, nameAndDesc] : *Locale)
|
|
{
|
|
localeStrings.push_back(std::get<0>(nameAndDesc));
|
|
localeStrings.push_back(std::get<1>(nameAndDesc));
|
|
}
|
|
}
|
|
|
|
if (EnumLocale != nullptr)
|
|
{
|
|
for (auto& [language, locale] : *EnumLocale)
|
|
{
|
|
for (auto& [value, nameAndDesc] : locale)
|
|
{
|
|
localeStrings.push_back(std::get<0>(nameAndDesc));
|
|
localeStrings.push_back(std::get<1>(nameAndDesc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::filesystem::path Config::GetConfigPath()
|
|
{
|
|
return GetUserPath() / "config.toml";
|
|
}
|
|
|
|
void Config::Load()
|
|
{
|
|
auto configPath = GetConfigPath();
|
|
|
|
if (!std::filesystem::exists(configPath))
|
|
{
|
|
Config::Save();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
toml::parse_result toml;
|
|
std::ifstream tomlStream(configPath);
|
|
|
|
if (tomlStream.is_open())
|
|
toml = toml::parse(tomlStream);
|
|
|
|
for (auto def : g_configDefinitions)
|
|
{
|
|
def->ReadValue(toml);
|
|
|
|
#if _DEBUG
|
|
LOGFN_UTILITY("{} (0x{:X})", def->GetDefinition().c_str(), (intptr_t)def->GetValue());
|
|
#endif
|
|
}
|
|
}
|
|
catch (toml::parse_error& err)
|
|
{
|
|
LOGFN_ERROR("Failed to parse configuration: {}", err.what());
|
|
}
|
|
}
|
|
|
|
void Config::Save()
|
|
{
|
|
auto userPath = GetUserPath();
|
|
|
|
if (!std::filesystem::exists(userPath))
|
|
std::filesystem::create_directory(userPath);
|
|
|
|
std::string result;
|
|
std::string section;
|
|
|
|
for (auto def : g_configDefinitions)
|
|
{
|
|
if (def->IsHidden())
|
|
continue;
|
|
|
|
auto isFirstSection = section.empty();
|
|
auto isDefWithSection = section != def->GetSection();
|
|
auto tomlDef = def->GetDefinition(isDefWithSection);
|
|
|
|
section = def->GetSection();
|
|
|
|
// Don't output prefix space for first section.
|
|
if (!isFirstSection && isDefWithSection)
|
|
result += '\n';
|
|
|
|
result += tomlDef + '\n';
|
|
}
|
|
|
|
std::ofstream out(GetConfigPath());
|
|
|
|
if (out.is_open())
|
|
{
|
|
out << result;
|
|
out.close();
|
|
}
|
|
else
|
|
{
|
|
LOGN_ERROR("Failed to write configuration.");
|
|
}
|
|
}
|