#include "config.h" #include #include #include std::vector g_configDefinitions; #define CONFIG_DEFINE_ENUM_TEMPLATE(type) \ static std::unordered_map 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(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 Config::name{section, #name, defaultValue}; #undef CONFIG_DEFINE_HIDDEN #define CONFIG_DEFINE_HIDDEN(section, type, name, defaultValue) \ ConfigDef Config::name{section, #name, defaultValue}; #undef CONFIG_DEFINE_LOCALISED #define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \ extern CONFIG_LOCALE g_##name##_locale; \ ConfigDef Config::name{section, #name, &g_##name##_locale, defaultValue}; #undef CONFIG_DEFINE_ENUM #define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \ ConfigDef 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 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 Config::name{section, #name, defaultValue, [](ConfigDef* def) readCallback}; #include "config_def.h" // CONFIG_DEFINE template ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue) { g_configDefinitions.emplace_back(this); } // CONFIG_DEFINE_LOCALISED template ConfigDef::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 ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map* 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 ConfigDef::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map* 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 ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue, std::function*)> callback) : Section(section), Name(name), DefaultValue(defaultValue), Callback(callback) { g_configDefinitions.emplace_back(this); } template ConfigDef::~ConfigDef() = default; template bool ConfigDef::IsHidden() { return isHidden && !IsLoadedFromConfig; } template void ConfigDef::ReadValue(toml::v3::ex::parse_result& toml) { if (auto pSection = toml[Section].as_table()) { const auto& section = *pSection; if constexpr (std::is_same::value) { Value = section[Name].value_or(DefaultValue); } else if constexpr (std::is_enum_v) { 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 void ConfigDef::MakeDefault() { Value = DefaultValue; } template std::string_view ConfigDef::GetSection() const { return Section; } template std::string_view ConfigDef::GetName() const { return Name; } template std::string ConfigDef::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 std::string ConfigDef::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 bool ConfigDef::IsDefaultValue() const { return Value == DefaultValue; } template const void* ConfigDef::GetValue() const { return &Value; } template std::string ConfigDef::GetValueLocalised(ELanguage language) const { CONFIG_ENUM_LOCALE(T)* locale = nullptr; if constexpr (std::is_enum_v) { locale = EnumLocale; } else if constexpr (std::is_same_v) { 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 std::string ConfigDef::GetValueDescription(ELanguage language) const { CONFIG_ENUM_LOCALE(T)* locale = nullptr; if constexpr (std::is_enum_v) { locale = EnumLocale; } else if constexpr (std::is_same_v) { 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 std::string ConfigDef::GetDefinition(bool withSection) const { std::string result; if (withSection) result += "[" + Section + "]\n"; result += Name + " = " + ToString(); return result; } template std::string ConfigDef::ToString(bool strWithQuotes) const { std::string result = "N/A"; if constexpr (std::is_same_v) { result = fmt::format("{}", Value); if (strWithQuotes) result = fmt::format("\"{}\"", result); } else if constexpr (std::is_enum_v) { 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 void ConfigDef::GetLocaleStrings(std::vector& 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."); } }