Skip to content

Commit

Permalink
refactor chat and add configurable chat messages (project-slippi#369)
Browse files Browse the repository at this point in the history
* update asm and c codeset

- Make MxDt reference our CSS methods

* chore: update asm codeset

* update codeset and fix default debug command

* chore: update codeset chat refactor

* update codeset

* Update codeset

* Update codeset

* chore: update asm and c codesets

project-slippi/slippi-ssbm-asm#106

* Use iconv fallbacks on macos so special and SJIS characters are properly converted and rendered in-game.

* build codesets

* start support for configured chat messages

* use test default messages

* receive chat messages from mm service

* feature: show configured messages in preview

* chore: clean comments and loggers

* fix: ifdef iconvctl for mac only

* chore: try to get linux building

* chore: use nullptr instead of NULL

* chore: ifdef attempt 3 for linux

* chore: dont call functions that dont exist

* swap elif for else

* undo template name move

* chore: update code lists

---------

Co-authored-by: Jas Laferriere <Fizzi36@gmail.com>
Co-authored-by: Nikhil Narayana <nikhil.narayana@live.com>
  • Loading branch information
3 people committed Aug 31, 2023
1 parent 1b4cec9 commit 1c46647
Show file tree
Hide file tree
Showing 16 changed files with 704 additions and 1,268 deletions.
Binary file modified Data/Sys/GameFiles/GALE01/GameSetup.dat
Binary file not shown.
Binary file modified Data/Sys/GameFiles/GALE01/SlippiCSS.dat
Binary file not shown.
Binary file modified Data/Sys/GameFiles/GALE01/slpCSS.dat
Binary file not shown.
882 changes: 255 additions & 627 deletions Data/Sys/GameSettings/GALE01r2.ini

Large diffs are not rendered by default.

882 changes: 255 additions & 627 deletions Data/Sys/GameSettings/GALJ01r2.ini

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Data/Sys/Slippi/InjectionLists/list_netplay-old2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Details": [
{
"InjectionAddress": "800056AC",
"Name": "Required: Slippi Online",
"Codetype": "Auto",
"Annotation": "Online/Static/ChatMessages.asm",
"Tags": ""
}
]
}
7 changes: 0 additions & 7 deletions Data/Sys/Slippi/InjectionLists/list_netplay.json
Original file line number Diff line number Diff line change
Expand Up @@ -1232,13 +1232,6 @@
"Annotation": "Online/Static/CSSUpdateCSP.asm",
"Tags": ""
},
{
"InjectionAddress": "800056AC",
"Name": "Required: Slippi Online",
"Codetype": "Auto",
"Annotation": "Online/Static/ChatMessages.asm",
"Tags": ""
},
{
"InjectionAddress": "80005690",
"Name": "Required: Slippi Online",
Expand Down
68 changes: 64 additions & 4 deletions Source/Core/Common/StringUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -544,11 +544,25 @@ std::string UTF32toUTF8(const std::u32string &input)
}
#else
template <typename T>
std::string CodeTo(const char *tocode, const char *fromcode, const std::basic_string<T>& input)
#ifdef __APPLE__
std::string CodeToWithFallbacks(const char *tocode, const char *fromcode, const std::basic_string<T> &input,
iconv_fallbacks *fallbacks)
#else
std::string CodeTo(const char *tocode, const char *fromcode, const std::basic_string<T> &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(COMMON, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno));
Expand Down Expand Up @@ -599,6 +613,13 @@ std::string CodeTo(const char *tocode, const char *fromcode, const std::basic_st
return result;
}

#ifdef __APPLE__
template <typename T> std::string CodeTo(const char *tocode, const char *fromcode, const std::basic_string<T> &input)
{
return CodeToWithFallbacks(tocode, fromcode, input, nullptr);
}
#endif

template <typename T>
std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input)
{
Expand All @@ -618,9 +639,48 @@ std::string SHIFTJISToUTF8(const std::string& input)
return CodeToUTF8("SJIS", input);
}

std::string UTF8ToSHIFTJIS(const std::string& 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)
{
static std::unordered_map<unsigned int, const char*> 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)
{
return CodeTo("SJIS", "UTF-8", 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 UTF16ToUTF8(const std::wstring& input)
Expand Down Expand Up @@ -671,4 +731,4 @@ std::string ValueToString(int value)
std::string ValueToString(bool value)
{
return value ? "True" : "False";
}
}
9 changes: 9 additions & 0 deletions Source/Core/Common/StringUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ std::string SHIFTJISToUTF8(const std::string& str);
std::string UTF8ToSHIFTJIS(const std::string& str);
std::string UTF16ToUTF8(const std::wstring &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 UTF8ToUTF16(const std::string& str);
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Common/Version.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//" " BUILD_TYPE_STR " " SCM_DESC_STR;
//#endif
#ifndef IS_PLAYBACK
#define SLIPPI_REV_STR "3.0.4" // netplay version
#define SLIPPI_REV_STR "3.0.5-dev.1" // netplay version
#else
#define SLIPPI_REV_STR "3.0.1" // playback version
#endif
Expand Down
41 changes: 40 additions & 1 deletion Source/Core/Core/HW/EXI_DeviceSlippi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2070,7 +2070,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.displayName;
oppName = p2Name = "Player 2";
#endif

Expand Down Expand Up @@ -3020,6 +3020,42 @@ void CEXISlippi::handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuer
}
}

