mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-22 02:09:06 +00:00
Rust Game Reporter (deaddda)
This commit is contained in:
parent
065823f71e
commit
85f5914c5b
9 changed files with 144 additions and 658 deletions
|
@ -62,6 +62,7 @@ enum class LogType : int
|
|||
SLIPPI_ONLINE,
|
||||
SLIPPI_RUST_DEPENDENCIES,
|
||||
SLIPPI_RUST_EXI,
|
||||
SLIPPI_RUST_GAME_REPORTER,
|
||||
SLIPPI_RUST_JUKEBOX,
|
||||
SP1,
|
||||
SYMBOLS,
|
||||
|
|
|
@ -166,6 +166,7 @@ LogManager::LogManager()
|
|||
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_JUKEBOX] = {"SLIPPI_RUST_JUKEBOX", "[Rust] Slippi Jukebox", false,
|
||||
true};
|
||||
m_log[LogType::SP1] = {"SP1", "Serial Port 1"};
|
||||
|
|
|
@ -534,8 +534,6 @@ add_library(core
|
|||
Slippi/SlippiUser.h
|
||||
Slippi/SlippiGame.cpp
|
||||
Slippi/SlippiGame.h
|
||||
Slippi/SlippiGameReporter.cpp
|
||||
Slippi/SlippiGameReporter.h
|
||||
Slippi/SlippiDirectCodes.cpp
|
||||
Slippi/SlippiPremadeText.h
|
||||
State.cpp
|
||||
|
|
|
@ -120,7 +120,7 @@ private:
|
|||
// it, as this class creates the CEXIMemoryCard instances.
|
||||
Memcard::HeaderData m_memcard_header_data;
|
||||
|
||||
// used by SlippiGameReporter for calculating the md5
|
||||
// used by game_reporter (rust) for calculating the md5 and Slippi Jukebox (rust) for playback
|
||||
std::string m_current_file_name;
|
||||
|
||||
// Devices
|
||||
|
|
|
@ -146,7 +146,6 @@ CEXISlippi::CEXISlippi(Core::System& system, const std::string current_file_name
|
|||
g_playbackStatus = std::make_unique<SlippiPlaybackStatus>();
|
||||
matchmaking = std::make_unique<SlippiMatchmaking>(user.get());
|
||||
gameFileLoader = std::make_unique<SlippiGameFileLoader>();
|
||||
game_reporter = std::make_unique<SlippiGameReporter>(user.get(), current_file_name);
|
||||
g_replayComm = std::make_unique<SlippiReplayComm>();
|
||||
directCodes = std::make_unique<SlippiDirectCodes>("direct-codes.json");
|
||||
teamsCodes = std::make_unique<SlippiDirectCodes>("teams-codes.json");
|
||||
|
@ -265,9 +264,6 @@ CEXISlippi::~CEXISlippi()
|
|||
{
|
||||
u8 empty[1];
|
||||
|
||||
// Instruct the Rust EXI device to shut down/drop everything.
|
||||
slprs_exi_device_destroy(slprs_exi_device_ptr);
|
||||
|
||||
// Closes file gracefully to prevent file corruption when emulation
|
||||
// suddenly stops. This would happen often on netplay when the opponent
|
||||
// would close the emulation before the file successfully finished writing
|
||||
|
@ -287,7 +283,9 @@ 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);
|
||||
game_reporter->ReportAbandonment(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());
|
||||
}
|
||||
handleConnectionCleanup();
|
||||
|
||||
|
@ -295,6 +293,9 @@ CEXISlippi::~CEXISlippi()
|
|||
|
||||
g_playbackStatus->resetPlayback();
|
||||
|
||||
// Instruct the Rust EXI device to shut down/drop everything.
|
||||
slprs_exi_device_destroy(slprs_exi_device_ptr);
|
||||
|
||||
// TODO: ENET shutdown should maybe be done at app shutdown instead.
|
||||
// Right now this might be problematic in the case where someone starts a netplay client
|
||||
// and then queues into online matchmaking, and then stops the game. That might deinit
|
||||
|
@ -358,7 +359,7 @@ std::vector<u8> CEXISlippi::generateMetadata()
|
|||
// Add game start time
|
||||
u8 dateTimeStrLength = sizeof "2011-10-08T07:07:09Z";
|
||||
std::vector<char> dateTimeBuf(dateTimeStrLength);
|
||||
strftime(&dateTimeBuf[0], dateTimeStrLength, "%FT%TZ", gmtime(&gameStartTime));
|
||||
strftime(&dateTimeBuf[0], dateTimeStrLength, "%FT%TZ", gmtime(&game_start_time));
|
||||
dateTimeBuf.pop_back(); // Removes the \0 from the back of string
|
||||
metadata.insert(metadata.end(), {'U', 7, 's', 't', 'a', 'r', 't', 'A', 't', 'S', 'U',
|
||||
static_cast<u8>(dateTimeBuf.size())});
|
||||
|
@ -519,12 +520,12 @@ void CEXISlippi::writeToFile(std::unique_ptr<WriteMessage> msg)
|
|||
// Get display names and connection codes from slippi netplay client
|
||||
if (slippi_netplay)
|
||||
{
|
||||
auto playerInfo = matchmaking->GetPlayerInfo();
|
||||
auto player_info = matchmaking->GetPlayerInfo();
|
||||
|
||||
for (int i = 0; i < playerInfo.size(); i++)
|
||||
for (int i = 0; i < player_info.size(); i++)
|
||||
{
|
||||
slippi_names[i] = playerInfo[i].display_name;
|
||||
slippi_connect_codes[i] = playerInfo[i].connect_code;
|
||||
slippi_names[i] = player_info[i].display_name;
|
||||
slippi_connect_codes[i] = player_info[i].connect_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,7 +611,7 @@ void CEXISlippi::createNewFile()
|
|||
// Append YYYY-MM to the directory path
|
||||
uint8_t yearMonthStrLength = sizeof "2020-06-Mainline";
|
||||
std::vector<char> yearMonthBuf(yearMonthStrLength);
|
||||
strftime(&yearMonthBuf[0], yearMonthStrLength, "%Y-%m-Mainline", localtime(&gameStartTime));
|
||||
strftime(&yearMonthBuf[0], yearMonthStrLength, "%Y-%m-Mainline", localtime(&game_start_time));
|
||||
|
||||
std::string yearMonth(&yearMonthBuf[0]);
|
||||
dirpath.append(yearMonth);
|
||||
|
@ -644,7 +645,7 @@ std::string CEXISlippi::generateFileName()
|
|||
// Add game start time
|
||||
u8 dateTimeStrLength = sizeof "20171015T095717";
|
||||
std::vector<char> dateTimeBuf(dateTimeStrLength);
|
||||
strftime(&dateTimeBuf[0], dateTimeStrLength, "%Y%m%dT%H%M%S", localtime(&gameStartTime));
|
||||
strftime(&dateTimeBuf[0], dateTimeStrLength, "%Y%m%dT%H%M%S", localtime(&game_start_time));
|
||||
|
||||
std::string str(&dateTimeBuf[0]);
|
||||
return StringFromFormat("Game_%s.slp", str.c_str());
|
||||
|
@ -1464,7 +1465,7 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
|
|||
|
||||
bool isSlow =
|
||||
(offsetUs < -t1 && fallBehindCounter > 50) || (offsetUs < -t2 && fallFarBehindCounter > 15);
|
||||
if (isSlow && lastSearch.mode != SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||
if (isSlow && last_search.mode != SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||
{
|
||||
// We don't show this message for teams because it seems to false positive a lot there, maybe
|
||||
// because the min offset is always selected? Idk I feel like doubles has some perf issues I
|
||||
|
@ -1569,7 +1570,7 @@ void CEXISlippi::prepareOpponentInputs(s32 frame, bool shouldSkip)
|
|||
results[i] = slippi_netplay->GetSlippiRemotePad(i, ROLLBACK_MAX_FRAMES);
|
||||
// results[i] = slippi_netplay->GetFakePadOutput(frame);
|
||||
|
||||
// INFO_LOG(SLIPPI_ONLINE, "Sending checksum values: [%d] %08x", results[i]->checksum_frame,
|
||||
// INFO_LOG_FMT(SLIPPI_ONLINE, "Sending checksum values: [{}] %08x", results[i]->checksum_frame,
|
||||
// results[i]->checksum);
|
||||
appendWordToBuffer(&m_read_queue, static_cast<u32>(results[i]->checksum_frame));
|
||||
appendWordToBuffer(&m_read_queue, results[i]->checksum);
|
||||
|
@ -1672,7 +1673,7 @@ void CEXISlippi::handleCaptureSavestate(u8* payload)
|
|||
activeSavestates[frame] = std::move(ss);
|
||||
|
||||
// u32 timeDiff = (u32)(Common::Timer::NowUs() - startTime);
|
||||
// INFO_LOG_FMT(SLIPPI_ONLINE, "SLIPPI ONLINE: Captured savestate for frame {} in: %f ms", frame,
|
||||
// INFO_LOG_FMT(SLIPPI_ONLINE, "SLIPPI ONLINE: Captured savestate for frame {} in: {} ms", frame,
|
||||
// ((double)timeDiff) / 1000);
|
||||
}
|
||||
|
||||
|
@ -1745,7 +1746,7 @@ void CEXISlippi::startFindMatch(u8* payload)
|
|||
search.connectCode = shiftJisCode;
|
||||
|
||||
// Store this search so we know what was queued for
|
||||
lastSearch = search;
|
||||
last_search = search;
|
||||
|
||||
// While we do have another condition that checks characters after being connected, it's nice to
|
||||
// give someone an early error before they even queue so that they wont enter the queue and make
|
||||
|
@ -1912,13 +1913,13 @@ void CEXISlippi::handleNameEntryLoad(u8* payload)
|
|||
appendWordToBuffer(&m_read_queue, curIndex);
|
||||
}
|
||||
|
||||
// teamId 0 = red, 1 = blue, 2 = green
|
||||
int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
||||
// team_id 0 = red, 1 = blue, 2 = green
|
||||
int CEXISlippi::getCharColor(u8 char_id, u8 team_id)
|
||||
{
|
||||
switch (charId)
|
||||
switch (char_id)
|
||||
{
|
||||
case 0x0: // Falcon
|
||||
switch (teamId)
|
||||
switch (team_id)
|
||||
{
|
||||
case 0:
|
||||
return 2;
|
||||
|
@ -1928,7 +1929,7 @@ int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
|||
return 4;
|
||||
}
|
||||
case 0x2: // Fox
|
||||
switch (teamId)
|
||||
switch (team_id)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
|
@ -1938,7 +1939,7 @@ int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
|||
return 3;
|
||||
}
|
||||
case 0xC: // Peach
|
||||
switch (teamId)
|
||||
switch (team_id)
|
||||
{
|
||||
case 0:
|
||||
return 0;
|
||||
|
@ -1948,7 +1949,7 @@ int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
|||
return 4;
|
||||
}
|
||||
case 0x13: // Sheik
|
||||
switch (teamId)
|
||||
switch (team_id)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
|
@ -1958,7 +1959,7 @@ int CEXISlippi::getCharColor(u8 charId, u8 teamId)
|
|||
return 3;
|
||||
}
|
||||
case 0x14: // Falco
|
||||
switch (teamId)
|
||||
switch (team_id)
|
||||
{
|
||||
case 0:
|
||||
return 1;
|
||||
|
@ -2037,8 +2038,8 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
// gets re-created when a connection is terminated, that said, it can still be useful to know
|
||||
// who we were connected to after they disconnect from us, for example in the case of
|
||||
// reporting a match. So let's copy the results.
|
||||
recentMmResult = matchmaking->GetMatchmakeResult();
|
||||
allowedStages = recentMmResult.stages;
|
||||
recent_mm_result = matchmaking->GetMatchmakeResult();
|
||||
allowedStages = recent_mm_result.stages;
|
||||
// Clear stage pool so that when we call getRandomStage it will use full list
|
||||
stagePool.clear();
|
||||
localSelections.stageId = getRandomStage();
|
||||
|
@ -2093,7 +2094,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
// Here we are connected, check to see if we should init play session
|
||||
if (!is_play_session_active)
|
||||
{
|
||||
game_reporter->StartNewSession();
|
||||
slprs_exi_device_start_new_reporter_session(slprs_exi_device_ptr);
|
||||
is_play_session_active = true;
|
||||
}
|
||||
}
|
||||
|
@ -2194,7 +2195,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
rps[i].isCharacterSelected = true;
|
||||
}
|
||||
|
||||
remotePlayerCount = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS ? 3 : 1;
|
||||
remotePlayerCount = last_search.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS ? 3 : 1;
|
||||
|
||||
oppName = std::string("Player");
|
||||
#endif
|
||||
|
@ -2245,7 +2246,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
break;
|
||||
}
|
||||
|
||||
if (SlippiMatchmaking::IsFixedRulesMode(lastSearch.mode))
|
||||
if (SlippiMatchmaking::IsFixedRulesMode(last_search.mode))
|
||||
{
|
||||
// If we enter one of these conditions, someone is doing something bad, clear the lobby
|
||||
if (!localCharOk)
|
||||
|
@ -2270,7 +2271,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||
else if (last_search.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||
{
|
||||
auto isMEX = SConfig::GetSlippiConfig().melee_version == Melee::Version::MEX;
|
||||
|
||||
|
@ -2363,10 +2364,10 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
*stage = Common::swap16(stageId);
|
||||
|
||||
// Turn pause off in unranked/ranked, on in other modes
|
||||
auto pauseAllowed = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::DIRECT;
|
||||
u8* gameBitField3 = static_cast<u8*>(&onlineMatchBlock[2]);
|
||||
*gameBitField3 = pauseAllowed ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
|
||||
//*gameBitField3 = *gameBitField3 | 0x8;
|
||||
auto pause_allowed = last_search.mode == SlippiMatchmaking::OnlinePlayMode::DIRECT;
|
||||
u8* game_bit_field3 = static_cast<u8*>(&onlineMatchBlock[2]);
|
||||
*game_bit_field3 = pause_allowed ? *game_bit_field3 & 0xF7 : *game_bit_field3 | 0x8;
|
||||
//*game_bit_field3 = *game_bit_field3 | 0x8;
|
||||
|
||||
// Group players into left/right side for team splash screen display
|
||||
for (int i = 0; i < 4; i++)
|
||||
|
@ -2457,10 +2458,10 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
std::string defaultConnectCodes[] = {"PLYR#001", "PLYR#002", "PLYR#003", "PLYR#004"};
|
||||
#endif
|
||||
|
||||
auto playerInfo = matchmaking->GetPlayerInfo();
|
||||
auto player_info = matchmaking->GetPlayerInfo();
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
std::string connectCode = i < playerInfo.size() ? playerInfo[i].connect_code : "";
|
||||
std::string connectCode = i < player_info.size() ? player_info[i].connect_code : "";
|
||||
#ifdef LOCAL_TESTING
|
||||
connectCode = defaultConnectCodes[i];
|
||||
#endif
|
||||
|
@ -2475,7 +2476,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
std::string uid = i < playerInfo.size() ? playerInfo[i].uid :
|
||||
std::string uid = i < player_info.size() ? player_info[i].uid :
|
||||
""; // UIDs are 28 characters + 1 null terminator
|
||||
#ifdef LOCAL_TESTING
|
||||
uid = defaultUids[i];
|
||||
|
@ -2493,9 +2494,9 @@ void CEXISlippi::prepareOnlineMatchState()
|
|||
m_read_queue.insert(m_read_queue.end(), onlineMatchBlock.begin(), onlineMatchBlock.end());
|
||||
|
||||
// Add match id to output
|
||||
std::string matchId = recentMmResult.id;
|
||||
matchId.resize(51);
|
||||
m_read_queue.insert(m_read_queue.end(), matchId.begin(), matchId.end());
|
||||
std::string match_id = recent_mm_result.id;
|
||||
match_id.resize(51);
|
||||
m_read_queue.insert(m_read_queue.end(), match_id.begin(), match_id.end());
|
||||
}
|
||||
|
||||
u16 CEXISlippi::getRandomStage()
|
||||
|
@ -2527,7 +2528,7 @@ void CEXISlippi::setMatchSelections(u8* payload)
|
|||
|
||||
s.stageId = Common::swap16(&payload[4]);
|
||||
u8 stageSelectOption = payload[6];
|
||||
// u8 onlineMode = payload[7];
|
||||
// u8 online_mode = payload[7];
|
||||
|
||||
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
|
||||
if (stageSelectOption == 3)
|
||||
|
@ -2675,7 +2676,7 @@ bool CEXISlippi::isSlippiChatEnabled()
|
|||
{
|
||||
auto chatEnabledChoice = Config::Get(Config::SLIPPI_ENABLE_QUICK_CHAT);
|
||||
bool res = true;
|
||||
switch (lastSearch.mode)
|
||||
switch (last_search.mode)
|
||||
{
|
||||
case SlippiMatchmaking::DIRECT:
|
||||
res = chatEnabledChoice == Slippi::Chat::ON || chatEnabledChoice == Slippi::Chat::DIRECT_ONLY;
|
||||
|
@ -2829,49 +2830,75 @@ void CEXISlippi::prepareNewSeed()
|
|||
|
||||
void CEXISlippi::handleReportGame(const SlippiExiTypes::ReportGameQuery& query)
|
||||
{
|
||||
SlippiGameReporter::GameReport r;
|
||||
r.match_id = recentMmResult.id;
|
||||
r.mode = static_cast<SlippiMatchmaking::OnlinePlayMode>(query.mode);
|
||||
r.duration_frames = query.frame_length;
|
||||
r.game_index = query.game_index;
|
||||
r.tiebreak_index = query.tiebreak_index;
|
||||
r.winner_idx = query.winner_idx;
|
||||
r.stage_id = Common::FromBigEndian(*(u16*)&query.game_info_block[0xE]);
|
||||
r.game_end_method = query.game_end_method;
|
||||
r.lras_initiator = query.lras_initiator;
|
||||
std::string match_id = recent_mm_result.id;
|
||||
SlippiMatchmakingOnlinePlayMode online_mode = static_cast<SlippiMatchmakingOnlinePlayMode>(query.mode);
|
||||
u32 duration_frames = query.frame_length;
|
||||
u32 game_index = query.game_index;
|
||||
u32 tiebreak_index = query.tiebreak_index;
|
||||
s8 winner_idx = query.winner_idx;
|
||||
int stage_id = Common::FromBigEndian(*(u16 *)&query.game_info_block[0xE]);
|
||||
u8 game_end_method = query.game_end_method;
|
||||
s8 lras_initiator = query.lras_initiator;
|
||||
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"Mode: {} / {}, Frames: {}, GameIdx: {}, TiebreakIdx: {}, WinnerIdx: {}, "
|
||||
"StageId: {}, GameEndMethod: {}, LRASInitiator: {}",
|
||||
static_cast<u8>(r.mode), query.mode, r.duration_frames, r.game_index,
|
||||
r.tiebreak_index, r.winner_idx, r.stage_id, r.game_end_method, r.lras_initiator);
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"Mode: {} / {}, Frames: {}, GameIdx: {}, TiebreakIdx: {}, WinnerIdx: {}, StageId: {}, GameEndMethod: {}, "
|
||||
"LRASInitiator: {}",
|
||||
static_cast<u8>(online_mode), query.mode, duration_frames, game_index, tiebreak_index, winner_idx, stage_id, game_end_method,
|
||||
lras_initiator);
|
||||
|
||||
auto mm_players = recentMmResult.players;
|
||||
auto user_info = user->GetUserInfo();
|
||||
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
SlippiGameReporter::PlayerReport p;
|
||||
p.uid = mm_players.size() > i ? mm_players[i].uid : "";
|
||||
p.slot_type = query.players[i].slot_type;
|
||||
p.stocks_remaining = query.players[i].stocks_remaining;
|
||||
p.damage_done = query.players[i].damage_done;
|
||||
p.char_id = query.game_info_block[0x60 + 0x24 * i];
|
||||
p.color_id = query.game_info_block[0x63 + 0x24 * i];
|
||||
p.starting_stocks = query.game_info_block[0x62 + 0x24 * i];
|
||||
p.starting_percent = Common::FromBigEndian(*(u16*)&query.game_info_block[0x70 + 0x24 * i]);
|
||||
// We pass `uid` and `playKey` here until the User side of things is
|
||||
// ported to Rust.
|
||||
uintptr_t game_report = slprs_game_report_create(user_info.uid.c_str(), user_info.play_key.c_str(), online_mode,
|
||||
match_id.c_str(), duration_frames, game_index, tiebreak_index,
|
||||
winner_idx, game_end_method, lras_initiator, stage_id);
|
||||
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"UID: {}, Port Type: {}, Stocks: {}, DamageDone: {}, CharId: {}, ColorId: {}, "
|
||||
"StartStocks: {}, "
|
||||
"StartPercent: {}",
|
||||
p.uid, p.slot_type, p.stocks_remaining, p.damage_done, p.char_id, p.color_id,
|
||||
p.starting_stocks, p.starting_percent);
|
||||
auto mm_players = recent_mm_result.players;
|
||||
|
||||
r.players.push_back(p);
|
||||
}
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
std::string uid = mm_players.size() > i ? mm_players[i].uid : "";
|
||||
u8 slot_type = query.players[i].slot_type;
|
||||
u8 stocks_remaining = query.players[i].stocks_remaining;
|
||||
float damage_done = query.players[i].damage_done;
|
||||
u8 char_id = query.game_info_block[0x60 + 0x24 * i];
|
||||
u8 color_id = query.game_info_block[0x63 + 0x24 * i];
|
||||
int starting_stocks = query.game_info_block[0x62 + 0x24 * i];
|
||||
int starting_percent = Common::FromBigEndian(*(u16 *)&query.game_info_block[0x70 + 0x24 * i]);
|
||||
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"UID: {}, Port Type: {}, Stocks: {}, DamageDone: {}, CharId: {}, ColorId: {}, StartStocks: {}, "
|
||||
"StartPercent: {}",
|
||||
uid.c_str(), slot_type, stocks_remaining, damage_done, char_id, color_id, starting_stocks, starting_percent);
|
||||
|
||||
uintptr_t player_report = slprs_player_report_create(uid.c_str(), slot_type, damage_done, stocks_remaining, char_id,
|
||||
color_id, starting_stocks, starting_percent);
|
||||
|
||||
slprs_game_report_add_player_report(game_report, player_report);
|
||||
}
|
||||
|
||||
// If ranked mode and the game ended with a quit out, this is either a desync or an interrupted game,
|
||||
// attempt to send synced values to opponents in order to restart the match where it was left off
|
||||
if (online_mode == SlippiMatchmakingOnlinePlayMode::Ranked && game_end_method == 7)
|
||||
{
|
||||
SlippiSyncedGameState s;
|
||||
s.match_id = match_id;
|
||||
s.game_index = game_index;
|
||||
s.tiebreak_index = tiebreak_index;
|
||||
s.seconds_remaining = query.synced_timer;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
s.fighters[i].stocks_remaining = query.players[i].synced_stocks_remaining;
|
||||
s.fighters[i].current_health = query.players[i].synced_current_health;
|
||||
}
|
||||
|
||||
if (slippi_netplay)
|
||||
slippi_netplay->SendSyncedGameState(s);
|
||||
}
|
||||
|
||||
#ifndef LOCAL_TESTING
|
||||
game_reporter->StartReport(r);
|
||||
slprs_exi_device_log_game_report(slprs_exi_device_ptr, game_report);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -2971,11 +2998,15 @@ void CEXISlippi::handleCompleteSet(const SlippiExiTypes::ReportSetCompletionQuer
|
|||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "Hello");
|
||||
|
||||
auto lastMatchId = recentMmResult.id;
|
||||
if (lastMatchId.find("mode.ranked") != std::string::npos)
|
||||
auto last_match_id = recent_mm_result.id;
|
||||
if (last_match_id.find("mode.ranked") != std::string::npos)
|
||||
{
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "Reporting set completion: {}", lastMatchId);
|
||||
game_reporter->ReportCompletion(lastMatchId, query.endMode);
|
||||
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(),
|
||||
query.end_mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2985,29 +3016,29 @@ void CEXISlippi::handleGetPlayerSettings()
|
|||
|
||||
SlippiExiTypes::GetPlayerSettingsResponse resp = {};
|
||||
|
||||
std::vector<std::vector<std::string>> messagesByPlayer = {
|
||||
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
|
||||
auto userChatMessages = user->GetUserInfo().chat_messages;
|
||||
if (userChatMessages.size() == 16)
|
||||
auto user_chat_messages = user->GetUserInfo().chat_messages;
|
||||
if (user_chat_messages.size() == 16)
|
||||
{
|
||||
messagesByPlayer[0] = userChatMessages;
|
||||
messages_by_player[0] = user_chat_messages;
|
||||
}
|
||||
|
||||
// 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)
|
||||
auto player_info = matchmaking->GetPlayerInfo();
|
||||
for (auto& player : player_info)
|
||||
{
|
||||
messagesByPlayer[player.port - 1] = player.chat_messages;
|
||||
messages_by_player[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);
|
||||
auto str = ConvertStringForGame(messages_by_player[i][j], MAX_MESSAGE_LENGTH);
|
||||
sprintf(resp.settings[i].chatMessages[j], "%s", str.c_str());
|
||||
}
|
||||
}
|
||||
|
@ -3039,15 +3070,16 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
u8 byte = memPtr[0];
|
||||
if (byte == CMD_RECEIVE_COMMANDS)
|
||||
{
|
||||
time(&gameStartTime); // Store game start time
|
||||
u8 receiveCommandsLen = memPtr[1];
|
||||
configureCommands(&memPtr[1], receiveCommandsLen);
|
||||
writeToFileAsync(&memPtr[0], receiveCommandsLen + 1, "create");
|
||||
bufLoc += receiveCommandsLen + 1;
|
||||
time(&game_start_time); // Store game start time
|
||||
u8 receive_commands_len = memPtr[1];
|
||||
configureCommands(&memPtr[1], receive_commands_len);
|
||||
writeToFileAsync(&memPtr[0], receive_commands_len + 1, "create");
|
||||
bufLoc += receive_commands_len + 1;
|
||||
g_needInputForFrame = true;
|
||||
SlippiSpectateServer::getInstance().startGame();
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[0], receiveCommandsLen + 1);
|
||||
game_reporter->PushReplayData(&memPtr[0], receiveCommandsLen + 1, "create");
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[0], receive_commands_len + 1);
|
||||
slprs_exi_device_reporter_push_replay_data(slprs_exi_device_ptr, &memPtr[0],
|
||||
receive_commands_len + 1);
|
||||
}
|
||||
|
||||
if (byte == CMD_MENU_FRAME)
|
||||
|
@ -3081,7 +3113,8 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "close");
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
||||
SlippiSpectateServer::getInstance().endGame();
|
||||
game_reporter->PushReplayData(&memPtr[bufLoc], payloadLen + 1, "close");
|
||||
slprs_exi_device_reporter_push_replay_data(slprs_exi_device_ptr, &memPtr[bufLoc],
|
||||
payloadLen + 1);
|
||||
break;
|
||||
case CMD_PREPARE_REPLAY:
|
||||
// log.open("log.txt");
|
||||
|
@ -3094,7 +3127,8 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
g_needInputForFrame = true;
|
||||
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
||||
game_reporter->PushReplayData(&memPtr[bufLoc], payloadLen + 1, "");
|
||||
slprs_exi_device_reporter_push_replay_data(slprs_exi_device_ptr, &memPtr[bufLoc],
|
||||
payloadLen + 1);
|
||||
break;
|
||||
case CMD_IS_STOCK_STEAL:
|
||||
prepareIsStockSteal(&memPtr[bufLoc + 1]);
|
||||
|
@ -3198,7 +3232,8 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
|||
default:
|
||||
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
|
||||
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
||||
game_reporter->PushReplayData(&memPtr[bufLoc], payloadLen + 1, "");
|
||||
slprs_exi_device_reporter_push_replay_data(slprs_exi_device_ptr, &memPtr[bufLoc],
|
||||
payloadLen + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3248,8 +3283,8 @@ void CEXISlippi::ConfigureJukebox()
|
|||
auto& system = Core::System::GetInstance();
|
||||
|
||||
slprs_exi_device_configure_jukebox(slprs_exi_device_ptr,
|
||||
Config::Get(Config::SLIPPI_ENABLE_JUKEBOX), system.GetMemory().GetRAM(),
|
||||
AudioCommonGetCurrentVolume);
|
||||
Config::Get(Config::SLIPPI_ENABLE_JUKEBOX),
|
||||
system.GetMemory().GetRAM(), AudioCommonGetCurrentVolume);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "Core/Slippi/SlippiExiTypes.h"
|
||||
#include "Core/Slippi/SlippiGame.h"
|
||||
#include "Core/Slippi/SlippiGameFileLoader.h"
|
||||
#include "Core/Slippi/SlippiGameReporter.h"
|
||||
#include "Core/Slippi/SlippiMatchmaking.h"
|
||||
#include "Core/Slippi/SlippiNetplay.h"
|
||||
#include "Core/Slippi/SlippiPlayback.h"
|
||||
|
@ -168,7 +167,7 @@ private:
|
|||
u32 writtenByteCount = 0;
|
||||
|
||||
// vars for metadata generation
|
||||
time_t gameStartTime;
|
||||
time_t game_start_time;
|
||||
s32 lastFrame;
|
||||
std::unordered_map<u8, std::unordered_map<u8, u32>> characterUsage;
|
||||
|
||||
|
@ -255,8 +254,8 @@ private:
|
|||
std::vector<u8> m_read_queue;
|
||||
std::unique_ptr<Slippi::SlippiGame> m_current_game = nullptr;
|
||||
SlippiSpectateServer* m_slippiserver = nullptr;
|
||||
SlippiMatchmaking::MatchSearchSettings lastSearch;
|
||||
SlippiMatchmaking::MatchmakeResult recentMmResult;
|
||||
SlippiMatchmaking::MatchSearchSettings last_search;
|
||||
SlippiMatchmaking::MatchmakeResult recent_mm_result;
|
||||
|
||||
std::vector<u16> stagePool;
|
||||
|
||||
|
@ -299,7 +298,6 @@ private:
|
|||
std::unique_ptr<SlippiGameFileLoader> gameFileLoader;
|
||||
std::unique_ptr<SlippiNetplayClient> slippi_netplay;
|
||||
std::unique_ptr<SlippiMatchmaking> matchmaking;
|
||||
std::unique_ptr<SlippiGameReporter> game_reporter;
|
||||
std::unique_ptr<SlippiDirectCodes> directCodes;
|
||||
std::unique_ptr<SlippiDirectCodes> teamsCodes;
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ struct ReportGameQuery
|
|||
struct ReportSetCompletionQuery
|
||||
{
|
||||
u8 command;
|
||||
u8 endMode;
|
||||
u8 end_mode;
|
||||
};
|
||||
|
||||
struct GpCompleteStepQuery
|
||||
|
|
|
@ -1,458 +0,0 @@
|
|||
#include "SlippiGameReporter.h"
|
||||
|
||||
#include "Common/Common.h"
|
||||
#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 "Core/ConfigManager.h"
|
||||
#include "Core/Slippi/SlippiMatchmaking.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
||||
#include <json.hpp>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <zlib.h>
|
||||
using json = nlohmann::json;
|
||||
|
||||
static std::array<u8, 16> s_MD5;
|
||||
static const mbedtls_md_info_t* s_md5_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
|
||||
|
||||
static size_t curl_receive(char* ptr, size_t size, size_t nmemb, void* rcvBuf)
|
||||
{
|
||||
size_t len = size * nmemb;
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Received data: {}", len);
|
||||
|
||||
std::string* buf = (std::string*)rcvBuf;
|
||||
|
||||
buf->insert(buf->end(), ptr, ptr + len);
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t curl_send(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
std::vector<u8>* buf = (std::vector<u8>*)userdata;
|
||||
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Sending data. Size: {}, Nmemb: {}. Buffer length: {}",
|
||||
size, nmemb, buf->size());
|
||||
|
||||
size_t copy_size = size * nmemb;
|
||||
if (copy_size > buf->size())
|
||||
copy_size = buf->size();
|
||||
|
||||
if (copy_size == 0)
|
||||
return 0;
|
||||
|
||||
// This method of reading from a vector seems so jank, im sure there's better ways to do this
|
||||
memcpy(ptr, &buf->at(0), copy_size);
|
||||
buf->erase(buf->begin(), buf->begin() + copy_size);
|
||||
|
||||
return copy_size;
|
||||
}
|
||||
|
||||
SlippiGameReporter::SlippiGameReporter(SlippiUser* user, const std::string current_file_name)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_receive);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000);
|
||||
// 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);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, m_curl_err_buf);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||
#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;
|
||||
}
|
||||
|
||||
CURL* curl_upload = curl_easy_init();
|
||||
if (curl_upload)
|
||||
{
|
||||
curl_easy_setopt(curl_upload, CURLOPT_READFUNCTION, &curl_send);
|
||||
curl_easy_setopt(curl_upload, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(curl_upload, CURLOPT_WRITEFUNCTION, &curl_receive);
|
||||
curl_easy_setopt(curl_upload, CURLOPT_TIMEOUT_MS, 10000);
|
||||
curl_easy_setopt(curl_upload, CURLOPT_ERRORBUFFER, m_curl_upload_err_buf);
|
||||
curl_easy_setopt(curl_upload, CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
// Set up HTTP Headers
|
||||
m_curl_upload_headers =
|
||||
curl_slist_append(m_curl_upload_headers, "Content-Type: application/octet-stream");
|
||||
curl_slist_append(m_curl_upload_headers, "Content-Encoding: gzip");
|
||||
curl_slist_append(m_curl_upload_headers, "X-Goog-Content-Length-Range: 0,10000000");
|
||||
curl_easy_setopt(curl_upload, CURLOPT_HTTPHEADER, m_curl_upload_headers);
|
||||
|
||||
#ifdef _WIN32
|
||||
// ALPN support is enabled by default but requires Windows >= 8.1.
|
||||
curl_easy_setopt(curl_upload, CURLOPT_SSL_ENABLE_ALPN, false);
|
||||
#endif
|
||||
|
||||
m_curl_upload = curl_upload;
|
||||
}
|
||||
|
||||
m_user = user;
|
||||
|
||||
run_thread = true;
|
||||
reporting_thread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
|
||||
|
||||
m_md5_thread = std::thread([this, current_file_name]() {
|
||||
if (!run_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_md_file(s_md5_info, current_file_name.c_str(), s_MD5.data());
|
||||
|
||||
std::string output{};
|
||||
for (u8 n : s_MD5)
|
||||
{
|
||||
output += StringFromFormat("%02x", n);
|
||||
}
|
||||
this->m_iso_hash = output;
|
||||
|
||||
if (!run_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (known_desync_isos.find(this->m_iso_hash) != known_desync_isos.end() &&
|
||||
known_desync_isos.at(this->m_iso_hash))
|
||||
{
|
||||
OSD::AddTypedMessage(OSD::MessageType::DesyncWarning,
|
||||
"\n\n\n\nCAUTION: You are using an ISO that is known to cause desyncs",
|
||||
20000, OSD::Color::RED);
|
||||
}
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "MD5 Hash: {}", this->m_iso_hash);
|
||||
});
|
||||
m_md5_thread.detach();
|
||||
}
|
||||
|
||||
SlippiGameReporter::~SlippiGameReporter()
|
||||
{
|
||||
run_thread = false;
|
||||
cv.notify_one();
|
||||
if (reporting_thread.joinable())
|
||||
reporting_thread.join();
|
||||
|
||||
if (m_md5_thread.joinable())
|
||||
m_md5_thread.join();
|
||||
|
||||
if (m_curl)
|
||||
{
|
||||
curl_slist_free_all(m_curl_header_list);
|
||||
curl_easy_cleanup(m_curl);
|
||||
}
|
||||
|
||||
if (m_curl_upload)
|
||||
{
|
||||
curl_slist_free_all(m_curl_upload_headers);
|
||||
curl_easy_cleanup(m_curl_upload);
|
||||
}
|
||||
}
|
||||
|
||||
void SlippiGameReporter::PushReplayData(u8* data, u32 length, std::string action)
|
||||
{
|
||||
if (action == "create")
|
||||
{
|
||||
m_replay_write_idx += 1;
|
||||
}
|
||||
|
||||
// This makes a vector at this index if it doesn't exist
|
||||
auto& v = m_replay_data[m_replay_write_idx];
|
||||
|
||||
// Insert new data into vector
|
||||
v.insert(v.end(), data, data + length);
|
||||
|
||||
if (action == "close")
|
||||
{
|
||||
m_replay_last_completed_idx = m_replay_write_idx;
|
||||
}
|
||||
}
|
||||
|
||||
void SlippiGameReporter::StartReport(GameReport report)
|
||||
{
|
||||
game_report_queue.emplace(report);
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
void SlippiGameReporter::StartNewSession()
|
||||
{
|
||||
// Maybe we could do stuff here? We used to initialize gameIndex but that isn't required anymore
|
||||
}
|
||||
|
||||
void SlippiGameReporter::ReportThreadHandler()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(mtx);
|
||||
|
||||
while (run_thread)
|
||||
{
|
||||
// Wait for report to come in
|
||||
cv.wait(lck);
|
||||
|
||||
auto queue_has_data = !game_report_queue.empty();
|
||||
|
||||
// Process all messages
|
||||
while (!game_report_queue.empty())
|
||||
{
|
||||
auto& report = game_report_queue.front();
|
||||
report.report_attempts += 1;
|
||||
|
||||
auto is_first_attempt = report.report_attempts == 1;
|
||||
auto is_last_attempt = report.report_attempts >= 5; // Only do five attempts
|
||||
auto error_sleep_ms = is_last_attempt ? 0 : report.report_attempts * 100;
|
||||
|
||||
// If the thread is shutting down, give up after one attempt
|
||||
if (!run_thread && !is_first_attempt)
|
||||
{
|
||||
game_report_queue.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// auto ranked = SlippiMatchmaking::OnlinePlayMode::RANKED;
|
||||
|
||||
auto user_info = m_user->GetUserInfo();
|
||||
WARN_LOG_FMT(SLIPPI_ONLINE, "Checking game report for game {}. Length: {}...",
|
||||
report.game_index, report.duration_frames);
|
||||
|
||||
// Prepare report
|
||||
json request;
|
||||
request["matchId"] = report.match_id;
|
||||
request["uid"] = user_info.uid;
|
||||
request["playKey"] = user_info.play_key;
|
||||
request["mode"] = report.mode;
|
||||
request["gameIndex"] = report.game_index;
|
||||
request["tiebreakIndex"] = report.tiebreak_index;
|
||||
request["gameIndex"] = report.game_index;
|
||||
request["gameDurationFrames"] = report.duration_frames;
|
||||
request["winnerIdx"] = report.winner_idx;
|
||||
request["gameEndMethod"] = report.game_end_method;
|
||||
request["lrasInitiator"] = report.lras_initiator;
|
||||
request["stageId"] = report.stage_id;
|
||||
request["isoHash"] = m_iso_hash;
|
||||
|
||||
json players = json::array();
|
||||
for (int i = 0; i < report.players.size(); i++)
|
||||
{
|
||||
json p;
|
||||
p["uid"] = report.players[i].uid;
|
||||
p["slotType"] = report.players[i].slot_type;
|
||||
p["damageDone"] = report.players[i].damage_done;
|
||||
p["stocksRemaining"] = report.players[i].stocks_remaining;
|
||||
p["characterId"] = report.players[i].char_id;
|
||||
p["colorId"] = report.players[i].color_id;
|
||||
p["startingStocks"] = report.players[i].starting_stocks;
|
||||
p["startingPercent"] = report.players[i].starting_percent;
|
||||
|
||||
players[i] = p;
|
||||
}
|
||||
|
||||
request["players"] = players;
|
||||
// Just pop before request if this is the last attempt
|
||||
if (is_last_attempt)
|
||||
{
|
||||
game_report_queue.pop();
|
||||
}
|
||||
|
||||
auto request_string = request.dump();
|
||||
|
||||
// Send report
|
||||
std::string resp;
|
||||
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, REPORT_URL.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, request_string.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, request_string.length());
|
||||
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &resp);
|
||||
CURLcode res = curl_easy_perform(m_curl);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Got error executing request. Err code : {}",
|
||||
static_cast<u8>(res));
|
||||
Common::SleepCurrentThread(error_sleep_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
long response_code;
|
||||
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
if (response_code != 200)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Server responded with non-success status: {}",
|
||||
response_code);
|
||||
Common::SleepCurrentThread(error_sleep_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if response is valid json
|
||||
if (!json::accept(resp))
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI, "[GameReport] Server responded with invalid json: {}", resp);
|
||||
Common::SleepCurrentThread(error_sleep_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
auto r = json::parse(resp);
|
||||
if (!r.is_object())
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI, "JSON was not an object. {}", resp);
|
||||
Common::SleepCurrentThread(error_sleep_ms);
|
||||
continue;
|
||||
}
|
||||
bool success = r.value("success", false);
|
||||
if (!success)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Report reached server but failed. {}",
|
||||
resp.c_str());
|
||||
Common::SleepCurrentThread(error_sleep_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this was not the last attempt, pop if we are successful. On the last attempt pop will
|
||||
// already have happened
|
||||
if (!is_last_attempt)
|
||||
{
|
||||
game_report_queue.pop();
|
||||
}
|
||||
|
||||
std::string upload_url = r.value("uploadUrl", "");
|
||||
UploadReplay(m_replay_last_completed_idx, upload_url);
|
||||
|
||||
Common::SleepCurrentThread(0);
|
||||
}
|
||||
|
||||
// Clean up replay data for games that are complete
|
||||
if (queue_has_data)
|
||||
{
|
||||
auto firstIdx = m_replay_data.begin()->first;
|
||||
for (int i = firstIdx; i < m_replay_last_completed_idx; i++)
|
||||
{
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "Cleaning index {} in replay data.", i);
|
||||
m_replay_data[i].clear();
|
||||
m_replay_data.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlippiGameReporter::ReportAbandonment(std::string match_id)
|
||||
{
|
||||
auto userInfo = m_user->GetUserInfo();
|
||||
|
||||
// Prepare report
|
||||
json request;
|
||||
request["matchId"] = match_id;
|
||||
request["uid"] = userInfo.uid;
|
||||
request["playKey"] = userInfo.play_key;
|
||||
|
||||
auto request_string = request.dump();
|
||||
|
||||
// Send report
|
||||
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, ABANDON_URL.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, request_string.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, request_string.length());
|
||||
CURLcode res = curl_easy_perform(m_curl);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"[GameReport] Got error executing abandonment request. Err code: {}",
|
||||
static_cast<u8>(res));
|
||||
}
|
||||
}
|
||||
|
||||
void SlippiGameReporter::ReportCompletion(std::string matchId, u8 endMode)
|
||||
{
|
||||
auto userInfo = m_user->GetUserInfo();
|
||||
|
||||
// Prepare report
|
||||
json request;
|
||||
request["matchId"] = matchId;
|
||||
request["uid"] = userInfo.uid;
|
||||
request["playKey"] = userInfo.play_key;
|
||||
request["endMode"] = endMode;
|
||||
|
||||
auto request_string = request.dump();
|
||||
|
||||
// Send report
|
||||
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
||||
curl_easy_setopt(m_curl, CURLOPT_URL, COMPLETE_URL.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, request_string.c_str());
|
||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, request_string.length());
|
||||
CURLcode res = curl_easy_perform(m_curl);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE,
|
||||
"[GameReport] Got error executing completion request. Err code: {}. Msg: {}",
|
||||
static_cast<u8>(res), m_curl_err_buf);
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/57699371/1249024
|
||||
int compressToGzip(const char* input, size_t inputSize, char* output, size_t outputSize)
|
||||
{
|
||||
z_stream zs;
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
zs.avail_in = (uInt)inputSize;
|
||||
zs.next_in = (Bytef*)input;
|
||||
zs.avail_out = (uInt)outputSize;
|
||||
zs.next_out = (Bytef*)output;
|
||||
|
||||
// hard to believe they don't have a macro for gzip encoding, "Add 16" is the best thing zlib can
|
||||
// do: "Add 16 to windowBits to write a simple gzip header and trailer around the compressed data
|
||||
// instead of a zlib wrapper"
|
||||
deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
|
||||
deflate(&zs, Z_FINISH);
|
||||
deflateEnd(&zs);
|
||||
return zs.total_out;
|
||||
}
|
||||
|
||||
void SlippiGameReporter::UploadReplay(int idx, std::string url)
|
||||
{
|
||||
if (url.length() <= 0)
|
||||
return;
|
||||
|
||||
// INFO_LOG(SLIPPI_ONLINE, "Uploading replay: {}, {}", idx, url.c_str());
|
||||
|
||||
auto replay_data = m_replay_data[idx];
|
||||
u32 raw_data_size = static_cast<u32>(replay_data.size());
|
||||
u8* rdbs = reinterpret_cast<u8*>(&raw_data_size);
|
||||
|
||||
// Add header and footer to replay file
|
||||
std::vector<u8> header(
|
||||
{'{', 'U', 3, 'r', 'a', 'w', '[', '$', 'U', '#', 'l', rdbs[3], rdbs[2], rdbs[1], rdbs[0]});
|
||||
replay_data.insert(replay_data.begin(), header.begin(), header.end());
|
||||
std::vector<u8> footer({'U', 8, 'm', 'e', 't', 'a', 'd', 'a', 't', 'a', '{', '}', '}'});
|
||||
replay_data.insert(replay_data.end(), footer.begin(), footer.end());
|
||||
|
||||
std::vector<u8> gzipped_data;
|
||||
gzipped_data.resize(replay_data.size());
|
||||
auto res_size = compressToGzip(reinterpret_cast<char*>(&replay_data[0]), replay_data.size(),
|
||||
reinterpret_cast<char*>(&gzipped_data[0]), gzipped_data.size());
|
||||
gzipped_data.resize(res_size);
|
||||
|
||||
INFO_LOG_FMT(SLIPPI_ONLINE, "Pre-compression size: {}. Post compression size: {}",
|
||||
replay_data.size(), res_size);
|
||||
|
||||
curl_easy_setopt(m_curl_upload, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(m_curl_upload, CURLOPT_READDATA, &gzipped_data);
|
||||
curl_easy_setopt(m_curl_upload, CURLOPT_INFILESIZE, res_size);
|
||||
CURLcode res = curl_easy_perform(m_curl_upload);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SLIPPI_ONLINE, "[GameReport] Got error uploading replay file. Err code: {}",
|
||||
static_cast<int>(res));
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable> // std::condition_variable
|
||||
#include <curl/curl.h>
|
||||
#include <map>
|
||||
#include <mutex> // std::mutex, std::unique_lock
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Slippi/SlippiMatchmaking.h"
|
||||
#include "Core/Slippi/SlippiUser.h"
|
||||
|
||||
class SlippiGameReporter
|
||||
{
|
||||
public:
|
||||
struct PlayerReport
|
||||
{
|
||||
std::string uid;
|
||||
u8 slot_type;
|
||||
float damage_done;
|
||||
u8 stocks_remaining;
|
||||
u8 char_id;
|
||||
u8 color_id;
|
||||
int starting_stocks;
|
||||
int starting_percent;
|
||||
};
|
||||
|
||||
struct GameReport
|
||||
{
|
||||
SlippiMatchmaking::OnlinePlayMode mode = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
|
||||
std::string match_id;
|
||||
int report_attempts = 0;
|
||||
u32 duration_frames = 0;
|
||||
u32 game_index = 0;
|
||||
u32 tiebreak_index = 0;
|
||||
s8 winner_idx = 0;
|
||||
u8 game_end_method = 0;
|
||||
s8 lras_initiator = 0;
|
||||
int stage_id = 0;
|
||||
std::vector<PlayerReport> players;
|
||||
};
|
||||
|
||||
SlippiGameReporter(SlippiUser* user, const std::string current_file_name);
|
||||
~SlippiGameReporter();
|
||||
|
||||
void StartReport(GameReport report);
|
||||
void ReportAbandonment(std::string match_id);
|
||||
void ReportCompletion(std::string matchId, u8 endMode);
|
||||
void StartNewSession();
|
||||
void ReportThreadHandler();
|
||||
void PushReplayData(u8* data, u32 length, std::string action);
|
||||
void UploadReplay(int idx, std::string url);
|
||||
|
||||
protected:
|
||||
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
|
||||
const std::string ABANDON_URL = "https://rankings-dot-slippi.uc.r.appspot.com/abandon";
|
||||
const std::string COMPLETE_URL = "https://rankings-dot-slippi.uc.r.appspot.com/complete";
|
||||
CURL* m_curl = nullptr;
|
||||
struct curl_slist* m_curl_header_list = nullptr;
|
||||
|
||||
CURL* m_curl_upload = nullptr;
|
||||
struct curl_slist* m_curl_upload_headers = nullptr;
|
||||
|
||||
char m_curl_err_buf[CURL_ERROR_SIZE];
|
||||
char m_curl_upload_err_buf[CURL_ERROR_SIZE];
|
||||
|
||||
std::unordered_map<std::string, bool> known_desync_isos = {
|
||||
{"23d6baef06bd65989585096915da20f2", true},
|
||||
{"27a5668769a54cd3515af47b8d9982f3", true},
|
||||
{"5805fa9f1407aedc8804d0472346fc5f", true},
|
||||
{"9bb3e275e77bb1a160276f2330f93931", true},
|
||||
};
|
||||
|
||||
SlippiUser* m_user;
|
||||
std::string m_iso_hash;
|
||||
std::queue<GameReport> game_report_queue;
|
||||
std::thread reporting_thread;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> run_thread;
|
||||
std::thread m_md5_thread;
|
||||
|
||||
std::map<int, std::vector<u8>> m_replay_data;
|
||||
int m_replay_write_idx = 0;
|
||||
int m_replay_last_completed_idx = -1;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue