diff --git a/src/client/component/config.cpp b/src/client/component/config.cpp index afd3686e..96d56631 100644 --- a/src/client/component/config.cpp +++ b/src/client/component/config.cpp @@ -3,6 +3,7 @@ #include "config.hpp" #include "console.hpp" +#include "language.hpp" #include #include @@ -11,6 +12,55 @@ namespace config { + namespace + { + using validate_type_callback_t = std::function; + using validate_callback_t = std::function; + + struct field_definition_t + { + field_type type; + field_value default_value; + std::optional validate_value = {}; + }; + + template + std::pair define_field(const std::string& name, Args&&... args) + { + return std::make_pair(name, field_definition_t{std::forward(args)...}); + } + + std::unordered_map field_definitions = + { + {define_field("language", config::field_type::string, language::get_default_language(), language::is_valid_language)}, + }; + } + + nlohmann::json validate_config_field(const std::string& key, const nlohmann::json& value) + { + const auto iter = field_definitions.find(key); + if (iter == field_definitions.end()) + { + return value; + } + + if (value.type() != iter->second.type) + { + return iter->second.default_value; + } + + if (iter->second.validate_value.has_value()) + { + const auto& validate_value = iter->second.validate_value.value(); + if (!validate_value(value)) + { + iter->second.default_value; + } + } + + return value; + } + void write_config(const nlohmann::json& json) { try diff --git a/src/client/component/config.hpp b/src/client/component/config.hpp index 91395404..0066050b 100644 --- a/src/client/component/config.hpp +++ b/src/client/component/config.hpp @@ -2,9 +2,14 @@ namespace config { + typedef nlohmann::json::value_t field_type; + typedef nlohmann::json field_value; + nlohmann::json read_config(); void write_config(const nlohmann::json& json); + nlohmann::json validate_config_field(const std::string& key, const field_value& value); + template std::optional get(const std::string& key) { @@ -14,14 +19,15 @@ namespace config return {}; } - return {cfg[key].get()}; + const auto value = validate_config_field(key, cfg[key]); + return {value.get()}; } template void set(const std::string& key, const T& value) { auto cfg = read_config(); - cfg[key] = value; + cfg[key] = validate_config_field(key, value); write_config(cfg); } } diff --git a/src/client/component/database.cpp b/src/client/component/database.cpp index bcf44034..5bcadb97 100644 --- a/src/client/component/database.cpp +++ b/src/client/component/database.cpp @@ -7,6 +7,7 @@ #include "filesystem.hpp" #include "console.hpp" #include "command.hpp" +#include "language.hpp" #include #include @@ -116,25 +117,6 @@ namespace database return file.ends_with(".ff") || file.ends_with(".pak"); } - std::unordered_set unsuppored_languages = - { - {game::LANGUAGE_CZECH} - }; - - bool is_unsupported_language(const std::string& name) - { - for (auto i = 0; i < 17; i++) - { - if ((game::languages[i].name == name || game::languages[i].shortname == name) && - unsuppored_languages.contains(static_cast(i))) - { - return true; - } - } - - return false; - } - bool is_loc_folder(game::Sys_Folder folder) { return folder == game::SF_PAKFILE_LOC || folder == game::SF_ZONE_LOC || folder == game::SF_VIDEO_LOC; @@ -160,7 +142,7 @@ namespace database { const auto loc = name.substr(0, 3); const auto found = find_fastfile(name); - if (is_unsupported_language(loc) && !found.has_value()) + if (language::is_custom_language(loc) && !found.has_value()) { name = "eng" + name.substr(3); file_ = name; @@ -421,7 +403,7 @@ namespace database utils::hook::detour sys_set_folder_hook; void sys_set_folder_stub(game::Sys_Folder folder, const char* path) { - if (is_loc_folder(folder) && is_unsupported_language(path)) + if (is_loc_folder(folder) && language::is_custom_language(path)) { path = "english"; } diff --git a/src/client/component/language.cpp b/src/client/component/language.cpp index e00bc099..09e01060 100644 --- a/src/client/component/language.cpp +++ b/src/client/component/language.cpp @@ -8,6 +8,9 @@ #include #include +#include + +#define OLD_LANGUAGE_FILE "players2/default/language" namespace language { @@ -27,20 +30,25 @@ namespace language {game::LANGUAGE_RUSSIAN_PARTIAL}, }; - std::unordered_set cyrillic_languages = + std::unordered_set polish_russian_languages = { - game::LANGUAGE_RUSSIAN, - game::LANGUAGE_POLISH, - game::LANGUAGE_CZECH, - game::LANGUAGE_RUSSIAN_PARTIAL, + {game::LANGUAGE_RUSSIAN}, + {game::LANGUAGE_POLISH}, + {game::LANGUAGE_CZECH}, + {game::LANGUAGE_RUSSIAN_PARTIAL}, }; std::unordered_set asian_languages = { - game::LANGUAGE_JAPANESE_FULL, - game::LANGUAGE_JAPANESE_PARTIAL, - game::LANGUAGE_TRADITIONAL_CHINESE, - game::LANGUAGE_SIMPLIFIED_CHINESE, + {game::LANGUAGE_JAPANESE_FULL}, + {game::LANGUAGE_JAPANESE_PARTIAL}, + {game::LANGUAGE_TRADITIONAL_CHINESE}, + {game::LANGUAGE_SIMPLIFIED_CHINESE}, + }; + + std::unordered_set custom_languages = + { + {game::LANGUAGE_CZECH}, }; const char* get_loc_language_string() @@ -58,6 +66,40 @@ namespace language } } + std::string get_default_language() + { + return "english"; + } + + bool is_valid_language(const std::string& name) + { + const auto lower = utils::string::to_lower(name); + for (auto i = 0; i < game::LANGUAGE_COUNT; i++) + { + const auto a = game::languages[i].name; + const auto b = game::languages[i].is_supported; + if (game::languages[i].name == lower) + { + return true; + } + } + + return false; + } + + bool is_custom_language(const std::string& name) + { + for (const auto& language : custom_languages) + { + if (game::languages[language].name == name || game::languages[language].shortname == name) + { + return true; + } + } + + return false; + } + game::language_t current() { static auto* loc_language = game::Dvar_FindVar("loc_language"); @@ -71,7 +113,7 @@ namespace language bool is_polrus() { - return cyrillic_languages.contains(current()); + return polish_russian_languages.contains(current()); } bool is_arabic() @@ -91,7 +133,7 @@ namespace language void set_from_index(const int index) { - if (index < 0 || index > 17) + if (index < 0 || index >= game::LANGUAGE_COUNT) { return; } @@ -107,9 +149,16 @@ namespace language { utils::hook::call(0x14060AFFB, get_loc_language_string); - for (auto i = 0; i < 17; i++) + if (utils::io::file_exists(OLD_LANGUAGE_FILE)) { - game::languages[i].is_supported = 1; + const auto lang = utils::io::read_file(OLD_LANGUAGE_FILE); + config::set("language", lang); + utils::io::remove_file(OLD_LANGUAGE_FILE); + } + + for (const auto& language : custom_languages) + { + game::languages[language].is_supported = 1; } } }; diff --git a/src/client/component/language.hpp b/src/client/component/language.hpp index e1a54eea..a549951a 100644 --- a/src/client/component/language.hpp +++ b/src/client/component/language.hpp @@ -4,6 +4,10 @@ namespace language { + std::string get_default_language(); + bool is_valid_language(const std::string& name); + bool is_custom_language(const std::string& name); + void set(const std::string& language); void set_from_index(const int index);