void CEXISlippi::handleGetPlayerSettings()
{
m_read_queue.clear();

SlippiExiTypes::GetPlayerSettingsResponse resp = {};

std::vector<std::vector<std::string>> messagesByPlayer = {
SlippiUser::defaultChatMessages, SlippiUser::defaultChatMessages, SlippiUser::defaultChatMessages,
SlippiUser::defaultChatMessages};

// These chat messages will be used when previewing messages
auto userChatMessages = user->GetUserInfo().chatMessages;
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.chatMessages;
}

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)
{
u8 *memPtr = Memory::GetPointer(_uAddr);
Expand Down Expand Up @@ -3187,6 +3223,9 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
case CMD_REPORT_SET_COMPLETE:
handleCompleteSet(SlippiExiTypes::Convert<SlippiExiTypes::ReportSetCompletionQuery>(&memPtr[bufLoc]));
break;
case CMD_GET_PLAYER_SETTINGS:
handleGetPlayerSettings();
break;
default:
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
m_slippiserver->write(&memPtr[bufLoc], payloadLen + 1);
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/Core/HW/EXI_DeviceSlippi.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#define ROLLBACK_MAX_FRAMES 7
#define MAX_NAME_LENGTH 15
#define MAX_MESSAGE_LENGTH 25
#define CONNECT_CODE_LENGTH 8

extern bool g_needInputForFrame;
Expand Down Expand Up @@ -79,6 +80,7 @@ class CEXISlippi : public IEXIDevice
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,
Expand Down Expand Up @@ -134,6 +136,7 @@ class CEXISlippi : public IEXIDevice
{CMD_GP_COMPLETE_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpCompleteStepQuery) - 1)},
{CMD_GP_FETCH_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpFetchStepQuery) - 1)},
{CMD_REPORT_SET_COMPLETE, static_cast<u32>(sizeof(SlippiExiTypes::ReportSetCompletionQuery) - 1)},
{CMD_GET_PLAYER_SETTINGS, 0},

// Misc
{CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself
Expand Down Expand Up @@ -205,6 +208,7 @@ class CEXISlippi : public IEXIDevice
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);
Expand Down
9 changes: 9 additions & 0 deletions Source/Core/Core/Slippi/SlippiExiTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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()

Expand Down
10 changes: 10 additions & 0 deletions Source/Core/Core/Slippi/SlippiMatchmaking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,16 @@ void SlippiMatchmaking::handleMatchmaking()
playerInfo.displayName = el.value("displayName", "");
playerInfo.connectCode = el.value("connectCode", "");
playerInfo.port = el.value("port", 0);
playerInfo.chatMessages = SlippiUser::defaultChatMessages;
if (el["chatMessages"].is_array())
{
playerInfo.chatMessages = el.value("chatMessages", SlippiUser::defaultChatMessages);
if (playerInfo.chatMessages.size() != 16)
{
playerInfo.chatMessages = SlippiUser::defaultChatMessages;
}
}

m_playerInfo.push_back(playerInfo);

if (isLocal)
Expand Down
43 changes: 42 additions & 1 deletion Source/Core/Core/Slippi/SlippiUser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@
#include <json.hpp>
using json = nlohmann::json;

const std::vector<std::string> SlippiUser::defaultChatMessages = {
"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)
Expand Down Expand Up @@ -275,6 +297,16 @@ SlippiUser::UserInfo SlippiUser::parseFile(std::string fileContents)
info.connectCode = readString(res, "connectCode");
info.latestVersion = readString(res, "latestVersion");

info.chatMessages = SlippiUser::defaultChatMessages;
if (res["chatMessages"].is_array())
{
info.chatMessages = res.value("chatMessages", SlippiUser::defaultChatMessages);
if (info.chatMessages.size() != 16)
{
info.chatMessages = SlippiUser::defaultChatMessages;
}
}

return info;
}

Expand All @@ -300,7 +332,7 @@ void SlippiUser::overwriteFromServer()

// Perform curl request
std::string resp;
curl_easy_setopt(m_curl, CURLOPT_URL, (url + "/" + userInfo.uid).c_str());
curl_easy_setopt(m_curl, CURLOPT_URL, (url + "/" + userInfo.uid + "?additionalFields=chatMessages").c_str());
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &resp);
CURLcode res = curl_easy_perform(m_curl);

Expand All @@ -323,4 +355,13 @@ void SlippiUser::overwriteFromServer()
userInfo.connectCode = r.value("connectCode", userInfo.connectCode);
userInfo.latestVersion = r.value("latestVersion", userInfo.latestVersion);
userInfo.displayName = r.value("displayName", userInfo.displayName);

if (r["chatMessages"].is_array())
{
userInfo.chatMessages = r.value("chatMessages", SlippiUser::defaultChatMessages);
if (userInfo.chatMessages.size() != 16)
{
userInfo.chatMessages = SlippiUser::defaultChatMessages;
}
}
}
4 changes: 4 additions & 0 deletions Source/Core/Core/Slippi/SlippiUser.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class SlippiUser
std::string fileContents = "";

int port;

std::vector<std::string> chatMessages;
};

SlippiUser();
Expand All @@ -36,6 +38,8 @@ class SlippiUser
bool IsLoggedIn();
void FileListenThread();

const static std::vector<std::string> defaultChatMessages;

protected:
UserInfo parseFile(std::string fileContents);
void deleteFile();
Expand Down

0 comments on commit 1c46647

Please sign in to comment.