diff --git a/Data/Sys/GameFiles/GALE01/slpCSS.dat b/Data/Sys/GameFiles/GALE01/slpCSS.dat index f2c8638ea5..2af007e1e8 100644 Binary files a/Data/Sys/GameFiles/GALE01/slpCSS.dat and b/Data/Sys/GameFiles/GALE01/slpCSS.dat differ diff --git a/Source/Core/Core/HW/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI_DeviceSlippi.cpp index 9459ac4abd..bd53582aa4 100644 --- a/Source/Core/Core/HW/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI_DeviceSlippi.cpp @@ -48,7 +48,7 @@ #define SLEEP_TIME_MS 8 #define WRITE_FILE_SLEEP_TIME_MS 85 -// #define LOCAL_TESTING +#define LOCAL_TESTING // #define CREATE_DIFF_FILES static std::unordered_map slippi_names; @@ -3138,6 +3138,33 @@ void CEXISlippi::handleGetPlayerSettings() m_read_queue.insert(m_read_queue.end(), data_ptr, data_ptr + sizeof(SlippiExiTypes::GetPlayerSettingsResponse)); } +void CEXISlippi::handleGetRank() +{ + auto userInfo = user->GetUserInfo(); + auto prevRankInfo = user->GetRankInfo(); + auto rankInfo = user->FetchUserRank(userInfo.connectCode); + + u8 rank = rankInfo.rank; + float ratingOrdinal = rankInfo.ratingOrdinal; + u8 global = rankInfo.globalPlacing; + u8 regional = rankInfo.regionalPlacing; + u8 ratingUpdateCount = rankInfo.ratingUpdateCount; + float ratingChange = rankInfo.ratingChange; + int rankChange = rankInfo.rankChange; + + m_read_queue.clear(); + m_read_queue.push_back(rank); + + appendWordToBuffer(&m_read_queue, *(u32 *)&ratingOrdinal); + + m_read_queue.push_back(global); + m_read_queue.push_back(regional); + m_read_queue.push_back(ratingUpdateCount); + + appendWordToBuffer(&m_read_queue, *(u32 *)&ratingChange); + appendWordToBuffer(&m_read_queue, *(u32 *)&rankChange); +} + void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) { u8 *memPtr = Memory::GetPointer(_uAddr); @@ -3310,6 +3337,9 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) case CMD_GET_PLAYER_SETTINGS: handleGetPlayerSettings(); break; + case CMD_GET_RANK: + handleGetRank(); + break; case CMD_PLAY_MUSIC: { auto args = SlippiExiTypes::Convert(&memPtr[bufLoc]); diff --git a/Source/Core/Core/HW/EXI_DeviceSlippi.h b/Source/Core/Core/HW/EXI_DeviceSlippi.h index 1b581da0e8..07893e76db 100644 --- a/Source/Core/Core/HW/EXI_DeviceSlippi.h +++ b/Source/Core/Core/HW/EXI_DeviceSlippi.h @@ -84,6 +84,7 @@ class CEXISlippi : public IEXIDevice CMD_GP_FETCH_STEP = 0xC1, CMD_REPORT_SET_COMPLETE = 0xC2, CMD_GET_PLAYER_SETTINGS = 0xC3, + CMD_GET_RANK = 0xC4, // Misc CMD_LOG_MESSAGE = 0xD0, @@ -143,6 +144,7 @@ class CEXISlippi : public IEXIDevice {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}, + {CMD_GET_RANK, 0}, // Misc {CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself @@ -222,6 +224,7 @@ class CEXISlippi : public IEXIDevice void prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery &query); void handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuery &query); void handleGetPlayerSettings(); + void handleGetRank(); // replay playback stuff void prepareGameInfo(u8 *payload); diff --git a/Source/Core/Core/Slippi/SlippiUser.cpp b/Source/Core/Core/Slippi/SlippiUser.cpp index 10b81dc2dc..f0a57c9bcb 100644 --- a/Source/Core/Core/Slippi/SlippiUser.cpp +++ b/Source/Core/Core/Slippi/SlippiUser.cpp @@ -25,10 +25,208 @@ std::vector ConvertChatMessagesFromRust(RustChatMessages *rsMessage SlippiUser::SlippiUser(uintptr_t rs_exi_device_ptr) { slprs_exi_device_ptr = rs_exi_device_ptr; + + InitUserRank(); } SlippiUser::~SlippiUser() {} +void SlippiUser::InitUserRank() { + userRank.rank = SlippiUser::SlippiRank (0); + userRank.ratingOrdinal = 0.0f; + userRank.globalPlacing = 0; + userRank.regionalPlacing = 0; + userRank.ratingUpdateCount = 0; + userRank.ratingChange = 0.0f; + userRank.rankChange = 0; +} + +SlippiUser::RankInfo SlippiUser::GetRankInfo() { + return userRank; +} + +SlippiUser::SlippiRank SlippiUser::GetRank(float ratingOrdinal, int globalPlacing, int regionalPlacing, int ratingUpdateCount) +{ + if (ratingUpdateCount < 5) + return RANK_UNRANKED; + if (ratingOrdinal > 0 && ratingOrdinal <= 765.42) + return RANK_BRONZE_1; + if (ratingOrdinal > 765.43 && ratingOrdinal <= 913.71) + return RANK_BRONZE_2; + if (ratingOrdinal > 913.72 && ratingOrdinal <= 1054.86) + return RANK_BRONZE_3; + if (ratingOrdinal > 1054.87 && ratingOrdinal <= 1188.87) + return RANK_SILVER_1; + if (ratingOrdinal > 1188.88 && ratingOrdinal <= 1315.74) + return RANK_SILVER_2; + if (ratingOrdinal > 1315.75 && ratingOrdinal <= 1435.47) + return RANK_SILVER_3; + if (ratingOrdinal > 1435.48 && ratingOrdinal <= 1548.06) + return RANK_GOLD_1; + if (ratingOrdinal > 1548.07 && ratingOrdinal <= 1653.51) + return RANK_GOLD_2; + if (ratingOrdinal > 1653.52 && ratingOrdinal <= 1751.82) + return RANK_GOLD_3; + if (ratingOrdinal > 1751.83 && ratingOrdinal <= 1842.99) + return RANK_PLATINUM_1; + if (ratingOrdinal > 1843 && ratingOrdinal <= 1927.02) + return RANK_PLATINUM_2; + if (ratingOrdinal > 1927.03 && ratingOrdinal <= 2003.91) + return RANK_PLATINUM_3; + if (ratingOrdinal > 2003.92 && ratingOrdinal <= 2073.66) + return RANK_DIAMOND_1; + if (ratingOrdinal > 2073.67 && ratingOrdinal <= 2136.27) + return RANK_DIAMOND_2; + if (ratingOrdinal > 2136.28 && ratingOrdinal <= 2191.74) + return RANK_DIAMOND_3; + if (ratingOrdinal >= 2191.75 && globalPlacing && regionalPlacing) + return RANK_GRANDMASTER; + if (ratingOrdinal > 2191.75 && ratingOrdinal <= 2274.99) + return RANK_MASTER_1; + if (ratingOrdinal > 2275 && ratingOrdinal <= 2350) + return RANK_MASTER_2; + if (ratingOrdinal > 2350) + return RANK_MASTER_3; + return RANK_UNRANKED; +} + +SlippiUser::RankInfo SlippiUser::FetchUserRank(std::string connectCode) +{ + RankInfo info; + + const char *query = + "fragment profileFields on NetplayProfile {\n" + " id\n" + " ratingOrdinal\n" + " ratingUpdateCount\n" + " wins\n" + " losses\n" + " dailyGlobalPlacement\n" + " dailyRegionalPlacement\n" + " continent\n characters {\n" + " id\n" + " character\n" + " gameCount\n" + " __typename\n" + " }\n" + " __typename\n" + "}\n" + "\n" + "fragment userProfilePage on User {\n" + " fbUid\n" + " displayName\n" + " connectCode {\n" + " code\n" + " __typename\n" + " }\n" + " status\n" + " activeSubscription {\n" + " level\n" + " hasGiftSub\n" + " __typename\n" + " }\n" + " rankedNetplayProfile {\n" + " ...profileFields\n" + " __typename\n" + " }\n" + " netplayProfiles {\n" + " ...profileFields\n" + " season {\n" + " id\n" + " startedAt\n" + " endedAt\n" + " name\n" + " status\n" + " __typename\n" + " }\n" + " __typename\n" + " }\n" + " __typename\n" + "}\n" + "\n" + "query AccountManagementPageQuery($cc: String!, $uid: String!) {\n" + " getUser(fbUid: $uid) {\n" + " ...userProfilePage\n" + " __typename\n" + " }\n" + " getConnectCode(code: $cc) {\n" + " user {\n" + " ...userProfilePage\n" + " __typename\n" + " }\n" + " __typename\n" + " }\n" + "}\n"; + + std::string url = "https://gql-gateway-dot-slippi.uc.r.appspot.com/graphql"; + json body = {{"operationName", "AccountManagementPageQuery"}, + {"variables", {{"cc", connectCode}, {"uid", connectCode}}}, + {"query", query}}; + + // INFO_LOG(SLIPPI_ONLINE, "Preparing request..."); + // Perform curl request + std::string resp; + curl_easy_setopt(m_curl, CURLOPT_URL, (url).c_str()); + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, (body.dump()).c_str()); + curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "POST"); + + CURLcode res = curl_easy_perform(m_curl); + + // INFO_LOG(SLIPPI_ONLINE, "Request sent"); + if (res != 0) + { + ERROR_LOG(SLIPPI, "[User] Error fetching user info from server, code: %d", res); + //return info; + } + + long responseCode; + curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &responseCode); + if (responseCode != 200) + { + ERROR_LOG(SLIPPI, "[User] Server responded with non-success status: %d", responseCode); + //return info; + } + + auto r = json::parse(resp); + auto rankedObject = r["data"]["getConnectCode"]["user"]["rankedNetplayProfile"]; + + float ratingOrdinal = rankedObject["ratingOrdinal"]; + INFO_LOG(SLIPPI_ONLINE, "Rating: %0000.00f", ratingOrdinal); + + u8 global = (rankedObject["dailyGlobalPlacement"]).is_null() ? 0 : rankedObject["dailyGlobalPlacement"]; + INFO_LOG(SLIPPI_ONLINE, "Global Placing: %d", global); + + u8 regional = (rankedObject["dailyRegionalPlacement"]).is_null() ? 0 : rankedObject["dailyRegionalPlacement"]; + INFO_LOG(SLIPPI_ONLINE, "Regional Placing: %d", regional); + + u8 ratingUpdateCount = (rankedObject["ratingUpdateCount"]).is_null() ? 0 : rankedObject["ratingUpdateCount"]; + INFO_LOG(SLIPPI_ONLINE, "Rating Update Count: %d", ratingUpdateCount); + + SlippiRank rank = GetRank(ratingOrdinal, global, regional, ratingUpdateCount); + INFO_LOG(SLIPPI_ONLINE, "Rank: %d", rank); + + float ratingChange = (userRank.ratingOrdinal > 0.001f) ? ratingOrdinal - userRank.ratingOrdinal : 0; + INFO_LOG(SLIPPI_ONLINE, "Rating Change: %0.1f", ratingChange); + + int rankChange = (userRank.rank > 0.001f) ? rank - userRank.rank : 0; + INFO_LOG(SLIPPI_ONLINE, "userRank: %d", userRank.rank); + INFO_LOG(SLIPPI_ONLINE, "Rank Change: %d", rankChange); + + info.rank = rank; + info.ratingOrdinal = ratingOrdinal; + info.globalPlacing = global; + info.regionalPlacing = regional; + info.ratingUpdateCount = ratingUpdateCount; + info.ratingChange = ratingChange; + info.rankChange = rankChange; + + // Set user rank + userRank = info; + + return info; +} + bool SlippiUser::AttemptLogin() { return slprs_user_attempt_login(slprs_exi_device_ptr); diff --git a/Source/Core/Core/Slippi/SlippiUser.h b/Source/Core/Core/Slippi/SlippiUser.h index 324b7a56db..f0a6f26979 100644 --- a/Source/Core/Core/Slippi/SlippiUser.h +++ b/Source/Core/Core/Slippi/SlippiUser.h @@ -18,6 +18,30 @@ class SlippiUser { public: + enum SlippiRank + { + RANK_UNRANKED, + RANK_BRONZE_1, + RANK_BRONZE_2, + RANK_BRONZE_3, + RANK_SILVER_1, + RANK_SILVER_2, + RANK_SILVER_3, + RANK_GOLD_1, + RANK_GOLD_2, + RANK_GOLD_3, + RANK_PLATINUM_1, + RANK_PLATINUM_2, + RANK_PLATINUM_3, + RANK_DIAMOND_1, + RANK_DIAMOND_2, + RANK_DIAMOND_3, + RANK_MASTER_1, + RANK_MASTER_2, + RANK_MASTER_3, + RANK_GRANDMASTER, + }; + // This type is filled in with data from the Rust side. // Eventually, this entire class will disappear. struct UserInfo @@ -33,6 +57,28 @@ class SlippiUser std::vector chatMessages; }; + struct RankInfo + { + SlippiRank rank; + float ratingOrdinal; + u8 globalPlacing; + u8 regionalPlacing; + u8 ratingUpdateCount; + float ratingChange; + int rankChange; + }; + + struct RankInfo + { + SlippiRank rank; + float ratingOrdinal; + u8 globalPlacing; + u8 regionalPlacing; + u8 ratingUpdateCount; + float ratingChange; + int rankChange; + }; + SlippiUser(uintptr_t rs_exi_device_ptr); ~SlippiUser(); @@ -46,6 +92,15 @@ class SlippiUser std::vector GetUserChatMessages(); std::vector GetDefaultChatMessages(); bool IsLoggedIn(); + void FileListenThread(); + + RankInfo FetchUserRank(std::string connectCode); + RankInfo GetRankInfo(); + void InitUserRank(); + + SlippiRank GetRank(float ratingOrdinal, int globalPlacing, int regionalPlacing, int ratingUpdateCount); + + const static std::vector defaultChatMessages; protected: // A pointer to a "shadow" EXI Device that lives on the Rust side of things.