diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index c3fb3528e5..6e535f707f 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -804,15 +804,18 @@ std::string GetSysDirectory() return sysDir; } +// This returns the folder where certain configuration files are stored (i.e, +// `user.json`, etc). +// // On Linux platforms, the user.json file lives in the XDG_CONFIG_HOME/SlippiOnline // directory in order to deal with the fact that we want the configuration for AppImage // builds to be mutable. -std::string GetSlippiUserJSONPath() +std::string GetSlippiUserConfigFolder() { #if defined(__APPLE__) - std::string userFilePath = File::GetApplicationSupportDirectory() + "/Slippi/user.json"; + std::string userFilePath = File::GetApplicationSupportDirectory() + "/Slippi"; #else - std::string userFilePath = File::GetUserPath(F_USERJSON_IDX); + std::string userFilePath = File::GetUserPath(D_SLIPPI_IDX); #endif return userFilePath; } diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 2c2d30a961..0c80465a33 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -152,8 +152,8 @@ void SetUserPath(unsigned int dir_index, const std::string &path); // probably doesn't belong here std::string GetThemeDir(const std::string &theme_name); -// Gets the path where a Slippi user.json file lives. -std::string GetSlippiUserJSONPath(); +// Gets the path where Slippi config files (e.g, user.json) live. +std::string GetSlippiUserConfigFolder(); // Returns the path to where the sys file are std::string GetSysDirectory(); diff --git a/Source/Core/Core/HW/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI_DeviceSlippi.cpp index 63d4b15677..e563696d10 100644 --- a/Source/Core/Core/HW/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI_DeviceSlippi.cpp @@ -153,12 +153,12 @@ CEXISlippi::CEXISlippi() // https://github.com/dolphin-emu/dolphin/blob/7f450f1d7e7d37bd2300f3a2134cb443d07251f9/Source/Core/Core/Movie.cpp#L246-L249 std::string isoPath = SConfig::GetInstance().m_strFilename; - // @TODO: Eventually we should move `GetSlippiUserJSONPath` out of the File module. - std::string userJSONPath = File::GetSlippiUserJSONPath(); + // @TODO: Eventually we should move `GetSlippiUserConfigFolder` out of the File module. + std::string userConfigFolder = File::GetSlippiUserConfigFolder(); SlippiRustEXIConfig slprs_exi_config; slprs_exi_config.iso_path = isoPath.c_str(); - slprs_exi_config.user_json_path = userJSONPath.c_str(); + slprs_exi_config.user_config_folder = userConfigFolder.c_str(); slprs_exi_config.scm_slippi_semver_str = scm_slippi_semver_str.c_str(); slprs_exi_config.osd_add_msg_fn = OSDMessageHandler; @@ -170,8 +170,8 @@ CEXISlippi::CEXISlippi() matchmaking = std::make_unique(user.get()); gameFileLoader = std::make_unique(); g_replayComm = std::make_unique(); - directCodes = std::make_unique("direct-codes.json"); - teamsCodes = std::make_unique("teams-codes.json"); + directCodes = std::make_unique(slprs_exi_device_ptr, SlippiDirectCodes::DIRECT); + teamsCodes = std::make_unique(slprs_exi_device_ptr, SlippiDirectCodes::TEAMS); generator = std::default_random_engine(Common::Timer::GetTimeMs()); diff --git a/Source/Core/Core/Slippi/SlippiDirectCodes.cpp b/Source/Core/Core/Slippi/SlippiDirectCodes.cpp index 577484b7ea..1ac1d968e1 100644 --- a/Source/Core/Core/Slippi/SlippiDirectCodes.cpp +++ b/Source/Core/Core/Slippi/SlippiDirectCodes.cpp @@ -1,230 +1,41 @@ -#include "SlippiDirectCodes.h" - -#ifdef _WIN32 -#include "AtlBase.h" -#include "AtlConv.h" -#endif - -#include "Common/CommonPaths.h" -#include "Common/FileUtil.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" - -#include "Core/ConfigManager.h" +#include "SlippiRustExtensions.h" -#include -#include -#include - -#include -using json = nlohmann::json; - -SlippiDirectCodes::SlippiDirectCodes(std::string fileName) -{ - m_fileName = fileName; +#include "SlippiDirectCodes.h" - // Prevent additional file reads, if we've already loaded data to memory. - // if (directCodeInfos.empty()) - ReadFile(); - Sort(); +DirectCodeKind mapCode(uint8_t code_kind) { + return code_kind == SlippiDirectCodes::DIRECT ? + DirectCodeKind::DirectCodes : + DirectCodeKind::TeamsCodes; } -SlippiDirectCodes::~SlippiDirectCodes() +SlippiDirectCodes::SlippiDirectCodes(uintptr_t rs_exi_device_ptr, uint8_t code_kind) { - // Add additional cleanup behavior here? Just added something - // So compiler wouldn't nag. - return; + slprs_exi_device_ptr = rs_exi_device_ptr; + kind = code_kind; } -void SlippiDirectCodes::ReadFile() -{ - std::string directCodesFilePath = getCodesFilePath(); - - INFO_LOG(SLIPPI_ONLINE, "Looking for direct codes file at %s", directCodesFilePath.c_str()); - - if (!File::Exists(directCodesFilePath)) - { - // Attempt to create empty file with array as parent json item. - if (File::CreateFullPath(directCodesFilePath) && File::CreateEmptyFile(directCodesFilePath)) - { - File::WriteStringToFile("[\n]", directCodesFilePath); - } - else - { - WARN_LOG(SLIPPI_ONLINE, "Was unable to create %s", directCodesFilePath.c_str()); - } - } - - std::string directCodesFileContents; - File::ReadFileToString(directCodesFilePath, directCodesFileContents); - - directCodeInfos = parseFile(directCodesFileContents); -} +SlippiDirectCodes::~SlippiDirectCodes() {} void SlippiDirectCodes::AddOrUpdateCode(std::string code) { - WARN_LOG(SLIPPI_ONLINE, "Attempting to add or update direct code: %s", code.c_str()); - - time_t curTime; - time(&curTime); - u8 dateTimeStrLength = sizeof "20171015T095717"; - std::vector dateTimeBuf(dateTimeStrLength); - strftime(&dateTimeBuf[0], dateTimeStrLength, "%Y%m%dT%H%M%S", localtime(&curTime)); - std::string timestamp(&dateTimeBuf[0]); - - bool found = false; - for (auto it = directCodeInfos.begin(); it != directCodeInfos.end(); ++it) - { - if (it->connectCode == code) - { - found = true; - it->lastPlayed = timestamp; - } - } - - if (!found) - { - CodeInfo newDirectCode = {code, timestamp, false}; - directCodeInfos.push_back(newDirectCode); - } - - // TODO: Maybe remove from here? - // Or start a thread that is periodically called, if file writes will happen enough. - WriteFile(); -} - -void SlippiDirectCodes::Sort(u8 sortByProperty) -{ - switch (sortByProperty) - { - case SORT_BY_TIME: - std::sort(directCodeInfos.begin(), directCodeInfos.end(), - [](const CodeInfo a, const CodeInfo b) -> bool { return a.lastPlayed > b.lastPlayed; }); - break; - - case SORT_BY_NAME: - std::sort(directCodeInfos.begin(), directCodeInfos.end(), - [](const CodeInfo a, const CodeInfo b) -> bool { return a.connectCode < b.connectCode; }); - break; - } -} - -std::string SlippiDirectCodes::Autocomplete(std::string startText) -{ - // Pre-sort direct codes. - Sort(); - - // Find first entry in our sorted vector that starts with the given text. - for (auto it = directCodeInfos.begin(); it != directCodeInfos.end(); it++) - { - if (it->connectCode.rfind(startText, 0) == 0) - { - return it->connectCode; - } - } - - return startText; + slprs_user_direct_codes_add_or_update(slprs_exi_device_ptr, mapCode(kind), code.c_str()); } std::string SlippiDirectCodes::get(int index) { - Sort(); - - if (index < directCodeInfos.size() && index >= 0) - { - return directCodeInfos.at(index).connectCode; - } + char *code = slprs_user_direct_codes_get_code_at_index(slprs_exi_device_ptr, mapCode(kind), index); + + // To be safe, just do an extra copy into a full C++ string type - i.e, the ownership + // that we're passing out from behind this method is clear. + std::string connectCode = std::string(code); - INFO_LOG(SLIPPI_ONLINE, "Out of bounds name entry index %d", index); + // Since the C string was allocated on the Rust side, we need to free it using that allocator. + slprs_user_direct_codes_free_code(code); - return (index >= directCodeInfos.size()) ? "1" : ""; + return connectCode; } int SlippiDirectCodes::length() { - return (int)directCodeInfos.size(); -} - -void SlippiDirectCodes::WriteFile() -{ - std::string directCodesFilePath = getCodesFilePath(); - - // Outer empty array. - json fileData = json::array(); - - // Inner contents. - json directCodeData = json::object(); - - // TODO Define constants for string literals. - for (auto it = directCodeInfos.begin(); it != directCodeInfos.end(); ++it) - { - directCodeData["connectCode"] = it->connectCode; - directCodeData["lastPlayed"] = it->lastPlayed; - directCodeData["isFavorite"] = it->isFavorite; - - fileData.emplace_back(directCodeData); - } - - File::WriteStringToFile(fileData.dump(), directCodesFilePath); -} - -std::string SlippiDirectCodes::getCodesFilePath() -{ - std::string fileName = m_fileName + ".json"; - - std::string directCodesPath = File::GetUserPath(D_SLIPPI_IDX) + m_fileName; - return directCodesPath; -} - -inline std::string readString(json obj, std::string key) -{ - auto item = obj.find(key); - if (item == obj.end() || item.value().is_null()) - { - return ""; - } - - return obj[key]; -} - -inline bool readBool(json obj, std::string key) -{ - auto item = obj.find(key); - if (item == obj.end() || item.value().is_null()) - { - return false; - } - - return obj[key]; -} - -std::vector SlippiDirectCodes::parseFile(std::string fileContents) -{ - std::vector directCodes; - - json res = json::parse(fileContents, nullptr, false); - // Unlike the user.json, the encapsulating type should be an array. - if (res.is_discarded() || !res.is_array()) - { - WARN_LOG(SLIPPI_ONLINE, "Malformed json in direct codes file."); - return directCodes; - } - - // Retrieve all saved direct codes and related info - for (auto it = res.begin(); it != res.end(); ++it) - { - if (it.value().is_object()) - { - CodeInfo curDirectCode; - curDirectCode.connectCode = readString(*it, "connectCode"); - curDirectCode.lastPlayed = readString(*it, "lastPlayed"); - curDirectCode.isFavorite = readBool(*it, "favorite"); - - directCodes.push_back(curDirectCode); - } - } - - return directCodes; + return slprs_user_direct_codes_get_length(slprs_exi_device_ptr, mapCode(kind)); } diff --git a/Source/Core/Core/Slippi/SlippiDirectCodes.h b/Source/Core/Core/Slippi/SlippiDirectCodes.h index 915f0aae92..ceb83f564a 100644 --- a/Source/Core/Core/Slippi/SlippiDirectCodes.h +++ b/Source/Core/Core/Slippi/SlippiDirectCodes.h @@ -1,39 +1,34 @@ #pragma once -#include "Common/CommonTypes.h" -#include #include -#include -#include +// This class is currently a shim for the Rust codes interface. We're doing it this way +// to migrate things over without needing to do larger invasive changes. +// +// The remaining methods on here are simply layers that direct the call over to the Rust +// side. A quirk of this is that we're using the EXI device pointer, so this class absolutely +// cannot outlive the EXI device - but we control that and just need to do our due diligence +// when making changes. class SlippiDirectCodes { public: - static const uint8_t SORT_BY_TIME = 1; - static const uint8_t SORT_BY_FAVORITE = 2; - static const uint8_t SORT_BY_NAME = 3; + // We can't currently expose `SlippiRustExtensions.h` in header files, so + // we export these two types for code clarity and map them in the implementation. + static const uint8_t DIRECT = 0; + static const uint8_t TEAMS = 1; - struct CodeInfo - { - std::string connectCode = ""; - std::string lastPlayed = ""; - bool isFavorite = false; - }; - - SlippiDirectCodes(std::string fileName); + SlippiDirectCodes(uintptr_t rs_exi_device_ptr, uint8_t kind); ~SlippiDirectCodes(); - void ReadFile(); - void AddOrUpdateCode(std::string code); std::string get(int index); int length(); - void Sort(u8 sortByProperty = SlippiDirectCodes::SORT_BY_TIME); - std::string Autocomplete(std::string startText); + void AddOrUpdateCode(std::string code); protected: - void WriteFile(); - std::string getCodesFilePath(); - std::vector parseFile(std::string fileContents); - std::vector directCodeInfos; - std::string m_fileName; -}; \ No newline at end of file + // A pointer to a "shadow" EXI Device that lives on the Rust side of things. + // Do *not* do any cleanup of this! The EXI device will handle it. + uintptr_t slprs_exi_device_ptr; + + // An internal marker for what kind of codes we're reading/reporting. + uint8_t kind; +};