Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shim SlippiDirectCodes to Rust port. #417

Open
wants to merge 1 commit into
base: slippi
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Source/Core/Common/FileUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions Source/Core/Common/FileUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions Source/Core/Core/HW/EXI_DeviceSlippi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -170,8 +170,8 @@ CEXISlippi::CEXISlippi()
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
gameFileLoader = std::make_unique<SlippiGameFileLoader>();
g_replayComm = std::make_unique<SlippiReplayComm>();
directCodes = std::make_unique<SlippiDirectCodes>("direct-codes.json");
teamsCodes = std::make_unique<SlippiDirectCodes>("teams-codes.json");
directCodes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::DIRECT);
teamsCodes = std::make_unique<SlippiDirectCodes>(slprs_exi_device_ptr, SlippiDirectCodes::TEAMS);

generator = std::default_random_engine(Common::Timer::GetTimeMs());

Expand Down
229 changes: 20 additions & 209 deletions Source/Core/Core/Slippi/SlippiDirectCodes.cpp
Original file line number Diff line number Diff line change
@@ -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 <codecvt>
#include <locale>
#include <time.h>

#include <json.hpp>
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<char> 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::CodeInfo> SlippiDirectCodes::parseFile(std::string fileContents)
{
std::vector<SlippiDirectCodes::CodeInfo> 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));
}
45 changes: 20 additions & 25 deletions Source/Core/Core/Slippi/SlippiDirectCodes.h
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
#pragma once

#include "Common/CommonTypes.h"
#include <atomic>
#include <string>
#include <thread>
#include <vector>

// 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<CodeInfo> parseFile(std::string fileContents);
std::vector<CodeInfo> directCodeInfos;
std::string m_fileName;
};
// 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;
};
Loading