diff --git a/CMakeLists.txt b/CMakeLists.txt index d83925bbb1..7b6d549d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -657,7 +657,7 @@ endif() set(SLIPPI_PLAYBACK TRUE) if(SLIPPI_PLAYBACK) # Slippi Playback build option - add_definitions(-DIS_PLAYBACK=1) + # add_definitions(-DIS_PLAYBACK=1) endif() ######################################## diff --git a/Source/Core/Common/StringUtil.cpp b/Source/Core/Common/StringUtil.cpp index bc782a0ca7..c2f7d3f9e4 100644 --- a/Source/Core/Common/StringUtil.cpp +++ b/Source/Core/Common/StringUtil.cpp @@ -611,11 +611,25 @@ std::string UTF16BEToUTF8(const char16_t* str, size_t max_size) #else template -std::string CodeTo(const char* tocode, const char* fromcode, std::basic_string_view input) +#ifdef __APPLE__ +std::string CodeToWithFallbacks(const char* tocode, const char* fromcode, + const std::basic_string& input, iconv_fallbacks* fallbacks) +#else +std::string CodeTo(const char* tocode, const char* fromcode, const std::basic_string& input) +#endif { std::string result; iconv_t const conv_desc = iconv_open(tocode, fromcode); + + // Only on OS X can we call iconvctl, it isn't found on Linux +#ifdef __APPLE__ + if (fallbacks) + { + iconvctl(conv_desc, ICONV_SET_FALLBACKS, fallbacks); + } +#endif + if ((iconv_t)-1 == conv_desc) { ERROR_LOG_FMT(COMMON, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno)); @@ -671,6 +685,14 @@ std::string CodeTo(const char* tocode, const char* fromcode, std::basic_string_v return result; } +#ifdef __APPLE__ +template +std::string CodeTo(const char* tocode, const char* fromcode, const std::basic_string& input) +{ + return CodeToWithFallbacks(tocode, fromcode, input, nullptr); +} +#endif + template std::string CodeToUTF8(const char* fromcode, std::basic_string_view input) { @@ -690,9 +712,74 @@ std::string SHIFTJISToUTF8(std::string_view input) return CodeToUTF8("SJIS", input); } -std::string UTF8ToSHIFTJIS(std::string_view input) +#ifdef __APPLE__ +void uc_to_mb_fb(unsigned int code, + void (*write_replacement)(const char* buf, size_t buflen, void* callback_arg), + void* callback_arg, void* data) { - return CodeTo("SJIS", "UTF-8", input); + static std::unordered_map specialCharConvert = { + {'!', (const char*)"\x81\x49"}, + {'"', (const char*)"\x81\x68"}, + {'#', (const char*)"\x81\x94"}, + {'$', (const char*)"\x81\x90"}, + {'%', (const char*)"\x81\x93"}, + {'&', (const char*)"\x81\x95"}, + {'\'', (const char*)"\x81\x66"}, + {'(', (const char*)"\x81\x69"}, + {')', (const char*)"\x81\x6a"}, + {'*', (const char*)"\x81\x96"}, + {'+', (const char*)"\x81\x7b"}, + {',', (const char*)"\x81\x43"}, + {'-', (const char*)"\x81\x7c"}, + {'.', (const char*)"\x81\x44"}, + {'/', (const char*)"\x81\x5e"}, + {':', (const char*)"\x81\x46"}, + {';', (const char*)"\x81\x47"}, + {'<', (const char*)"\x81\x83"}, + {'=', (const char*)"\x81\x81"}, + {'>', (const char*)"\x81\x84"}, + {'?', (const char*)"\x81\x48"}, + {'@', (const char*)"\x81\x97"}, + {'[', (const char*)"\x81\x6d"}, + {'\\', (const char*)"\x81\x5f"}, + {']', (const char*)"\x81\x6e"}, + {'^', (const char*)"\x81\x4f"}, + {'_', (const char*)"\x81\x51"}, + {'`', (const char*)"\x81\x4d"}, + {'{', (const char*)"\x81\x6f"}, + {'|', (const char*)"\x81\x62"}, + {'}', (const char*)"\x81\x70"}, + {'~', (const char*)"\x81\x60"}, + {U'¥', "\x81\x8f"}, + {U'•', "\x81\x45"}, + {U'—', "\x81\x7C"}}; + + bool hasConversion = specialCharConvert.count(code); + if (!hasConversion) + return; + + auto newChar = specialCharConvert[code]; + // Add new chars to pos to replace + write_replacement(newChar, 2, callback_arg); +} +#endif + +std::string UTF8ToSHIFTJIS(const std::string& input) +{ +#ifdef __APPLE__ + // Set SHIFTJIS callbacks only if converting to shift jis + auto fallbacks = new iconv_fallbacks(); + fallbacks->uc_to_mb_fallback = uc_to_mb_fb; + fallbacks->mb_to_uc_fallback = nullptr; + fallbacks->mb_to_wc_fallback = nullptr; + fallbacks->wc_to_mb_fallback = nullptr; + fallbacks->data = nullptr; + auto str = CodeToWithFallbacks("SJIS", "UTF-8", input, fallbacks); + free(fallbacks); +#else + auto str = CodeTo("SJIS", "UTF-8", input); +#endif + return str; } std::string WStringToUTF8(std::wstring_view input) diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h index 2389367663..93e26f225c 100644 --- a/Source/Core/Common/StringUtil.h +++ b/Source/Core/Common/StringUtil.h @@ -188,6 +188,17 @@ std::string UTF16BEToUTF8(const char16_t* str, size_t max_size); // Stops at \0 std::string UTF16ToUTF8(std::u16string_view str); std::u16string UTF8ToUTF16(std::string_view str); +#ifdef __APPLE__ +/** + * Callback Implementation used for iconv when a unicode character could not be automatically + * converted. This callback is used specifically for SHIFTJIS failures when converting some special + * wide characters for melee. + */ +void uc_to_mb_fb(unsigned int code, + void (*write_replacement)(const char* buf, size_t buflen, void* callback_arg), + void* callback_arg, void* data); +#endif + #ifdef _WIN32 std::wstring UTF8ToWString(std::string_view str); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp index b3edfa9a26..46ed8d4b66 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp @@ -2084,7 +2084,7 @@ void CEXISlippi::prepareOnlineMatchState() chatMessagePlayerIdx = 0; localChatMessageId = 0; // in CSS p1 is always current player and p2 is opponent - localPlayerName = p1Name = "Player 1"; + localPlayerName = p1Name = userInfo.display_name; oppName = p2Name = "Player 2"; #endif @@ -2949,6 +2949,44 @@ void CEXISlippi::handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuer } } +void CEXISlippi::handleGetPlayerSettings() +{ + m_read_queue.clear(); + + SlippiExiTypes::GetPlayerSettingsResponse resp = {}; + + std::vector> messagesByPlayer = { + 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 + auto userChatMessages = user->GetUserInfo().chat_messages; + if (userChatMessages.size() == 16) + { + messagesByPlayer[0] = userChatMessages; + } + + // These chat messages will be set when we have an opponent. We load their and our messages + auto playerInfo = matchmaking->GetPlayerInfo(); + for (auto& player : playerInfo) + { + messagesByPlayer[player.port - 1] = player.chat_messages; + } + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 16; j++) + { + auto str = ConvertStringForGame(messagesByPlayer[i][j], MAX_MESSAGE_LENGTH); + sprintf(resp.settings[i].chatMessages[j], "%s", str.c_str()); + } + } + + auto data_ptr = (u8*)&resp; + m_read_queue.insert(m_read_queue.end(), data_ptr, + data_ptr + sizeof(SlippiExiTypes::GetPlayerSettingsResponse)); +} + void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) { auto& system = Core::System::GetInstance(); @@ -3123,6 +3161,9 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) handleCompleteSet( SlippiExiTypes::Convert(&memPtr[bufLoc])); break; + case CMD_GET_PLAYER_SETTINGS: + handleGetPlayerSettings(); + break; default: writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, ""); SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h index c4b61fe842..9c7fa34216 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h @@ -23,6 +23,7 @@ #define ROLLBACK_MAX_FRAMES 7 #define MAX_NAME_LENGTH 15 +#define MAX_MESSAGE_LENGTH 25 #define CONNECT_CODE_LENGTH 8 namespace ExpansionInterface @@ -80,6 +81,7 @@ private: CMD_GP_COMPLETE_STEP = 0xC0, CMD_GP_FETCH_STEP = 0xC1, CMD_REPORT_SET_COMPLETE = 0xC2, + CMD_GET_PLAYER_SETTINGS = 0xC3, // Misc CMD_LOG_MESSAGE = 0xD0, @@ -137,6 +139,7 @@ private: {CMD_GP_FETCH_STEP, static_cast(sizeof(SlippiExiTypes::GpFetchStepQuery) - 1)}, {CMD_REPORT_SET_COMPLETE, static_cast(sizeof(SlippiExiTypes::ReportSetCompletionQuery) - 1)}, + {CMD_GET_PLAYER_SETTINGS, 0}, // Misc {CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself @@ -203,6 +206,7 @@ private: void handleGamePrepStepComplete(const SlippiExiTypes::GpCompleteStepQuery& query); void prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery& query); void handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuery& query); + void handleGetPlayerSettings(); // replay playback stuff void prepareGameInfo(u8* payload); diff --git a/Source/Core/Core/Slippi/SlippiExiTypes.h b/Source/Core/Core/Slippi/SlippiExiTypes.h index cffa17447c..ecb46ae4f7 100644 --- a/Source/Core/Core/Slippi/SlippiExiTypes.h +++ b/Source/Core/Core/Slippi/SlippiExiTypes.h @@ -80,6 +80,15 @@ struct OverwriteSelectionsQuery OverwriteCharSelections chars[4]; }; +struct PlayerSettings +{ + char chatMessages[16][51]; +}; +struct GetPlayerSettingsResponse +{ + PlayerSettings settings[4]; +}; + // Not sure if resetting is strictly needed, might be contained to the file #pragma pack() diff --git a/Source/Core/Core/Slippi/SlippiGameReporter.cpp b/Source/Core/Core/Slippi/SlippiGameReporter.cpp index c5ede4d4cb..706673f69b 100644 --- a/Source/Core/Core/Slippi/SlippiGameReporter.cpp +++ b/Source/Core/Core/Slippi/SlippiGameReporter.cpp @@ -123,9 +123,6 @@ SlippiGameReporter::SlippiGameReporter(SlippiUser* user, const std::string curre INFO_LOG_FMT(SLIPPI_ONLINE, "MD5 Hash: {}", this->m_iso_hash); }); m_md5_thread.detach(); - - run_thread = true; - reporting_thread = std::thread(&SlippiGameReporter::ReportThreadHandler, this); } SlippiGameReporter::~SlippiGameReporter() diff --git a/Source/Core/Core/Slippi/SlippiMatchmaking.cpp b/Source/Core/Core/Slippi/SlippiMatchmaking.cpp index f83b18d403..21434c64da 100644 --- a/Source/Core/Core/Slippi/SlippiMatchmaking.cpp +++ b/Source/Core/Core/Slippi/SlippiMatchmaking.cpp @@ -507,6 +507,15 @@ void SlippiMatchmaking::handleMatchmaking() playerInfo.display_name = el.value("displayName", ""); playerInfo.connect_code = el.value("connectCode", ""); playerInfo.port = el.value("port", 0); + playerInfo.chat_messages = SlippiUser::default_chat_messages; + if (el["chatMessages"].is_array()) + { + playerInfo.chat_messages = el.value("chatMessages", SlippiUser::default_chat_messages); + if (playerInfo.chat_messages.size() != 16) + { + playerInfo.chat_messages = SlippiUser::default_chat_messages; + } + } m_playerInfo.push_back(playerInfo); if (isLocal) diff --git a/Source/Core/Core/Slippi/SlippiUser.cpp b/Source/Core/Core/Slippi/SlippiUser.cpp index 616ddb8eea..6e6e3ab9e2 100644 --- a/Source/Core/Core/Slippi/SlippiUser.cpp +++ b/Source/Core/Core/Slippi/SlippiUser.cpp @@ -22,6 +22,28 @@ #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) @@ -173,17 +195,17 @@ void SlippiUser::OpenLogInPage() void SlippiUser::UpdateApp() { - std::string url = "https://slippi.gg/downloads?update=true"; + std::string url = "https://slippi.gg/downloads?update=true"; #ifdef _WIN32 - std::string command = "explorer \"" + url + "\""; + std::string command = "explorer \"" + url + "\""; #elif defined(__APPLE__) - std::string command = "open \"" + url + "\""; + std::string command = "open \"" + url + "\""; #else - std::string command = "xdg-open \"" + url + "\""; // Linux + std::string command = "xdg-open \"" + url + "\""; // Linux #endif - RunSystemCommand(command); + RunSystemCommand(command); } void SlippiUser::ListenForLogIn() @@ -279,6 +301,15 @@ SlippiUser::UserInfo SlippiUser::parseFile(std::string file_contents) 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; } @@ -296,7 +327,8 @@ void SlippiUser::overwriteFromServer() // Perform curl request std::string resp; - curl_easy_setopt(m_curl, CURLOPT_URL, (URL_START + "/" + m_user_info.uid).c_str()); + 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); @@ -320,4 +352,12 @@ void SlippiUser::overwriteFromServer() 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; + } + } } diff --git a/Source/Core/Core/Slippi/SlippiUser.h b/Source/Core/Core/Slippi/SlippiUser.h index 0da36d7a5b..bd04ff9868 100644 --- a/Source/Core/Core/Slippi/SlippiUser.h +++ b/Source/Core/Core/Slippi/SlippiUser.h @@ -21,6 +21,8 @@ public: std::string file_contents = ""; int port; + + std::vector chat_messages; }; SlippiUser(); @@ -36,6 +38,8 @@ public: bool IsLoggedIn(); void FileListenThread(); + const static std::vector default_chat_messages; + protected: std::string getUserFilePath(); UserInfo parseFile(std::string file_contents); diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 0af0714d9a..20cb701946 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -513,7 +513,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