Skip to content
4 changes: 3 additions & 1 deletion FEXCore/Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions FEXCore/Source/Utils/WildcardMatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

#include <FEXCore/Utils/WildcardMatcher.h>
#include "FEXCore/fextl/map.h"
namespace FEXCore::Utils::Wildcard {
class WildcardMatcher {
private:
std::string_view pattern;
std::string_view text;
fextl::map<std::pair<size_t, size_t>, 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
8 changes: 8 additions & 0 deletions FEXCore/include/FEXCore/Utils/WildcardMatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT

#pragma once
#include <string_view>

namespace FEXCore::Utils::Wildcard {
bool Matches(std::string_view pattern, std::string_view text);
} // namespace FEXCore::Utils::Wildcard
54 changes: 42 additions & 12 deletions Source/Common/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "Common/ArgumentLoader.h"
#include "Common/Config.h"
#include "Common/JSONPool.h"
#include "FEXCore/fextl/vector.h"

#include <FEXCore/Config/Config.h>
#include <FEXCore/fextl/fmt.h>
Expand All @@ -10,7 +11,7 @@
#include <FEXCore/Utils/FileLoading.h>
#include <FEXHeaderUtils/Filesystem.h>
#include <FEXHeaderUtils/SymlinkChecks.h>

#include <FEXCore/Utils/WildcardMatcher.h>
#include <cstring>
#include <functional>
#ifndef _WIN32
Expand Down Expand Up @@ -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<const json_t*> 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
Expand Down
4 changes: 3 additions & 1 deletion unittests/APITests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ set(TESTS
FileMappingBaseAddress
Filesystem
InterruptableConditionVariable
StringUtils)
StringUtils
WildcardMatcher
)

list(APPEND LIBS Common FEXCore JemallocLibs)

Expand Down
48 changes: 48 additions & 0 deletions unittests/APITests/WildcardMatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "FEXCore/fextl/string.h"
#include <FEXCore/Utils/WildcardMatcher.h>
#include <catch2/catch_test_macros.hpp>

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"));
}
Loading