mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-25 19:55:50 +00:00
refactor: migrate SlippiUser to be backed by a Rust layer (#6)
* fix: user folder path for macOS * fix: slippi_rust_extensions -> slippi-rust-extensions when linking * fix: set application support folder to dolphin-beta and fix log
This commit is contained in:
parent
7e201a68ea
commit
03887849e5
12 changed files with 119 additions and 364 deletions
2
Externals/SlippiRustExtensions
vendored
2
Externals/SlippiRustExtensions
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit af04c6c1878731da5ccc7e1742f355af54755bb5
|
Subproject commit c5643d5d2cb7073b52dcfbdb7836771b264bc33d
|
|
@ -154,7 +154,7 @@ PUBLIC
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
${MBEDTLS_LIBRARIES}
|
${MBEDTLS_LIBRARIES}
|
||||||
minizip-ng
|
minizip-ng
|
||||||
slippi_rust_extensions
|
slippi-rust-extensions
|
||||||
|
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
|
|
|
@ -754,6 +754,19 @@ std::string GetBundleDirectory()
|
||||||
|
|
||||||
return app_bundle_path;
|
return app_bundle_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GetApplicationSupportDirectory()
|
||||||
|
{
|
||||||
|
std::string dir =
|
||||||
|
File::GetHomeDirectory() + "/Library/Application Support/com.project-slippi.dolphin-beta";
|
||||||
|
|
||||||
|
if (!CreateDir(dir))
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(COMMON, "Unable to create Application Support directory: {}", dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string GetExePath()
|
std::string GetExePath()
|
||||||
|
|
|
@ -234,6 +234,7 @@ void SetSysDirectory(const std::string& path);
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string GetBundleDirectory();
|
std::string GetBundleDirectory();
|
||||||
|
std::string GetApplicationSupportDirectory();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string GetExePath();
|
std::string GetExePath();
|
||||||
|
|
|
@ -61,8 +61,7 @@ enum class LogType : int
|
||||||
SLIPPI,
|
SLIPPI,
|
||||||
SLIPPI_ONLINE,
|
SLIPPI_ONLINE,
|
||||||
SLIPPI_RUST_DEPENDENCIES,
|
SLIPPI_RUST_DEPENDENCIES,
|
||||||
SLIPPI_RUST_EXI,
|
SLIPPI_RUST_ONLINE,
|
||||||
SLIPPI_RUST_GAME_REPORTER,
|
|
||||||
SLIPPI_RUST_JUKEBOX,
|
SLIPPI_RUST_JUKEBOX,
|
||||||
SP1,
|
SP1,
|
||||||
SYMBOLS,
|
SYMBOLS,
|
||||||
|
|
|
@ -166,9 +166,7 @@ LogManager::LogManager()
|
||||||
m_log[LogType::SLIPPI_ONLINE] = {"SLIPPI_ONLINE", "Slippi Online"};
|
m_log[LogType::SLIPPI_ONLINE] = {"SLIPPI_ONLINE", "Slippi Online"};
|
||||||
m_log[LogType::SLIPPI_RUST_DEPENDENCIES] = {"SLIPPI_RUST_DEPENDENCIES",
|
m_log[LogType::SLIPPI_RUST_DEPENDENCIES] = {"SLIPPI_RUST_DEPENDENCIES",
|
||||||
"[Rust] Slippi Dependencies", false, true};
|
"[Rust] Slippi Dependencies", false, true};
|
||||||
m_log[LogType::SLIPPI_RUST_EXI] = {"SLIPPI_RUST_EXI", "[Rust] Slippi EXI", false, true};
|
m_log[LogType::SLIPPI_RUST_ONLINE] = {"SLIPPI_RUST_ONLINE,", "[Rust] Slippi Online", false, true};
|
||||||
m_log[LogType::SLIPPI_RUST_GAME_REPORTER] = {"SLIPPI_RUST_GAME_REPORTER",
|
|
||||||
"[Rust] Slippi Game Reporter", false, true};
|
|
||||||
m_log[LogType::SLIPPI_RUST_JUKEBOX] = {"SLIPPI_RUST_JUKEBOX", "[Rust] Slippi Jukebox", false,
|
m_log[LogType::SLIPPI_RUST_JUKEBOX] = {"SLIPPI_RUST_JUKEBOX", "[Rust] Slippi Jukebox", false,
|
||||||
true};
|
true};
|
||||||
m_log[LogType::SP1] = {"SP1", "Serial Port 1"};
|
m_log[LogType::SP1] = {"SP1", "Serial Port 1"};
|
||||||
|
|
|
@ -141,9 +141,17 @@ CEXISlippi::CEXISlippi(Core::System& system, const std::string current_file_name
|
||||||
{
|
{
|
||||||
INFO_LOG_FMT(SLIPPI, "EXI SLIPPI Constructor called.");
|
INFO_LOG_FMT(SLIPPI, "EXI SLIPPI Constructor called.");
|
||||||
|
|
||||||
slprs_exi_device_ptr = slprs_exi_device_create(current_file_name.c_str(), OSDMessageHandler);
|
std::string user_file_path = File::GetUserPath(F_USERJSON_IDX);
|
||||||
|
|
||||||
user = std::make_unique<SlippiUser>();
|
SlippiRustEXIConfig slprs_exi_config;
|
||||||
|
slprs_exi_config.iso_path = current_file_name.c_str();
|
||||||
|
slprs_exi_config.user_json_path = user_file_path.c_str();
|
||||||
|
slprs_exi_config.scm_slippi_semver_str = Common::GetSemVerStr().c_str();
|
||||||
|
slprs_exi_config.osd_add_msg_fn = OSDMessageHandler;
|
||||||
|
|
||||||
|
slprs_exi_device_ptr = slprs_exi_device_create(slprs_exi_config);
|
||||||
|
|
||||||
|
user = std::make_unique<SlippiUser>(slprs_exi_device_ptr);
|
||||||
g_playback_status = std::make_unique<SlippiPlaybackStatus>();
|
g_playback_status = std::make_unique<SlippiPlaybackStatus>();
|
||||||
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
|
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
|
||||||
game_file_loader = std::make_unique<SlippiGameFileLoader>();
|
game_file_loader = std::make_unique<SlippiGameFileLoader>();
|
||||||
|
@ -287,9 +295,7 @@ CEXISlippi::~CEXISlippi()
|
||||||
if (active_match_id.find("mode.ranked") != std::string::npos)
|
if (active_match_id.find("mode.ranked") != std::string::npos)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Exit during in-progress ranked game: {}", active_match_id);
|
ERROR_LOG_FMT(SLIPPI_ONLINE, "Exit during in-progress ranked game: {}", active_match_id);
|
||||||
auto user_info = user->GetUserInfo();
|
slprs_exi_device_report_match_abandonment(slprs_exi_device_ptr, active_match_id.c_str());
|
||||||
slprs_exi_device_report_match_abandonment(slprs_exi_device_ptr, user_info.uid.c_str(),
|
|
||||||
user_info.play_key.c_str(), active_match_id.c_str());
|
|
||||||
}
|
}
|
||||||
handleConnectionCleanup();
|
handleConnectionCleanup();
|
||||||
|
|
||||||
|
@ -2714,7 +2720,6 @@ void CEXISlippi::handleChatMessage(u8* payload)
|
||||||
|
|
||||||
if (slippi_netplay)
|
if (slippi_netplay)
|
||||||
{
|
{
|
||||||
auto user_info = user->GetUserInfo();
|
|
||||||
auto packet = std::make_unique<sf::Packet>();
|
auto packet = std::make_unique<sf::Packet>();
|
||||||
// OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
|
// OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
|
||||||
slippi_netplay->remote_sent_chat_message_id = message_id;
|
slippi_netplay->remote_sent_chat_message_id = message_id;
|
||||||
|
@ -3017,10 +3022,8 @@ void CEXISlippi::handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuer
|
||||||
if (last_match_id.find("mode.ranked") != std::string::npos)
|
if (last_match_id.find("mode.ranked") != std::string::npos)
|
||||||
{
|
{
|
||||||
INFO_LOG_FMT(SLIPPI_ONLINE, "Reporting set completion: {}", last_match_id);
|
INFO_LOG_FMT(SLIPPI_ONLINE, "Reporting set completion: {}", last_match_id);
|
||||||
auto user_info = user->GetUserInfo();
|
|
||||||
|
|
||||||
slprs_exi_device_report_match_completion(slprs_exi_device_ptr, user_info.uid.c_str(),
|
slprs_exi_device_report_match_completion(slprs_exi_device_ptr, last_match_id.c_str(),
|
||||||
user_info.play_key.c_str(), last_match_id.c_str(),
|
|
||||||
query.end_mode);
|
query.end_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3031,12 +3034,10 @@ void CEXISlippi::handleGetPlayerSettings()
|
||||||
|
|
||||||
SlippiExiTypes::GetPlayerSettingsResponse resp = {};
|
SlippiExiTypes::GetPlayerSettingsResponse resp = {};
|
||||||
|
|
||||||
std::vector<std::vector<std::string>> messages_by_player = {
|
std::vector<std::vector<std::string>> messages_by_player = {{}, {}, {}, {}};
|
||||||
SlippiUser::default_chat_messages, SlippiUser::default_chat_messages,
|
|
||||||
SlippiUser::default_chat_messages, SlippiUser::default_chat_messages};
|
|
||||||
|
|
||||||
// These chat messages will be used when previewing messages
|
// These chat messages will be used when previewing messages
|
||||||
auto user_chat_messages = user->GetUserInfo().chat_messages;
|
auto user_chat_messages = user->GetUserChatMessages();
|
||||||
if (user_chat_messages.size() == 16)
|
if (user_chat_messages.size() == 16)
|
||||||
{
|
{
|
||||||
messages_by_player[0] = user_chat_messages;
|
messages_by_player[0] = user_chat_messages;
|
||||||
|
@ -3051,6 +3052,13 @@ void CEXISlippi::handleGetPlayerSettings()
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
|
// If any of the users in the chat messages vector have a payload that is incorrect,
|
||||||
|
// force that player to the default chat messages. A valid payload is 16 entries.
|
||||||
|
if (messages_by_player[i].size() != 16)
|
||||||
|
{
|
||||||
|
messages_by_player[i] = user->GetDefaultChatMessages();
|
||||||
|
}
|
||||||
|
|
||||||
for (int j = 0; j < 16; j++)
|
for (int j = 0; j < 16; j++)
|
||||||
{
|
{
|
||||||
auto str = ConvertStringForGame(messages_by_player[i][j], MAX_MESSAGE_LENGTH);
|
auto str = ConvertStringForGame(messages_by_player[i][j], MAX_MESSAGE_LENGTH);
|
||||||
|
|
|
@ -540,15 +540,19 @@ void SlippiMatchmaking::handleMatchmaking()
|
||||||
player_info.display_name = el.value("displayName", "");
|
player_info.display_name = el.value("displayName", "");
|
||||||
player_info.connect_code = el.value("connectCode", "");
|
player_info.connect_code = el.value("connectCode", "");
|
||||||
player_info.port = el.value("port", 0);
|
player_info.port = el.value("port", 0);
|
||||||
player_info.chat_messages = SlippiUser::default_chat_messages;
|
|
||||||
if (el["chatMessages"].is_array())
|
if (el["chatMessages"].is_array())
|
||||||
{
|
{
|
||||||
player_info.chat_messages = el.value("chatMessages", SlippiUser::default_chat_messages);
|
player_info.chat_messages = el.value("chatMessages", m_user->GetDefaultChatMessages());
|
||||||
if (player_info.chat_messages.size() != 16)
|
if (player_info.chat_messages.size() != 16)
|
||||||
{
|
{
|
||||||
player_info.chat_messages = SlippiUser::default_chat_messages;
|
player_info.chat_messages = m_user->GetDefaultChatMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player_info.chat_messages = m_user->GetDefaultChatMessages();
|
||||||
|
}
|
||||||
|
|
||||||
m_player_info.push_back(player_info);
|
m_player_info.push_back(player_info);
|
||||||
|
|
||||||
if (is_local)
|
if (is_local)
|
||||||
|
|
|
@ -1,363 +1,94 @@
|
||||||
#include "SlippiUser.h"
|
#include "SlippiUser.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/Logging/Log.h"
|
||||||
#include "Common/MsgHandler.h"
|
|
||||||
#include "Common/StringUtil.h"
|
|
||||||
#include "Common/Thread.h"
|
|
||||||
#include "Common/Version.h"
|
|
||||||
|
|
||||||
#include "Common/Common.h"
|
#include "SlippiRustExtensions.h"
|
||||||
#include "Core/ConfigManager.h"
|
|
||||||
|
|
||||||
#include <codecvt>
|
// Takes a RustChatMessages pointer and extracts messages from them, then
|
||||||
#include <locale>
|
// frees the underlying memory safely.
|
||||||
|
std::vector<std::string> ConvertChatMessagesFromRust(RustChatMessages* rsMessages)
|
||||||
#include <json.hpp>
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
const std::vector<std::string> SlippiUser::default_chat_messages = {
|
|
||||||
"ggs",
|
|
||||||
"one more",
|
|
||||||
"brb",
|
|
||||||
"good luck",
|
|
||||||
|
|
||||||
"well played",
|
|
||||||
"that was fun",
|
|
||||||
"thanks",
|
|
||||||
"too good",
|
|
||||||
|
|
||||||
"sorry",
|
|
||||||
"my b",
|
|
||||||
"lol",
|
|
||||||
"wow",
|
|
||||||
|
|
||||||
"gotta go",
|
|
||||||
"one sec",
|
|
||||||
"let's play again later",
|
|
||||||
"bad connection",
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define MAX_SYSTEM_PROGRAM (4096)
|
|
||||||
static void system_hidden(const char* cmd)
|
|
||||||
{
|
{
|
||||||
PROCESS_INFORMATION p_info;
|
std::vector<std::string> chatMessages;
|
||||||
STARTUPINFO s_info;
|
|
||||||
|
|
||||||
memset(&s_info, 0, sizeof(s_info));
|
for (int i = 0; i < rsMessages->len; i++)
|
||||||
memset(&p_info, 0, sizeof(p_info));
|
|
||||||
s_info.cb = sizeof(s_info);
|
|
||||||
|
|
||||||
wchar_t utf16cmd[MAX_SYSTEM_PROGRAM] = {0};
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, cmd, -1, utf16cmd, MAX_SYSTEM_PROGRAM);
|
|
||||||
if (CreateProcessW(NULL, utf16cmd, NULL, NULL, 0, CREATE_NO_WINDOW, NULL, NULL, &s_info, &p_info))
|
|
||||||
{
|
{
|
||||||
DWORD ExitCode;
|
std::string message = std::string(rsMessages->data[i]);
|
||||||
WaitForSingleObject(p_info.hProcess, INFINITE);
|
chatMessages.push_back(message);
|
||||||
GetExitCodeProcess(p_info.hProcess, &ExitCode);
|
|
||||||
CloseHandle(p_info.hProcess);
|
|
||||||
CloseHandle(p_info.hThread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void RunSystemCommand(const std::string& command)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
_wsystem(UTF8ToTStr(command).c_str());
|
|
||||||
#else
|
|
||||||
system(command.c_str());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t receive(char* ptr, size_t size, size_t nmemb, void* rcvBuf)
|
slprs_user_free_messages(rsMessages);
|
||||||
{
|
|
||||||
size_t len = size * nmemb;
|
|
||||||
INFO_LOG_FMT(SLIPPI_ONLINE, "[User] Received data: {}", len);
|
|
||||||
|
|
||||||
std::string* buf = (std::string*)rcvBuf;
|
return chatMessages;
|
||||||
|
|
||||||
buf->insert(buf->end(), ptr, ptr + len);
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiUser::SlippiUser()
|
SlippiUser::SlippiUser(uintptr_t rs_exi_device_ptr)
|
||||||
{
|
{
|
||||||
CURL* curl = curl_easy_init();
|
slprs_exi_device_ptr = rs_exi_device_ptr;
|
||||||
if (curl)
|
|
||||||
{
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5000);
|
|
||||||
|
|
||||||
// Set up HTTP Headers
|
|
||||||
m_curl_header_list = curl_slist_append(m_curl_header_list, "Content-Type: application/json");
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, m_curl_header_list);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// ALPN support is enabled by default but requires Windows >= 8.1.
|
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_curl = curl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiUser::~SlippiUser()
|
SlippiUser::~SlippiUser()
|
||||||
{
|
{
|
||||||
// Wait for thread to terminate
|
// Do nothing, the exi ptr is cleaned up by the exi device
|
||||||
m_run_thread = false;
|
|
||||||
if (m_file_listen_thread.joinable())
|
|
||||||
m_file_listen_thread.join();
|
|
||||||
|
|
||||||
if (m_curl)
|
|
||||||
{
|
|
||||||
curl_slist_free_all(m_curl_header_list);
|
|
||||||
curl_easy_cleanup(m_curl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SlippiUser::AttemptLogin()
|
bool SlippiUser::AttemptLogin()
|
||||||
{
|
{
|
||||||
std::string user_file_path = getUserFilePath();
|
return slprs_user_attempt_login(slprs_exi_device_ptr);
|
||||||
|
|
||||||
// TODO: Remove a couple updates after ranked
|
|
||||||
#ifndef __APPLE__
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
std::string old_user_file_path = File::GetExeDirectory() + DIR_SEP + "user.json";
|
|
||||||
#else
|
|
||||||
std::string old_user_file_path = File::GetUserPath(D_USER_IDX) + DIR_SEP + "user.json";
|
|
||||||
#endif
|
|
||||||
if (File::Exists(old_user_file_path) && !File::Rename(old_user_file_path, user_file_path))
|
|
||||||
{
|
|
||||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Could not move file {} to {}", old_user_file_path,
|
|
||||||
user_file_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get user file
|
|
||||||
std::string user_file_contents;
|
|
||||||
File::ReadFileToString(user_file_path, user_file_contents);
|
|
||||||
|
|
||||||
m_user_info = parseFile(user_file_contents);
|
|
||||||
|
|
||||||
m_is_logged_in = !m_user_info.uid.empty();
|
|
||||||
if (m_is_logged_in)
|
|
||||||
{
|
|
||||||
overwriteFromServer();
|
|
||||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Found user {} ({})", m_user_info.display_name, m_user_info.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_is_logged_in;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiUser::OpenLogInPage()
|
void SlippiUser::OpenLogInPage()
|
||||||
{
|
{
|
||||||
std::string url = "https://slippi.gg/online/enable";
|
slprs_user_open_login_page(slprs_exi_device_ptr);
|
||||||
std::string path = getUserFilePath();
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// On windows, sometimes the path can have backslashes and slashes mixed, convert all to
|
|
||||||
// backslashes
|
|
||||||
path = ReplaceAll(path, "\\", "\\");
|
|
||||||
path = ReplaceAll(path, "/", "\\");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef __APPLE__
|
|
||||||
char* escaped_path = curl_easy_escape(nullptr, path.c_str(), static_cast<int>(path.length()));
|
|
||||||
path = std::string(escaped_path);
|
|
||||||
curl_free(escaped_path);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::string full_url = url + "?path=" + path;
|
|
||||||
|
|
||||||
INFO_LOG_FMT(SLIPPI_ONLINE, "[User] Login at path: {}", full_url);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
std::string command = "explorer \"" + full_url + "\"";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
std::string command = "open \"" + full_url + "\"";
|
|
||||||
#else
|
|
||||||
std::string command = "xdg-open \"" + full_url + "\""; // Linux
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RunSystemCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SlippiUser::UpdateApp()
|
|
||||||
{
|
|
||||||
std::string url = "https://slippi.gg/downloads?update=true";
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
std::string command = "explorer \"" + url + "\"";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
std::string command = "open \"" + url + "\"";
|
|
||||||
#else
|
|
||||||
std::string command = "xdg-open \"" + url + "\""; // Linux
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RunSystemCommand(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiUser::ListenForLogIn()
|
void SlippiUser::ListenForLogIn()
|
||||||
{
|
{
|
||||||
if (m_run_thread)
|
slprs_user_listen_for_login(slprs_exi_device_ptr);
|
||||||
return;
|
}
|
||||||
|
|
||||||
if (m_file_listen_thread.joinable())
|
bool SlippiUser::UpdateApp()
|
||||||
m_file_listen_thread.join();
|
{
|
||||||
|
return slprs_user_update_app(slprs_exi_device_ptr);
|
||||||
m_run_thread = true;
|
|
||||||
m_file_listen_thread = std::thread(&SlippiUser::FileListenThread, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiUser::LogOut()
|
void SlippiUser::LogOut()
|
||||||
{
|
{
|
||||||
m_run_thread = false;
|
slprs_user_logout(slprs_exi_device_ptr);
|
||||||
deleteFile();
|
|
||||||
|
|
||||||
UserInfo empty_user;
|
|
||||||
m_is_logged_in = false;
|
|
||||||
m_user_info = empty_user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiUser::OverwriteLatestVersion(std::string version)
|
void SlippiUser::OverwriteLatestVersion(std::string version)
|
||||||
{
|
{
|
||||||
m_user_info.latest_version = version;
|
slprs_user_overwrite_latest_version(slprs_exi_device_ptr, version.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiUser::UserInfo SlippiUser::GetUserInfo()
|
SlippiUser::UserInfo SlippiUser::GetUserInfo()
|
||||||
{
|
{
|
||||||
return m_user_info;
|
SlippiUser::UserInfo userInfo;
|
||||||
|
|
||||||
|
RustUserInfo* info = slprs_user_get_info(slprs_exi_device_ptr);
|
||||||
|
userInfo.uid = std::string(info->uid);
|
||||||
|
userInfo.play_key = std::string(info->play_key);
|
||||||
|
userInfo.display_name = std::string(info->display_name);
|
||||||
|
userInfo.connect_code = std::string(info->connect_code);
|
||||||
|
userInfo.latest_version = std::string(info->latest_version);
|
||||||
|
slprs_user_free_info(info);
|
||||||
|
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SlippiUser::GetDefaultChatMessages()
|
||||||
|
{
|
||||||
|
RustChatMessages* chatMessages = slprs_user_get_default_messages(slprs_exi_device_ptr);
|
||||||
|
return ConvertChatMessagesFromRust(chatMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SlippiUser::GetUserChatMessages()
|
||||||
|
{
|
||||||
|
RustChatMessages* chatMessages = slprs_user_get_messages(slprs_exi_device_ptr);
|
||||||
|
return ConvertChatMessagesFromRust(chatMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SlippiUser::IsLoggedIn()
|
bool SlippiUser::IsLoggedIn()
|
||||||
{
|
{
|
||||||
return m_is_logged_in;
|
return slprs_user_get_is_logged_in(slprs_exi_device_ptr);
|
||||||
}
|
|
||||||
|
|
||||||
void SlippiUser::FileListenThread()
|
|
||||||
{
|
|
||||||
while (m_run_thread)
|
|
||||||
{
|
|
||||||
if (AttemptLogin())
|
|
||||||
{
|
|
||||||
m_run_thread = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Common::SleepCurrentThread(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 SlippiUser::getUserFilePath()
|
|
||||||
{
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
std::string user_file_path =
|
|
||||||
File::GetBundleDirectory() + "/Contents/Resources" + DIR_SEP + "user.json";
|
|
||||||
#else
|
|
||||||
std::string user_file_path = File::GetUserPath(F_USERJSON_IDX);
|
|
||||||
INFO_LOG_FMT(SLIPPI, "{}", user_file_path);
|
|
||||||
#endif
|
|
||||||
return user_file_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
SlippiUser::UserInfo SlippiUser::parseFile(std::string file_contents)
|
|
||||||
{
|
|
||||||
UserInfo info;
|
|
||||||
info.file_contents = file_contents;
|
|
||||||
|
|
||||||
auto res = json::parse(file_contents, nullptr, false);
|
|
||||||
if (res.is_discarded() || !res.is_object())
|
|
||||||
{
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.uid = readString(res, "uid");
|
|
||||||
info.display_name = readString(res, "displayName");
|
|
||||||
info.play_key = readString(res, "playKey");
|
|
||||||
info.connect_code = readString(res, "connectCode");
|
|
||||||
info.latest_version = readString(res, "latestVersion");
|
|
||||||
info.chat_messages = SlippiUser::default_chat_messages;
|
|
||||||
if (res["chatMessages"].is_array())
|
|
||||||
{
|
|
||||||
info.chat_messages = res.value("chatMessages", SlippiUser::default_chat_messages);
|
|
||||||
if (info.chat_messages.size() != 16)
|
|
||||||
{
|
|
||||||
info.chat_messages = SlippiUser::default_chat_messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SlippiUser::deleteFile()
|
|
||||||
{
|
|
||||||
std::string user_file_path = getUserFilePath();
|
|
||||||
File::Delete(user_file_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SlippiUser::overwriteFromServer()
|
|
||||||
{
|
|
||||||
if (!m_curl)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Perform curl request
|
|
||||||
std::string resp;
|
|
||||||
curl_easy_setopt(m_curl, CURLOPT_URL,
|
|
||||||
(URL_START + "/" + m_user_info.uid + "?additionalFields=chatMessages").c_str());
|
|
||||||
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &resp);
|
|
||||||
CURLcode res = curl_easy_perform(m_curl);
|
|
||||||
|
|
||||||
if (res != 0)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(SLIPPI, "[User] Error fetching user info from server, code: {}",
|
|
||||||
static_cast<u8>(res));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long response_code;
|
|
||||||
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
||||||
if (response_code != 200)
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(SLIPPI, "[User] Server responded with non-success status: {}", response_code);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite user info with data from server
|
|
||||||
auto r = json::parse(resp, nullptr, false);
|
|
||||||
m_user_info.connect_code = r.value("connectCode", m_user_info.connect_code);
|
|
||||||
m_user_info.latest_version = r.value("latestVersion", m_user_info.latest_version);
|
|
||||||
m_user_info.display_name = r.value("displayName", m_user_info.display_name);
|
|
||||||
if (r["chatMessages"].is_array())
|
|
||||||
{
|
|
||||||
m_user_info.chat_messages = r.value("chatMessages", SlippiUser::default_chat_messages);
|
|
||||||
if (m_user_info.chat_messages.size() != 16)
|
|
||||||
{
|
|
||||||
m_user_info.chat_messages = SlippiUser::default_chat_messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,18 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
// This class is currently a shim for the Rust user interface. We're doing it this way
|
||||||
|
// to begin migrating things over without needing to do larger invasive changes.
|
||||||
|
//
|
||||||
|
// The remaining methods in 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 SlippiUser
|
class SlippiUser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// This type is filled in with data from the Rust side.
|
||||||
|
// Eventually, this entire class will disappear.
|
||||||
struct UserInfo
|
struct UserInfo
|
||||||
{
|
{
|
||||||
std::string uid = "";
|
std::string uid = "";
|
||||||
|
@ -25,35 +34,22 @@ public:
|
||||||
std::vector<std::string> chat_messages;
|
std::vector<std::string> chat_messages;
|
||||||
};
|
};
|
||||||
|
|
||||||
SlippiUser();
|
SlippiUser(uintptr_t rs_exi_device_ptr);
|
||||||
~SlippiUser();
|
~SlippiUser();
|
||||||
|
|
||||||
bool AttemptLogin();
|
bool AttemptLogin();
|
||||||
void OpenLogInPage();
|
void OpenLogInPage();
|
||||||
void UpdateApp();
|
bool UpdateApp();
|
||||||
void ListenForLogIn();
|
void ListenForLogIn();
|
||||||
void LogOut();
|
void LogOut();
|
||||||
void OverwriteLatestVersion(std::string version);
|
void OverwriteLatestVersion(std::string version);
|
||||||
UserInfo GetUserInfo();
|
UserInfo GetUserInfo();
|
||||||
|
std::vector<std::string> GetUserChatMessages();
|
||||||
|
std::vector<std::string> GetDefaultChatMessages();
|
||||||
bool IsLoggedIn();
|
bool IsLoggedIn();
|
||||||
void FileListenThread();
|
|
||||||
|
|
||||||
const static std::vector<std::string> default_chat_messages;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string getUserFilePath();
|
// A pointer to a "shadow" EXI Device that lives on the Rust side of things.
|
||||||
UserInfo parseFile(std::string file_contents);
|
// Do *not* do any cleanup of this! The EXI device will handle it.
|
||||||
void deleteFile();
|
uintptr_t slprs_exi_device_ptr;
|
||||||
void overwriteFromServer();
|
|
||||||
|
|
||||||
UserInfo m_user_info;
|
|
||||||
bool m_is_logged_in = false;
|
|
||||||
|
|
||||||
const std::string URL_START = "https://users-rest-dot-slippi.uc.r.appspot.com/user";
|
|
||||||
CURL* m_curl = nullptr;
|
|
||||||
struct curl_slist* m_curl_header_list = nullptr;
|
|
||||||
std::vector<char> m_receive_buf;
|
|
||||||
|
|
||||||
std::thread m_file_listen_thread;
|
|
||||||
std::atomic<bool> m_run_thread;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -565,7 +565,7 @@ endif()
|
||||||
#endif()
|
#endif()
|
||||||
|
|
||||||
corrosion_import_crate(MANIFEST_PATH ${CMAKE_SOURCE_DIR}/Externals/SlippiRustExtensions/Cargo.toml ${RUST_FEATURES})
|
corrosion_import_crate(MANIFEST_PATH ${CMAKE_SOURCE_DIR}/Externals/SlippiRustExtensions/Cargo.toml ${RUST_FEATURES})
|
||||||
target_link_libraries(dolphin-emu PUBLIC slippi_rust_extensions)
|
target_link_libraries(dolphin-emu PUBLIC slippi-rust-extensions)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
include(BundleUtilities)
|
include(BundleUtilities)
|
||||||
|
|
|
@ -410,9 +410,14 @@ void SetUserDirectory(std::string custom_path)
|
||||||
std::string home_path = std::string(home) + DIR_SEP;
|
std::string home_path = std::string(home) + DIR_SEP;
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
// Mainline Dolphin switched to storing things elsewhere some time ago.
|
// Since the Replays build shares the same identifier as the netplay build,
|
||||||
// To get it working for now, let's just use the Slippi route.
|
// we'll just have a netplay and playback folder inside the identifer similar to how
|
||||||
user_path = File::GetBundleDirectory() + "/Contents/Resources/User" DIR_SEP;
|
// the Launcher does it to keep with some convention.
|
||||||
|
#ifdef IS_PLAYBACK
|
||||||
|
user_path = File::GetApplicationSupportDirectory() + "/playback/User" DIR_SEP;
|
||||||
|
#else
|
||||||
|
user_path = File::GetApplicationSupportDirectory() + "/netplay/User" DIR_SEP;
|
||||||
|
#endif
|
||||||
#elif defined(ANDROID)
|
#elif defined(ANDROID)
|
||||||
if (env_path)
|
if (env_path)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue