diff --git a/Externals/SlippiRustExtensions b/Externals/SlippiRustExtensions index af04c6c187..c5643d5d2c 160000 --- a/Externals/SlippiRustExtensions +++ b/Externals/SlippiRustExtensions @@ -1 +1 @@ -Subproject commit af04c6c1878731da5ccc7e1742f355af54755bb5 +Subproject commit c5643d5d2cb7073b52dcfbdb7836771b264bc33d diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 9db2f77d63..0ff3357b96 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -154,7 +154,7 @@ PUBLIC fmt::fmt ${MBEDTLS_LIBRARIES} minizip-ng - slippi_rust_extensions + slippi-rust-extensions PRIVATE ${CURL_LIBRARIES} diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index ce5f64675f..cb6204f968 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -754,6 +754,19 @@ std::string GetBundleDirectory() 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 std::string GetExePath() diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 644efaf843..962e6392e2 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -234,6 +234,7 @@ void SetSysDirectory(const std::string& path); #ifdef __APPLE__ std::string GetBundleDirectory(); +std::string GetApplicationSupportDirectory(); #endif std::string GetExePath(); diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index adfd734e94..4567e1e695 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -61,8 +61,7 @@ enum class LogType : int SLIPPI, SLIPPI_ONLINE, SLIPPI_RUST_DEPENDENCIES, - SLIPPI_RUST_EXI, - SLIPPI_RUST_GAME_REPORTER, + SLIPPI_RUST_ONLINE, SLIPPI_RUST_JUKEBOX, SP1, SYMBOLS, diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index 9669a687b9..196eafc468 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -166,9 +166,7 @@ LogManager::LogManager() m_log[LogType::SLIPPI_ONLINE] = {"SLIPPI_ONLINE", "Slippi Online"}; m_log[LogType::SLIPPI_RUST_DEPENDENCIES] = {"SLIPPI_RUST_DEPENDENCIES", "[Rust] Slippi Dependencies", false, true}; - m_log[LogType::SLIPPI_RUST_EXI] = {"SLIPPI_RUST_EXI", "[Rust] Slippi EXI", false, true}; - m_log[LogType::SLIPPI_RUST_GAME_REPORTER] = {"SLIPPI_RUST_GAME_REPORTER", - "[Rust] Slippi Game Reporter", false, true}; + m_log[LogType::SLIPPI_RUST_ONLINE] = {"SLIPPI_RUST_ONLINE,", "[Rust] Slippi Online", false, true}; m_log[LogType::SLIPPI_RUST_JUKEBOX] = {"SLIPPI_RUST_JUKEBOX", "[Rust] Slippi Jukebox", false, true}; m_log[LogType::SP1] = {"SP1", "Serial Port 1"}; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp index 1eb0d77acf..6692ed9028 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp @@ -141,9 +141,17 @@ CEXISlippi::CEXISlippi(Core::System& system, const std::string current_file_name { 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(); + 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(slprs_exi_device_ptr); g_playback_status = std::make_unique(); matchmaking = std::make_unique(user.get()); game_file_loader = std::make_unique(); @@ -287,9 +295,7 @@ CEXISlippi::~CEXISlippi() if (active_match_id.find("mode.ranked") != std::string::npos) { 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, user_info.uid.c_str(), - user_info.play_key.c_str(), active_match_id.c_str()); + slprs_exi_device_report_match_abandonment(slprs_exi_device_ptr, active_match_id.c_str()); } handleConnectionCleanup(); @@ -2714,7 +2720,6 @@ void CEXISlippi::handleChatMessage(u8* payload) if (slippi_netplay) { - auto user_info = user->GetUserInfo(); auto packet = std::make_unique(); // OSD::AddMessage("[Me]: "+ msg, OSD::Duration::VERY_LONG, OSD::Color::YELLOW); 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) { 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(), - user_info.play_key.c_str(), last_match_id.c_str(), + slprs_exi_device_report_match_completion(slprs_exi_device_ptr, last_match_id.c_str(), query.end_mode); } } @@ -3031,12 +3034,10 @@ void CEXISlippi::handleGetPlayerSettings() SlippiExiTypes::GetPlayerSettingsResponse resp = {}; - std::vector> messages_by_player = { - SlippiUser::default_chat_messages, SlippiUser::default_chat_messages, - SlippiUser::default_chat_messages, SlippiUser::default_chat_messages}; + std::vector> messages_by_player = {{}, {}, {}, {}}; // 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) { messages_by_player[0] = user_chat_messages; @@ -3051,6 +3052,13 @@ void CEXISlippi::handleGetPlayerSettings() 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++) { auto str = ConvertStringForGame(messages_by_player[i][j], MAX_MESSAGE_LENGTH); diff --git a/Source/Core/Core/Slippi/SlippiMatchmaking.cpp b/Source/Core/Core/Slippi/SlippiMatchmaking.cpp index 61bda6d1a1..98f4ac9658 100644 --- a/Source/Core/Core/Slippi/SlippiMatchmaking.cpp +++ b/Source/Core/Core/Slippi/SlippiMatchmaking.cpp @@ -540,15 +540,19 @@ void SlippiMatchmaking::handleMatchmaking() player_info.display_name = el.value("displayName", ""); player_info.connect_code = el.value("connectCode", ""); player_info.port = el.value("port", 0); - player_info.chat_messages = SlippiUser::default_chat_messages; 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) { - 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); if (is_local) diff --git a/Source/Core/Core/Slippi/SlippiUser.cpp b/Source/Core/Core/Slippi/SlippiUser.cpp index 6e6e3ab9e2..24a94aab2d 100644 --- a/Source/Core/Core/Slippi/SlippiUser.cpp +++ b/Source/Core/Core/Slippi/SlippiUser.cpp @@ -1,363 +1,94 @@ #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/MsgHandler.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" -#include "Common/Version.h" -#include "Common/Common.h" -#include "Core/ConfigManager.h" +#include "SlippiRustExtensions.h" -#include -#include - -#include -using json = nlohmann::json; - -const std::vector 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) +// Takes a RustChatMessages pointer and extracts messages from them, then +// frees the underlying memory safely. +std::vector ConvertChatMessagesFromRust(RustChatMessages* rsMessages) { - PROCESS_INFORMATION p_info; - STARTUPINFO s_info; + std::vector chatMessages; - memset(&s_info, 0, sizeof(s_info)); - 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)) + for (int i = 0; i < rsMessages->len; i++) { - DWORD ExitCode; - WaitForSingleObject(p_info.hProcess, INFINITE); - GetExitCodeProcess(p_info.hProcess, &ExitCode); - CloseHandle(p_info.hProcess); - CloseHandle(p_info.hThread); + std::string message = std::string(rsMessages->data[i]); + chatMessages.push_back(message); } -} -#endif -static void RunSystemCommand(const std::string& command) -{ -#ifdef _WIN32 - _wsystem(UTF8ToTStr(command).c_str()); -#else - system(command.c_str()); -#endif + slprs_user_free_messages(rsMessages); + + return chatMessages; } -static size_t receive(char* ptr, size_t size, size_t nmemb, void* rcvBuf) +SlippiUser::SlippiUser(uintptr_t rs_exi_device_ptr) { - size_t len = size * nmemb; - INFO_LOG_FMT(SLIPPI_ONLINE, "[User] Received data: {}", len); - - std::string* buf = (std::string*)rcvBuf; - - buf->insert(buf->end(), ptr, ptr + len); - - return len; -} - -SlippiUser::SlippiUser() -{ - CURL* curl = curl_easy_init(); - 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; - } + slprs_exi_device_ptr = rs_exi_device_ptr; } SlippiUser::~SlippiUser() { - // Wait for thread to terminate - 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); - } + // Do nothing, the exi ptr is cleaned up by the exi device } bool SlippiUser::AttemptLogin() { - std::string user_file_path = getUserFilePath(); - - // 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; + return slprs_user_attempt_login(slprs_exi_device_ptr); } void SlippiUser::OpenLogInPage() { - std::string url = "https://slippi.gg/online/enable"; - 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(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); + slprs_user_open_login_page(slprs_exi_device_ptr); } void SlippiUser::ListenForLogIn() { - if (m_run_thread) - return; + slprs_user_listen_for_login(slprs_exi_device_ptr); +} - if (m_file_listen_thread.joinable()) - m_file_listen_thread.join(); - - m_run_thread = true; - m_file_listen_thread = std::thread(&SlippiUser::FileListenThread, this); +bool SlippiUser::UpdateApp() +{ + return slprs_user_update_app(slprs_exi_device_ptr); } void SlippiUser::LogOut() { - m_run_thread = false; - deleteFile(); - - UserInfo empty_user; - m_is_logged_in = false; - m_user_info = empty_user; + slprs_user_logout(slprs_exi_device_ptr); } 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() { - 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 SlippiUser::GetDefaultChatMessages() +{ + RustChatMessages* chatMessages = slprs_user_get_default_messages(slprs_exi_device_ptr); + return ConvertChatMessagesFromRust(chatMessages); +} + +std::vector SlippiUser::GetUserChatMessages() +{ + RustChatMessages* chatMessages = slprs_user_get_messages(slprs_exi_device_ptr); + return ConvertChatMessagesFromRust(chatMessages); } bool SlippiUser::IsLoggedIn() { - return m_is_logged_in; -} - -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(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; - } - } + return slprs_user_get_is_logged_in(slprs_exi_device_ptr); } diff --git a/Source/Core/Core/Slippi/SlippiUser.h b/Source/Core/Core/Slippi/SlippiUser.h index bd04ff9868..aa473fabb4 100644 --- a/Source/Core/Core/Slippi/SlippiUser.h +++ b/Source/Core/Core/Slippi/SlippiUser.h @@ -8,9 +8,18 @@ #include #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 { public: + // This type is filled in with data from the Rust side. + // Eventually, this entire class will disappear. struct UserInfo { std::string uid = ""; @@ -25,35 +34,22 @@ public: std::vector chat_messages; }; - SlippiUser(); + SlippiUser(uintptr_t rs_exi_device_ptr); ~SlippiUser(); bool AttemptLogin(); void OpenLogInPage(); - void UpdateApp(); + bool UpdateApp(); void ListenForLogIn(); void LogOut(); void OverwriteLatestVersion(std::string version); UserInfo GetUserInfo(); + std::vector GetUserChatMessages(); + std::vector GetDefaultChatMessages(); bool IsLoggedIn(); - void FileListenThread(); - - const static std::vector default_chat_messages; protected: - std::string getUserFilePath(); - UserInfo parseFile(std::string file_contents); - void deleteFile(); - 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 m_receive_buf; - - std::thread m_file_listen_thread; - std::atomic m_run_thread; + // 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; }; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 66c693e224..954b6fe055 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -565,7 +565,7 @@ endif() #endif() 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) include(BundleUtilities) diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index b566c059a0..9df6b70ef3 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -410,9 +410,14 @@ void SetUserDirectory(std::string custom_path) std::string home_path = std::string(home) + DIR_SEP; #if defined(__APPLE__) - // Mainline Dolphin switched to storing things elsewhere some time ago. - // To get it working for now, let's just use the Slippi route. - user_path = File::GetBundleDirectory() + "/Contents/Resources/User" DIR_SEP; + // Since the Replays build shares the same identifier as the netplay build, + // we'll just have a netplay and playback folder inside the identifer similar to how + // 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) if (env_path) { @@ -513,7 +518,7 @@ bool TriggerSTMPowerEvent() #ifdef HAVE_X11 void InhibitScreenSaver(Window win, bool inhibit) #else - void InhibitScreenSaver(bool inhibit) +void InhibitScreenSaver(bool inhibit) #endif { // Inhibit the screensaver. Depending on the operating system this may also