diff --git a/FEXCore/Source/CMakeLists.txt b/FEXCore/Source/CMakeLists.txt index d1bc94211e..873435f25f 100644 --- a/FEXCore/Source/CMakeLists.txt +++ b/FEXCore/Source/CMakeLists.txt @@ -6,7 +6,9 @@ set(FEXCORE_BASE_SRCS Utils/FileLoading.cpp Utils/ForcedAssert.cpp Utils/LogManager.cpp - Utils/SpinWaitLock.cpp) + Utils/SpinWaitLock.cpp + Utils/WildcardMatcher.cpp + ) if (NOT MINGW) list(APPEND FEXCORE_BASE_SRCS diff --git a/FEXCore/Source/Utils/WildcardMatcher.cpp b/FEXCore/Source/Utils/WildcardMatcher.cpp new file mode 100644 index 0000000000..9dfa8e15f4 --- /dev/null +++ b/FEXCore/Source/Utils/WildcardMatcher.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +#include +#include "FEXCore/fextl/map.h" +namespace FEXCore::Utils::Wildcard { +class WildcardMatcher { +private: + std::string_view pattern; + std::string_view text; + fextl::map, bool> cache; + +public: + WildcardMatcher(std::string_view pattern, std::string_view text) + : pattern(pattern) + , text(text) {} + + bool matchHelper(size_t p_idx, size_t t_idx); +}; + +bool WildcardMatcher::matchHelper(size_t p_idx, size_t t_idx) { + auto key = std::make_pair(p_idx, t_idx); + + // Check cache + if (auto it = cache.find(key); it != cache.end()) { + return it->second; + } + + bool result; + + // Pattern exhausted + if (p_idx == pattern.size()) { + result = (t_idx == text.size()); + } + // Wildcard + else if (pattern[p_idx] == '*') { + // Try matching zero characters, or one or more characters + result = matchHelper(p_idx + 1, t_idx) || (t_idx < text.size() && matchHelper(p_idx, t_idx + 1)); + } + // Match normally + else { + result = (t_idx < text.size() && pattern[p_idx] == text[t_idx] && matchHelper(p_idx + 1, t_idx + 1)); + } + cache[key] = result; + return result; +} + +bool Matches(std::string_view pattern, std::string_view text) { + WildcardMatcher matcher(pattern, text); + return matcher.matchHelper(0, 0); +} + +} // namespace FEXCore::Utils::Wildcard diff --git a/FEXCore/include/FEXCore/Utils/WildcardMatcher.h b/FEXCore/include/FEXCore/Utils/WildcardMatcher.h new file mode 100644 index 0000000000..6bee8d5a06 --- /dev/null +++ b/FEXCore/include/FEXCore/Utils/WildcardMatcher.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +#pragma once +#include + +namespace FEXCore::Utils::Wildcard { +bool Matches(std::string_view pattern, std::string_view text); +} // namespace FEXCore::Utils::Wildcard diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp index 25614405dc..7f638f803f 100644 --- a/Source/Common/Config.cpp +++ b/Source/Common/Config.cpp @@ -2,6 +2,7 @@ #include "Common/ArgumentLoader.h" #include "Common/Config.h" #include "Common/JSONPool.h" +#include "FEXCore/fextl/vector.h" #include #include @@ -10,7 +11,7 @@ #include #include #include - +#include #include #include #ifndef _WIN32 @@ -43,21 +44,50 @@ namespace JSON { return; } - for (const json_t* ConfigItem = json_getChild(ConfigList); ConfigItem != nullptr; ConfigItem = json_getSibling(ConfigItem)) { - const char* ConfigName = json_getName(ConfigItem); - const char* ConfigString = json_getValue(ConfigItem); + fextl::vector ConfigBlocks; + ConfigBlocks.push_back(ConfigList); - if (!ConfigName) { - LogMan::Msg::EFmt("JSON file '{}': Couldn't get config name for an item", Config); - return; - } + const json_t* OverrideList = json_getProperty(json, "AppOverrides"); + if (OverrideList) { + for (const json_t* Item = json_getChild(OverrideList); Item != nullptr; Item = json_getSibling(Item)) { + const char* ItemName = json_getName(Item); + const json_t* OverrideNamedList = json_getProperty(OverrideList, ItemName); + + if (!ItemName) { + LogMan::Msg::EFmt("JSON file '{}': Couldn't get config name for an item", Config); + break; + } - if (!ConfigString) { - LogMan::Msg::EFmt("JSON file '{}': Couldn't get value for config item '{}'", Config, ConfigName); - return; + if (!OverrideNamedList) { + LogMan::Msg::EFmt("JSON file '{}': Couldn't get value for config item '{}'", Config, ItemName); + break; + } + + // Matches the first and then get out + if (FEXCore::Utils::Wildcard::Matches(ItemName, Config)) { + // Safe to assume its just pairs of strings at this point? + ConfigBlocks.push_back(OverrideNamedList); + break; + } } + } + + for (auto ConfigBlock : ConfigBlocks) { + for (const json_t* ConfigItem = json_getChild(ConfigBlock); ConfigItem != nullptr; ConfigItem = json_getSibling(ConfigItem)) { + const char* ConfigName = json_getName(ConfigItem); + const char* ConfigString = json_getValue(ConfigItem); + + if (!ConfigName) { + LogMan::Msg::EFmt("JSON file '{}': Couldn't get config name for an item", Config); + return; + } - Func(ConfigName, ConfigString); + if (!ConfigString) { + LogMan::Msg::EFmt("JSON file '{}': Couldn't get value for config item '{}'", Config, ConfigName); + return; + } + Func(ConfigName, ConfigString); + } } } } // namespace JSON diff --git a/unittests/APITests/CMakeLists.txt b/unittests/APITests/CMakeLists.txt index 2e472e8d9f..f4eda80223 100644 --- a/unittests/APITests/CMakeLists.txt +++ b/unittests/APITests/CMakeLists.txt @@ -6,7 +6,9 @@ set(TESTS FileMappingBaseAddress Filesystem InterruptableConditionVariable - StringUtils) + StringUtils + WildcardMatcher + ) list(APPEND LIBS Common FEXCore JemallocLibs) diff --git a/unittests/APITests/WildcardMatcher.cpp b/unittests/APITests/WildcardMatcher.cpp new file mode 100644 index 0000000000..160dc89f6b --- /dev/null +++ b/unittests/APITests/WildcardMatcher.cpp @@ -0,0 +1,48 @@ +#include "FEXCore/fextl/string.h" +#include +#include + +using namespace FEXCore::Utils::Wildcard; + +TEST_CASE("Singular regex") { + CHECK(Matches("a", "a")); + CHECK(Matches("a*", "a*")); + CHECK(Matches("a*", "aaaaaaa")); +} + +TEST_CASE("Concat regex") { + CHECK(Matches("aaa", "aaa")); + CHECK(Matches("ab", "ab")); + CHECK(!Matches("a", "ab")); + CHECK(!Matches("ab", "a")); +} +TEST_CASE("Wildcard beginning end") { + CHECK(Matches("a*", "a")); + CHECK(Matches("*a", "a")); + CHECK(Matches("*a*", "a")); +} +TEST_CASE("Wildcard middle") { + CHECK(Matches("test*pattern", "test__pattern")); +} + +TEST_CASE("Wildcard mult") { + CHECK(Matches("test*pattern*more", "test__pattern__more")); + CHECK(Matches("test**pattern", "test_pattern")); +} + +TEST_CASE("Wildcard regex simple") { + CHECK(Matches("*", "")); + CHECK(Matches("*", "setup.json")); + CHECK(Matches("test*pattern", "test__pattern")); + CHECK(!Matches("setup.*", "setupjson")); + CHECK(Matches("setup*", "setup.json")); + CHECK(Matches("setup*", "setup/setup.json")); + CHECK(Matches("*setup*", "setup/setup.json")); +} + + +// Tests potential usage inside fex itself +TEST_CASE("FEX regex") { + CHECK(Matches("*Config*", "/home/ubuntu/.fex-emu/Config.json")); + CHECK(Matches("*Config.json", "/home/ubuntu/.fex-emu/Config.json")); +}