#include "Common/Config.h" #include "LogManager.h" #include #include #include #include #include #include #include #include #include #include #include namespace FEX::Config { std::unordered_map> ConfigMap; struct JsonAllocator { jsonPool_t PoolObject; std::unique_ptr> json_objects; }; std::string GetConfigFileLocation() { char const *HomeDir = getenv("HOME"); if (!HomeDir) { HomeDir = getenv("PWD"); } if (!HomeDir) { HomeDir = ""; } char const *ConfigXDG = getenv("XDG_CONFIG_HOME"); std::string ConfigDir = ConfigXDG ? ConfigXDG : HomeDir; ConfigDir += "/.fex-emu/"; // Ensure the folder structure is created for our configuration if (!std::filesystem::exists(ConfigDir) && !std::filesystem::create_directories(ConfigDir)) { LogMan::Msg::D("Couldn't create config directory: '%s'", ConfigDir.c_str()); // Let's go local in this case return "Config.json"; } std::string ConfigFile = ConfigDir; ConfigFile += "Config.json"; return ConfigFile; } json_t* PoolInit(jsonPool_t* Pool) { JsonAllocator* alloc = json_containerOf(Pool, JsonAllocator, PoolObject); alloc->json_objects = std::make_unique>(); return &*alloc->json_objects->emplace(alloc->json_objects->end()); } json_t* PoolAlloc(jsonPool_t* Pool) { JsonAllocator* alloc = json_containerOf(Pool, JsonAllocator, PoolObject); return &*alloc->json_objects->emplace(alloc->json_objects->end()); } bool LoadConfigFile(std::vector &Data) { std::fstream ConfigFile; ConfigFile.open(GetConfigFileLocation(), std::fstream::in); if (!ConfigFile.is_open()) { return false; } if (!ConfigFile.seekg(0, std::fstream::end)) { LogMan::Msg::D("Couldn't load configuration file: Seek end"); return false; } size_t FileSize = ConfigFile.tellg(); if (ConfigFile.fail()) { LogMan::Msg::D("Couldn't load configuration file: tellg"); return false; } if (!ConfigFile.seekg(0, std::fstream::beg)) { LogMan::Msg::D("Couldn't load configuration file: Seek beginning"); return false; } if (FileSize > 0) { Data.resize(FileSize); if (ConfigFile.read(&Data.at(0), FileSize)) { // Probably means permissions aren't set. Just early exit return false; } ConfigFile.close(); } else { return false; } return true; } void Init() { std::vector Data; if (!LoadConfigFile(Data)) { return; } JsonAllocator Pool { .PoolObject = { .init = PoolInit, .alloc = PoolAlloc, }, }; json_t const *json = json_createWithPool(&Data.at(0), &Pool.PoolObject); if (!json) { LogMan::Msg::E("Couldn't create json"); return; } json_t const* ConfigList = json_getProperty(json, "Config"); if (!ConfigList) { LogMan::Msg::E("Couldn't get config list"); return; } for (json_t const* ConfigItem = json_getChild(ConfigList); ConfigItem != nullptr; ConfigItem = json_getSibling(ConfigItem)) { const char* ConfigName = json_getName(ConfigItem); const char* ConfigString = json_getValue(ConfigItem); if (!ConfigName) { LogMan::Msg::E("Couldn't get config name"); return; } if (!ConfigString) { LogMan::Msg::E("Couldn't get ConfigString for '%s'", ConfigName); return; } FEX::Config::Add(ConfigName, ConfigString); } } void Shutdown() { char Buffer[4096]; char *Dest; Dest = json_objOpen(Buffer, nullptr); Dest = json_objOpen(Dest, "Config"); for (auto &it : ConfigMap) { if (!it.second().empty()) { Dest = json_str(Dest, it.first.data(), it.second().c_str()); } } Dest = json_objClose(Dest); Dest = json_objClose(Dest); json_end(Dest); // Write the config to a temporary file first then move it to the correct location // This is to solve threading issues when multiple applications are loading and storing the configuration std::string Config = GetConfigFileLocation(); std::string ConfigTmp{Config}; ConfigTmp.resize(Config.size() + 6); auto it = ConfigTmp.rbegin(); for (int i = 6; i > 0; --i) { *it = 'X'; ++it; } int tmp = mkstemp(&ConfigTmp.at(0)); write(tmp, Buffer, strlen(Buffer)); close(tmp); // Rename only works if ther files are in the same filesystem rename(ConfigTmp.c_str(), GetConfigFileLocation().c_str()); ConfigMap.clear(); } void Add(std::string const &Key, std::string_view const Value) { FEX::Config::Value Val{Key, Value, true}; ConfigMap.try_emplace(Key, Val); } void Append(std::string const &Key, std::string_view const Value) { auto Iter = ConfigMap.find(Key); if (Iter == ConfigMap.end()) { FEX::Config::Value Val{Key, "", true}; Iter = ConfigMap.try_emplace(Key, Val).first; } Iter->second.Append(std::string(Value)); } bool Exists(std::string const &Key) { return ConfigMap.find(Key) != ConfigMap.end(); } Value &Get(std::string const &Key) { auto Value = ConfigMap.find(Key); if (Value == ConfigMap.end()) assert(0 && "Not a real config value"); return Value->second; } Value *GetIfExists(std::string const &Key) { auto Value = ConfigMap.find(Key); if (Value == ConfigMap.end()) return nullptr; return &Value->second; } template T Get(std::string const &Key) { T Value; if (!FEX::StrConv::Conv(Get(Key)(), &Value)) { assert(0 && "Attempted to convert invalid value"); } return Value; } template T GetIfExists(std::string const &Key, T Default) { T Value; if (Exists(Key) && FEX::StrConv::Conv(FEX::Config::Get(Key)(), &Value)) { return Value; } else { return Default; } } template<> std::string GetIfExists(std::string const &Key, std::string Default) { auto Res = GetIfExists(Key); if (Res) { return (*Res)(); } else { return Default; } } template bool GetIfExists(std::string const &Key, bool Default); template uint8_t GetIfExists(std::string const &Key, uint8_t Default); template uint64_t GetIfExists(std::string const &Key, uint64_t Default); void GetListIfExists(std::string const &Key, std::list *List) { List->clear(); auto Res = GetIfExists(Key); if (Res) { *List = Res->All(); } } }