This commit is contained in:
Nikhil Narayana 2021-12-01 20:30:09 -08:00
commit 2964a86b9b
8 changed files with 715 additions and 695 deletions

View file

@ -115,7 +115,7 @@ CEXISlippi::CEXISlippi()
localSelections.Reset(); localSelections.Reset();
// Forces savestate to re-init regions when a new ISO is loaded // Forces savestate to re-init regions when a new ISO is loaded
SlippiSavestate::shouldForceInit = true; SlippiSavestate::shouldForceInit = true;
// Update user file and then listen for User // Update user file and then listen for User
#ifndef IS_PLAYBACK #ifndef IS_PLAYBACK
@ -448,15 +448,15 @@ void CEXISlippi::writeToFile(std::unique_ptr<WriteMessage> msg)
// Get display names and connection codes from slippi netplay client // Get display names and connection codes from slippi netplay client
if (slippi_netplay) if (slippi_netplay)
{ {
auto playerInfo = matchmaking->GetPlayerInfo(); auto playerInfo = matchmaking->GetPlayerInfo();
for (int i = 0; i < playerInfo.size(); i++) for (int i = 0; i < playerInfo.size(); i++)
{ {
slippi_names[i] = playerInfo[i].display_name; slippi_names[i] = playerInfo[i].display_name;
slippi_connect_codes[i] = playerInfo[i].connect_code; slippi_connect_codes[i] = playerInfo[i].connect_code;
} }
} }
} }
// If no file, do nothing // If no file, do nothing
@ -1516,11 +1516,11 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
} }
if (isDisconnected()) if (isDisconnected())
{ {
auto status = slippi_netplay->GetSlippiConnectStatus(); auto status = slippi_netplay->GetSlippiConnectStatus();
m_read_queue.push_back(3); // Indicate we disconnected m_read_queue.push_back(3); // Indicate we disconnected
return; return;
} }
if (shouldSkipOnlineFrame(frame)) if (shouldSkipOnlineFrame(frame))
{ {
@ -1620,6 +1620,17 @@ void CEXISlippi::handleSendInputs(u8* payload)
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3]; int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
u8 delay = payload[4]; u8 delay = payload[4];
// On the first frame sent, we need to queue up empty dummy pads for as many
// frames as we have delay
if (frame == 1)
{
for (int i = 1; i <= delay; i++)
{
auto empty = std::make_unique<SlippiPad>(i);
slippi_netplay->SendSlippiPad(std::move(empty));
}
}
auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[5]); auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[5]);
slippi_netplay->SendSlippiPad(std::move(pad)); slippi_netplay->SendSlippiPad(std::move(pad));
@ -1641,55 +1652,56 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
m_read_queue.push_back(frameResult); // Indicate a continue frame m_read_queue.push_back(frameResult); // Indicate a continue frame
u8 remotePlayerCount = matchmaking->RemotePlayerCount(); u8 remotePlayerCount = matchmaking->RemotePlayerCount();
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3]; int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX]; std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
int offset[SLIPPI_REMOTE_PLAYER_MAX]; int offset[SLIPPI_REMOTE_PLAYER_MAX];
INFO_LOG(SLIPPI_ONLINE, "Preparing pad data for frame %d", frame); INFO_LOG(SLIPPI_ONLINE, "Preparing pad data for frame %d", frame);
// Get pad data for each remote player and write each of their latest frame nums to the buf // Get pad data for each remote player and write each of their latest frame nums to the buf
for (int i = 0; i < remotePlayerCount; i++) for (int i = 0; i < remotePlayerCount; i++)
{ {
results[i] = slippi_netplay->GetSlippiRemotePad(frame, i); results[i] = slippi_netplay->GetSlippiRemotePad(frame, i);
// determine offset from which to copy data // determine offset from which to copy data
offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE; offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
offset[i] = offset[i] < 0 ? 0 : offset[i]; offset[i] = offset[i] < 0 ? 0 : offset[i];
// add latest frame we are transfering to begining of return buf // add latest frame we are transfering to begining of return buf
int32_t latestFrame = results[i]->latestFrame; int32_t latestFrame = results[i]->latestFrame;
if (latestFrame > frame) if (latestFrame > frame)
latestFrame = frame; latestFrame = frame;
appendWordToBuffer(&m_read_queue, *(u32 *)&latestFrame); appendWordToBuffer(&m_read_queue, *(u32*)&latestFrame);
// INFO_LOG(SLIPPI_ONLINE, "Sending frame num %d for pIdx %d (offset: %d)", latestFrame, i, offset[i]); // INFO_LOG(SLIPPI_ONLINE, "Sending frame num %d for pIdx %d (offset: %d)", latestFrame, i,
} // offset[i]);
// Send the current frame for any unused player slots. }
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++) // Send the current frame for any unused player slots.
{ for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
appendWordToBuffer(&m_read_queue, *(u32 *)&frame); {
} appendWordToBuffer(&m_read_queue, *(u32*)&frame);
}
// copy pad data over // copy pad data over
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++) for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{ {
std::vector<u8> tx; std::vector<u8> tx;
// Get pad data if this remote player exists // Get pad data if this remote player exists
if (i < remotePlayerCount) if (i < remotePlayerCount)
{ {
auto txStart = results[i]->data.begin() + offset[i]; auto txStart = results[i]->data.begin() + offset[i];
auto txEnd = results[i]->data.end(); auto txEnd = results[i]->data.end();
tx.insert(tx.end(), txStart, txEnd); tx.insert(tx.end(), txStart, txEnd);
} }
tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0); tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0);
m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end()); m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end());
} }
slippi_netplay->DropOldRemoteInputs(frame); slippi_netplay->DropOldRemoteInputs(frame);
// ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5], // ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5],
// m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10], // m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10],
@ -1821,28 +1833,31 @@ void CEXISlippi::startFindMatch(u8* payload)
void CEXISlippi::prepareOnlineMatchState() void CEXISlippi::prepareOnlineMatchState()
{ {
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young Link // This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young
// on Battlefield. The proper values will be overwritten // Link on Battlefield. The proper values will be overwritten
static std::vector<u8> onlineMatchBlock = { static std::vector<u8> onlineMatchBlock = {
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00, 0x1F, 0x00, 0x00, 0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF,
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
}; 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09,
0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
};
m_read_queue.clear(); m_read_queue.clear();
@ -1896,21 +1911,21 @@ void CEXISlippi::prepareOnlineMatchState()
remotePlayersReady = true; remotePlayersReady = true;
#else #else
remotePlayersReady = 1; remotePlayersReady = 1;
u8 remotePlayerCount = matchmaking->RemotePlayerCount(); u8 remotePlayerCount = matchmaking->RemotePlayerCount();
for (int i = 0; i < remotePlayerCount; i++) for (int i = 0; i < remotePlayerCount; i++)
{ {
if (!matchInfo->remotePlayerSelections[i].isCharacterSelected) if (!matchInfo->remotePlayerSelections[i].isCharacterSelected)
{ {
remotePlayersReady = 0; remotePlayersReady = 0;
} }
} }
if (remotePlayerCount == 1) if (remotePlayerCount == 1)
{ {
auto isDecider = slippi_netplay->IsDecider(); auto isDecider = slippi_netplay->IsDecider();
localPlayerIndex = isDecider ? 0 : 1; localPlayerIndex = isDecider ? 0 : 1;
remotePlayerIndex = isDecider ? 1 : 0; remotePlayerIndex = isDecider ? 1 : 0;
} }
#endif #endif
auto isDecider = slippi_netplay->IsDecider(); auto isDecider = slippi_netplay->IsDecider();
@ -1922,25 +1937,25 @@ void CEXISlippi::prepareOnlineMatchState()
#ifndef LOCAL_TESTING #ifndef LOCAL_TESTING
// If we get here, our opponent likely disconnected. Let's trigger a clean up // If we get here, our opponent likely disconnected. Let's trigger a clean up
handleConnectionCleanup(); handleConnectionCleanup();
prepareOnlineMatchState(); // run again with new state prepareOnlineMatchState(); // run again with new state
return; return;
#endif #endif
} }
// Here we are connected, check to see if we should init play session // Here we are connected, check to see if we should init play session
if (!is_play_session_active) if (!is_play_session_active)
{ {
std::vector<std::string> uids; std::vector<std::string> uids;
auto mmPlayers = matchmaking->GetPlayerInfo(); auto mmPlayers = matchmaking->GetPlayerInfo();
for (auto mmp : mmPlayers) for (auto mmp : mmPlayers)
{ {
uids.push_back(mmp.uid); uids.push_back(mmp.uid);
} }
game_reporter->StartNewSession(uids); game_reporter->StartNewSession(uids);
is_play_session_active = true; is_play_session_active = true;
} }
} }
else else
{ {
@ -1949,7 +1964,7 @@ void CEXISlippi::prepareOnlineMatchState()
u32 rngOffset = 0; u32 rngOffset = 0;
std::string localPlayerName = ""; std::string localPlayerName = "";
std::string oppName = ""; std::string oppName = "";
std::string p1Name = ""; std::string p1Name = "";
std::string p2Name = ""; std::string p2Name = "";
u8 chatMessageId = 0; u8 chatMessageId = 0;
@ -1957,83 +1972,83 @@ void CEXISlippi::prepareOnlineMatchState()
u8 sentChatMessageId = 0; u8 sentChatMessageId = 0;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
localPlayerIndex = 0; localPlayerIndex = 0;
chatMessageId = localChatMessageId; chatMessageId = localChatMessageId;
chatMessagePlayerIdx = 0; chatMessagePlayerIdx = 0;
localChatMessageId = 0; localChatMessageId = 0;
// in CSS p1 is always current player and p2 is opponent // in CSS p1 is always current player and p2 is opponent
localPlayerName = p1Name = "Player 1"; localPlayerName = p1Name = "Player 1";
oppName = p2Name = "Player 2"; oppName = p2Name = "Player 2";
#endif #endif
m_read_queue.push_back(localPlayerReady); // Local player ready m_read_queue.push_back(localPlayerReady); // Local player ready
m_read_queue.push_back(remotePlayersReady); // Remote players ready m_read_queue.push_back(remotePlayersReady); // Remote players ready
m_read_queue.push_back(localPlayerIndex); // Local player index m_read_queue.push_back(localPlayerIndex); // Local player index
m_read_queue.push_back(remotePlayerIndex); // Remote player index m_read_queue.push_back(remotePlayerIndex); // Remote player index
// Set chat message if any // Set chat message if any
if (slippi_netplay) if (slippi_netplay)
{ {
auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage(); auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage();
chatMessageId = remoteMessageSelection.messageId; chatMessageId = remoteMessageSelection.messageId;
chatMessagePlayerIdx = remoteMessageSelection.playerIdx; chatMessagePlayerIdx = remoteMessageSelection.playerIdx;
sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage(); sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage();
// in CSS p1 is always current player and p2 is opponent // in CSS p1 is always current player and p2 is opponent
localPlayerName = p1Name = userInfo.display_name; localPlayerName = p1Name = userInfo.display_name;
} }
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT; auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
std::vector<u8> leftTeamPlayers = {}; std::vector<u8> leftTeamPlayers = {};
std::vector<u8> rightTeamPlayers = {}; std::vector<u8> rightTeamPlayers = {};
if (localPlayerReady && remotePlayersReady) if (localPlayerReady && remotePlayersReady)
{ {
auto isDecider = slippi_netplay->IsDecider(); auto isDecider = slippi_netplay->IsDecider();
u8 remotePlayerCount = matchmaking->RemotePlayerCount(); u8 remotePlayerCount = matchmaking->RemotePlayerCount();
auto matchInfo = slippi_netplay->GetMatchInfo(); auto matchInfo = slippi_netplay->GetMatchInfo();
SlippiPlayerSelections lps = matchInfo->localPlayerSelections; SlippiPlayerSelections lps = matchInfo->localPlayerSelections;
auto rps = matchInfo->remotePlayerSelections; auto rps = matchInfo->remotePlayerSelections;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
lps.playerIdx = 0; lps.playerIdx = 0;
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++) for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{ {
if (i == 0) if (i == 0)
{ {
rps[i].characterColor = 1; rps[i].characterColor = 1;
rps[i].teamId = 1; rps[i].teamId = 1;
} }
else else
{ {
rps[i].characterColor = 2; rps[i].characterColor = 2;
rps[i].teamId = 2; rps[i].teamId = 2;
} }
rps[i].characterId = 0x2 + i; rps[i].characterId = 0x2 + i;
rps[i].playerIdx = i + 1; rps[i].playerIdx = i + 1;
rps[i].isCharacterSelected = true; rps[i].isCharacterSelected = true;
} }
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS) if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
{ {
remotePlayerCount = 4; remotePlayerCount = 4;
} }
oppName = std::string("Player"); oppName = std::string("Player");
#endif #endif
// Check if someone is picking dumb characters in non-direct // Check if someone is picking dumb characters in non-direct
auto localCharOk = lps.characterId < 26; auto localCharOk = lps.characterId < 26;
auto remoteCharOk = true; auto remoteCharOk = true;
INFO_LOG(SLIPPI_ONLINE, "remotePlayerCount: %d", remotePlayerCount); INFO_LOG(SLIPPI_ONLINE, "remotePlayerCount: %d", remotePlayerCount);
for (int i = 0; i < remotePlayerCount; i++) for (int i = 0; i < remotePlayerCount; i++)
{ {
if (rps[i].characterId >= 26) if (rps[i].characterId >= 26)
remoteCharOk = false; remoteCharOk = false;
} }
if (lastSearch.mode < directMode && (!localCharOk || !remoteCharOk)) if (lastSearch.mode < directMode && (!localCharOk || !remoteCharOk))
{ {
// If we get here, someone is doing something bad, clear the lobby // If we get here, someone is doing something bad, clear the lobby
handleConnectionCleanup(); handleConnectionCleanup();
@ -2045,98 +2060,99 @@ void CEXISlippi::prepareOnlineMatchState()
// Overwrite local player character // Overwrite local player character
onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId; onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId;
onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor; onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor;
onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0; onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId; onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId;
// Overwrite remote player character // Overwrite remote player character
for (int i = 0; i < remotePlayerCount; i++) for (int i = 0; i < remotePlayerCount; i++)
{ {
u8 idx = matchInfo->remotePlayerSelections[i].playerIdx; u8 idx = matchInfo->remotePlayerSelections[i].playerIdx;
onlineMatchBlock[0x60 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterId; onlineMatchBlock[0x60 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterId;
// Set Char Colors // Set Char Colors
onlineMatchBlock[0x63 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterColor; onlineMatchBlock[0x63 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterColor;
// Set Team Ids // Set Team Ids
onlineMatchBlock[0x69 + idx * 0x24] = matchInfo->remotePlayerSelections[i].teamId; onlineMatchBlock[0x69 + idx * 0x24] = matchInfo->remotePlayerSelections[i].teamId;
} }
// Handle Singles/Teams specific logic // Handle Singles/Teams specific logic
if (remotePlayerCount < 3) if (remotePlayerCount < 3)
{ {
onlineMatchBlock[0x8] = 0; // is Teams = false onlineMatchBlock[0x8] = 0; // is Teams = false
// Set p3/p4 player type to none // Set p3/p4 player type to none
onlineMatchBlock[0x61 + 2 * 0x24] = 3; onlineMatchBlock[0x61 + 2 * 0x24] = 3;
onlineMatchBlock[0x61 + 3 * 0x24] = 3; onlineMatchBlock[0x61 + 3 * 0x24] = 3;
// Make one character lighter if same character, same color // Make one character lighter if same character, same color
bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 || bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 ||
lps.characterId == 0x13 && rps[0].characterId == 0x12; lps.characterId == 0x13 && rps[0].characterId == 0x12;
bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda; bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda;
bool colMatch = lps.characterColor == rps[0].characterColor; bool colMatch = lps.characterColor == rps[0].characterColor;
onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0; onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0;
} }
else else
{ {
onlineMatchBlock[0x8] = 1; // is Teams = true onlineMatchBlock[0x8] = 1; // is Teams = true
// Set p3/p4 player type to human // Set p3/p4 player type to human
onlineMatchBlock[0x61 + 2 * 0x24] = 0; onlineMatchBlock[0x61 + 2 * 0x24] = 0;
onlineMatchBlock[0x61 + 3 * 0x24] = 0; onlineMatchBlock[0x61 + 3 * 0x24] = 0;
// Set alt color to light/dark costume for multiples of the same character on a team // Set alt color to light/dark costume for multiples of the same character on a team
int characterCount[26][3] = {0}; int characterCount[26][3] = {0};
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
int charId = onlineMatchBlock[0x60 + i * 0x24]; int charId = onlineMatchBlock[0x60 + i * 0x24];
int teamId = onlineMatchBlock[0x69 + i * 0x24]; int teamId = onlineMatchBlock[0x69 + i * 0x24];
onlineMatchBlock[0x67 + i * 0x24] = characterCount[charId][teamId]; onlineMatchBlock[0x67 + i * 0x24] = characterCount[charId][teamId];
characterCount[charId][teamId]++; characterCount[charId][teamId]++;
} }
} }
// Overwrite stage // Overwrite stage
u16 stageId; u16 stageId;
if (isDecider) if (isDecider)
{ {
stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId; stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId;
} }
else else
{ {
stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId; stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId;
} }
u16 *stage = (u16 *)&onlineMatchBlock[0xE]; u16* stage = (u16*)&onlineMatchBlock[0xE];
*stage = Common::swap16(stageId); *stage = Common::swap16(stageId);
// Set rng offset // Set rng offset
rngOffset = isDecider ? lps.rngOffset : rps[0].rngOffset; rngOffset = isDecider ? lps.rngOffset : rps[0].rngOffset;
WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset); WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset);
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], onlineMatchBlock[0x84]); WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60],
onlineMatchBlock[0x84]);
// Turn pause on in direct, off in everything else // Turn pause on in direct, off in everything else
u8 *gameBitField3 = (u8 *)&onlineMatchBlock[2]; u8* gameBitField3 = (u8*)&onlineMatchBlock[2];
*gameBitField3 = lastSearch.mode >= directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8; *gameBitField3 = lastSearch.mode >= directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
//*gameBitField3 = *gameBitField3 | 0x8; //*gameBitField3 = *gameBitField3 | 0x8;
// Group players into left/right side for team splash screen display // Group players into left/right side for team splash screen display
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
int teamId = onlineMatchBlock[0x69 + i * 0x24]; int teamId = onlineMatchBlock[0x69 + i * 0x24];
if (teamId == lps.teamId) if (teamId == lps.teamId)
leftTeamPlayers.push_back(i); leftTeamPlayers.push_back(i);
else else
rightTeamPlayers.push_back(i); rightTeamPlayers.push_back(i);
} }
int leftTeamSize = leftTeamPlayers.size(); int leftTeamSize = leftTeamPlayers.size();
int rightTeamSize = rightTeamPlayers.size(); int rightTeamSize = rightTeamPlayers.size();
leftTeamPlayers.resize(4, 0); leftTeamPlayers.resize(4, 0);
rightTeamPlayers.resize(4, 0); rightTeamPlayers.resize(4, 0);
leftTeamPlayers[3] = leftTeamSize; leftTeamPlayers[3] = leftTeamSize;
rightTeamPlayers[3] = rightTeamSize; rightTeamPlayers[3] = rightTeamSize;
} }
// Add rng offset to output // Add rng offset to output
@ -2148,52 +2164,52 @@ void CEXISlippi::prepareOnlineMatchState()
// Add chat messages id // Add chat messages id
m_read_queue.push_back((u8)sentChatMessageId); m_read_queue.push_back((u8)sentChatMessageId);
m_read_queue.push_back((u8)chatMessageId); m_read_queue.push_back((u8)chatMessageId);
m_read_queue.push_back((u8)chatMessagePlayerIdx); m_read_queue.push_back((u8)chatMessagePlayerIdx);
// Add player groupings for VS splash screen // Add player groupings for VS splash screen
leftTeamPlayers.resize(4, 0); leftTeamPlayers.resize(4, 0);
rightTeamPlayers.resize(4, 0); rightTeamPlayers.resize(4, 0);
m_read_queue.insert(m_read_queue.end(), leftTeamPlayers.begin(), leftTeamPlayers.end()); m_read_queue.insert(m_read_queue.end(), leftTeamPlayers.begin(), leftTeamPlayers.end());
m_read_queue.insert(m_read_queue.end(), rightTeamPlayers.begin(), rightTeamPlayers.end()); m_read_queue.insert(m_read_queue.end(), rightTeamPlayers.begin(), rightTeamPlayers.end());
// Add names to output // Add names to output
// Always send static local player name // Always send static local player name
localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH); localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end()); m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end());
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
std::string defaultNames[] = {"Player 1", "Player 2", "Player 3", "Player 4"}; std::string defaultNames[] = {"Player 1", "Player 2", "Player 3", "Player 4"};
#endif #endif
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
std::string name = matchmaking->GetPlayerName(i); std::string name = matchmaking->GetPlayerName(i);
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
name = defaultNames[i]; name = defaultNames[i];
#endif #endif
name = ConvertStringForGame(name, MAX_NAME_LENGTH); name = ConvertStringForGame(name, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), name.begin(), name.end()); m_read_queue.insert(m_read_queue.end(), name.begin(), name.end());
} }
// Create the opponent string using the names of all players on opposing teams // Create the opponent string using the names of all players on opposing teams
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24]; int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
std::string oppText = ""; std::string oppText = "";
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
if (i == localPlayerIndex) if (i == localPlayerIndex)
continue; continue;
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx) if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
{ {
if (oppText != "") if (oppText != "")
oppText += "/"; oppText += "/";
oppText += matchmaking->GetPlayerName(i); oppText += matchmaking->GetPlayerName(i);
} }
} }
if (matchmaking->RemotePlayerCount() == 1) if (matchmaking->RemotePlayerCount() == 1)
oppText = matchmaking->GetPlayerName(remotePlayerIndex); oppText = matchmaking->GetPlayerName(remotePlayerIndex);
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1); oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end()); m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
// Add error message if there is one // Add error message if there is one
@ -2237,12 +2253,12 @@ void CEXISlippi::setMatchSelections(u8* payload)
SlippiPlayerSelections s; SlippiPlayerSelections s;
s.teamId = payload[0]; s.teamId = payload[0];
s.characterId = payload[1]; s.characterId = payload[1];
s.characterColor = payload[2]; s.characterColor = payload[2];
s.isCharacterSelected = payload[3]; s.isCharacterSelected = payload[3];
s.stageId = Common::swap16(&payload[4]); s.stageId = Common::swap16(&payload[4]);
u8 stageSelectOption = payload[6]; u8 stageSelectOption = payload[6];
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3; s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
if (stageSelectOption == 3) if (stageSelectOption == 3)
@ -2251,16 +2267,16 @@ void CEXISlippi::setMatchSelections(u8* payload)
s.stageId = getRandomStage(); s.stageId = getRandomStage();
} }
INFO_LOG(SLIPPI, "LPS set char: %d, iSS: %d, %d, stage: %d, team: %d", s.isCharacterSelected, stageSelectOption, INFO_LOG(SLIPPI, "LPS set char: %d, iSS: %d, %d, stage: %d, team: %d", s.isCharacterSelected,
s.isStageSelected, s.stageId, s.teamId); stageSelectOption, s.isStageSelected, s.stageId, s.teamId);
s.rngOffset = generator() % 0xFFFF; s.rngOffset = generator() % 0xFFFF;
if (matchmaking->LocalPlayerIndex() == 1 && firstMatch) if (matchmaking->LocalPlayerIndex() == 1 && firstMatch)
{ {
firstMatch = false; firstMatch = false;
s.stageId = getRandomStage(); s.stageId = getRandomStage();
} }
// Merge these selections // Merge these selections
localSelections.Merge(s); localSelections.Merge(s);
@ -2432,7 +2448,7 @@ void CEXISlippi::handleConnectionCleanup()
forcedError.clear(); forcedError.clear();
// Reset play session // Reset play session
is_play_session_active = false; is_play_session_active = false;
firstMatch = true; firstMatch = true;
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
@ -2451,28 +2467,28 @@ void CEXISlippi::prepareNewSeed()
appendWordToBuffer(&m_read_queue, newSeed); appendWordToBuffer(&m_read_queue, newSeed);
} }
void CEXISlippi::handleReportGame(u8 *payload) void CEXISlippi::handleReportGame(u8* payload)
{ {
SlippiGameReporter::GameReport r; SlippiGameReporter::GameReport r;
r.duration_frames = Common::swap32(&payload[0]); r.duration_frames = Common::swap32(&payload[0]);
//ERROR_LOG(SLIPPI_ONLINE, "Frames: %d", r.duration_frames); // ERROR_LOG(SLIPPI_ONLINE, "Frames: %d", r.duration_frames);
for (auto i = 0; i < 2; ++i) for (auto i = 0; i < 2; ++i)
{ {
SlippiGameReporter::PlayerReport p; SlippiGameReporter::PlayerReport p;
auto offset = i * 6; auto offset = i * 6;
p.stocks_remaining = payload[5 + offset]; p.stocks_remaining = payload[5 + offset];
auto swappedDamageDone = Common::swap32(&payload[6 + offset]); auto swappedDamageDone = Common::swap32(&payload[6 + offset]);
p.damage_done = *(float *)&swappedDamageDone; p.damage_done = *(float*)&swappedDamageDone;
//ERROR_LOG(SLIPPI_ONLINE, "Stocks: %d, DamageDone: %f", p.stocks_remaining, p.damage_done); // ERROR_LOG(SLIPPI_ONLINE, "Stocks: %d, DamageDone: %f", p.stocks_remaining, p.damage_done);
r.players.push_back(p); r.players.push_back(p);
} }
game_reporter->StartReport(r); game_reporter->StartReport(r);
} }
void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize) void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
@ -2602,8 +2618,8 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
prepareNewSeed(); prepareNewSeed();
break; break;
case CMD_REPORT_GAME: case CMD_REPORT_GAME:
handleReportGame(&memPtr[bufLoc + 1]); handleReportGame(&memPtr[bufLoc + 1]);
break; break;
default: default:
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, ""); writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1); SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);

View file

@ -16,123 +16,123 @@
#include <json.hpp> #include <json.hpp>
using json = nlohmann::json; using json = nlohmann::json;
static size_t receive(char *ptr, size_t size, size_t nmemb, void *rcvBuf) static size_t receive(char* ptr, size_t size, size_t nmemb, void* rcvBuf)
{ {
size_t len = size * nmemb; size_t len = size * nmemb;
INFO_LOG(SLIPPI_ONLINE, "[User] Received data: %d", len); INFO_LOG(SLIPPI_ONLINE, "[User] Received data: %d", len);
std::string *buf = (std::string *)rcvBuf; std::string* buf = (std::string*)rcvBuf;
buf->insert(buf->end(), ptr, ptr + len); buf->insert(buf->end(), ptr, ptr + len);
return len; return len;
} }
SlippiGameReporter::SlippiGameReporter(SlippiUser *user) SlippiGameReporter::SlippiGameReporter(SlippiUser* user)
{ {
CURL *curl = curl_easy_init(); CURL* curl = curl_easy_init();
if (curl) if (curl)
{ {
// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive); // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000); curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000);
// Set up HTTP Headers // Set up HTTP Headers
m_curl_header_list = curl_slist_append(m_curl_header_list, "Content-Type: application/json"); 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_HTTPHEADER, m_curl_header_list);
#ifdef _WIN32 #ifdef _WIN32
// ALPN support is enabled by default but requires Windows >= 8.1. // ALPN support is enabled by default but requires Windows >= 8.1.
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false); curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false);
#endif #endif
m_curl = curl; m_curl = curl;
} }
m_user = user; m_user = user;
run_thread = true; run_thread = true;
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this); reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
} }
SlippiGameReporter::~SlippiGameReporter() SlippiGameReporter::~SlippiGameReporter()
{ {
run_thread = false; run_thread = false;
cv.notify_one(); cv.notify_one();
if (reportingThread.joinable()) if (reportingThread.joinable())
reportingThread.join(); reportingThread.join();
if (m_curl) if (m_curl)
{ {
curl_slist_free_all(m_curl_header_list); curl_slist_free_all(m_curl_header_list);
curl_easy_cleanup(m_curl); curl_easy_cleanup(m_curl);
} }
} }
void SlippiGameReporter::StartReport(GameReport report) void SlippiGameReporter::StartReport(GameReport report)
{ {
game_report_queue.emplace(report); game_report_queue.emplace(report);
cv.notify_one(); cv.notify_one();
} }
void SlippiGameReporter::StartNewSession(std::vector<std::string> player_uids) void SlippiGameReporter::StartNewSession(std::vector<std::string> player_uids)
{ {
this->player_uids = player_uids; this->player_uids = player_uids;
gameIndex = 1; gameIndex = 1;
} }
void SlippiGameReporter::ReportThreadHandler() void SlippiGameReporter::ReportThreadHandler()
{ {
std::unique_lock<std::mutex> lck(mtx); std::unique_lock<std::mutex> lck(mtx);
while (run_thread) while (run_thread)
{ {
// Wait for report to come in // Wait for report to come in
cv.wait(lck); cv.wait(lck);
// Process all messages // Process all messages
while (!game_report_queue.empty()) while (!game_report_queue.empty())
{ {
auto report = game_report_queue.front(); auto report = game_report_queue.front();
game_report_queue.pop(); game_report_queue.pop();
auto userInfo = m_user->GetUserInfo(); auto userInfo = m_user->GetUserInfo();
// Prepare report // Prepare report
json request; json request;
request["uid"] = userInfo.uid; request["uid"] = userInfo.uid;
request["playKey"] = userInfo.play_key; request["playKey"] = userInfo.play_key;
request["gameIndex"] = gameIndex; request["gameIndex"] = gameIndex;
request["gameDurationFrames"] = report.duration_frames; request["gameDurationFrames"] = report.duration_frames;
json players = json::array(); json players = json::array();
for (int i = 0; i < report.players.size(); i++) for (int i = 0; i < report.players.size(); i++)
{ {
json p; json p;
p["uid"] = player_uids[i]; p["uid"] = player_uids[i];
p["damage_done"] = report.players[i].damage_done; p["damage_done"] = report.players[i].damage_done;
p["stocks_remaining"] = report.players[i].stocks_remaining; p["stocks_remaining"] = report.players[i].stocks_remaining;
players[i] = p; players[i] = p;
} }
request["players"] = players; request["players"] = players;
auto requestString = request.dump(); auto requestString = request.dump();
// Send report // Send report
curl_easy_setopt(m_curl, CURLOPT_POST, true); 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_URL, REPORT_URL.c_str());
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str());
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length());
CURLcode res = curl_easy_perform(m_curl); CURLcode res = curl_easy_perform(m_curl);
if (res != 0) if (res != 0)
{ {
ERROR_LOG(SLIPPI_ONLINE, "[GameReport] Got error executing request. Err code: %d", res); ERROR_LOG(SLIPPI_ONLINE, "[GameReport] Got error executing request. Err code: %d", res);
} }
gameIndex++; gameIndex++;
Common::SleepCurrentThread(0); Common::SleepCurrentThread(0);
} }
} }
} }

View file

@ -1,49 +1,49 @@
#pragma once #pragma once
#include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiUser.h"
#include <atomic> #include <atomic>
#include <condition_variable> // std::condition_variable #include <condition_variable> // std::condition_variable
#include <curl/curl.h> #include <curl/curl.h>
#include <mutex> // std::mutex, std::unique_lock #include <mutex> // std::mutex, std::unique_lock
#include <queue>
#include <string> #include <string>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <queue> #include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiUser.h"
class SlippiGameReporter class SlippiGameReporter
{ {
public: public:
struct PlayerReport struct PlayerReport
{ {
float damage_done; float damage_done;
u8 stocks_remaining; u8 stocks_remaining;
}; };
struct GameReport struct GameReport
{ {
u32 duration_frames = 0; u32 duration_frames = 0;
std::vector<PlayerReport> players; std::vector<PlayerReport> players;
}; };
SlippiGameReporter(SlippiUser *user); SlippiGameReporter(SlippiUser* user);
~SlippiGameReporter(); ~SlippiGameReporter();
void StartReport(GameReport report); void StartReport(GameReport report);
void StartNewSession(std::vector<std::string> player_uids); void StartNewSession(std::vector<std::string> player_uids);
void ReportThreadHandler(); void ReportThreadHandler();
protected: protected:
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report"; const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
CURL *m_curl = nullptr; CURL* m_curl = nullptr;
struct curl_slist *m_curl_header_list = nullptr; struct curl_slist* m_curl_header_list = nullptr;
u32 gameIndex = 1; u32 gameIndex = 1;
std::vector<std::string> player_uids; std::vector<std::string> player_uids;
SlippiUser *m_user; SlippiUser* m_user;
std::queue<GameReport> game_report_queue; std::queue<GameReport> game_report_queue;
std::thread reportingThread; std::thread reportingThread;
std::mutex mtx; std::mutex mtx;
std::condition_variable cv; std::condition_variable cv;
std::atomic<bool> run_thread; std::atomic<bool> run_thread;
}; };

View file

@ -244,9 +244,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
// Check that the packet actually contains the data it claims to // Check that the packet actually contains the data it claims to
if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize()) if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize())
{ {
ERROR_LOG(SLIPPI_ONLINE, ERROR_LOG_FMT(
"Netplay packet too small to read pad buffer. Size: %d, Inputs: %d, MinSize: %d", SLIPPI_ONLINE,
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE); "Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
break; break;
} }

View file

@ -19,12 +19,12 @@ SlippiPad::SlippiPad(int32_t frame, u8* padBuf) : SlippiPad(frame)
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE); memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
} }
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf) : SlippiPad(frame) SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf) : SlippiPad(frame)
{ {
this->frame = frame; this->frame = frame;
this->playerIdx = playerIdx; this->playerIdx = playerIdx;
// Overwrite the data portion of the pad // Overwrite the data portion of the pad
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE); memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
} }
SlippiPad::~SlippiPad() SlippiPad::~SlippiPad()

View file

@ -10,7 +10,7 @@ class SlippiPad
public: public:
SlippiPad(int32_t frame); SlippiPad(int32_t frame);
SlippiPad(int32_t frame, u8* padBuf); SlippiPad(int32_t frame, u8* padBuf);
SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf); SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf);
~SlippiPad(); ~SlippiPad();
int32_t frame; int32_t frame;

View file

@ -1,9 +1,9 @@
#include "SlippiSpectate.h" #include "SlippiSpectate.h"
#include <Core/ConfigManager.h>
#include "Common/Base64.hpp" #include "Common/Base64.hpp"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/Version.h" #include "Common/Version.h"
#include <Core/ConfigManager.h>
// Networking // Networking
#ifdef _WIN32 #ifdef _WIN32
@ -21,12 +21,13 @@ inline bool isSpectatorEnabled()
SlippiSpectateServer& SlippiSpectateServer::getInstance() SlippiSpectateServer& SlippiSpectateServer::getInstance()
{ {
static SlippiSpectateServer instance; static SlippiSpectateServer instance;
return instance; return instance;
} }
void SlippiSpectateServer::write(u8 *payload, u32 length) void SlippiSpectateServer::write(u8* payload, u32 length)
{ {
if (isSpectatorEnabled()) { if (isSpectatorEnabled())
{
std::string str_payload((char*)payload, length); std::string str_payload((char*)payload, length);
m_event_queue.Push(str_payload); m_event_queue.Push(str_payload);
} }
@ -35,134 +36,135 @@ void SlippiSpectateServer::write(u8 *payload, u32 length)
// CALLED FROM DOLPHIN MAIN THREAD // CALLED FROM DOLPHIN MAIN THREAD
void SlippiSpectateServer::startGame() void SlippiSpectateServer::startGame()
{ {
if (isSpectatorEnabled()) if (isSpectatorEnabled())
{ {
m_event_queue.Push("START_GAME"); m_event_queue.Push("START_GAME");
} }
} }
// CALLED FROM DOLPHIN MAIN THREAD // CALLED FROM DOLPHIN MAIN THREAD
void SlippiSpectateServer::endGame() void SlippiSpectateServer::endGame()
{ {
if (isSpectatorEnabled()) if (isSpectatorEnabled())
{ {
m_event_queue.Push("END_GAME"); m_event_queue.Push("END_GAME");
} }
} }
// CALLED FROM SERVER THREAD // CALLED FROM SERVER THREAD
void SlippiSpectateServer::writeEvents(u16 peer_id) void SlippiSpectateServer::writeEvents(u16 peer_id)
{ {
// Send menu events // Send menu events
if (!m_in_game && (m_sockets[peer_id]->m_menu_cursor != m_menu_cursor)) if (!m_in_game && (m_sockets[peer_id]->m_menu_cursor != m_menu_cursor))
{ {
ENetPacket *packet = enet_packet_create(m_menu_event.data(), m_menu_event.length(), ENET_PACKET_FLAG_RELIABLE); ENetPacket* packet =
// Batch for sending enet_packet_create(m_menu_event.data(), m_menu_event.length(), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet); // Batch for sending
// Record for the peer that it was sent enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
m_sockets[peer_id]->m_menu_cursor = m_menu_cursor; // Record for the peer that it was sent
} m_sockets[peer_id]->m_menu_cursor = m_menu_cursor;
}
// Send game events // Send game events
// Loop through each event that needs to be sent // Loop through each event that needs to be sent
// send all the events starting at their cursor // send all the events starting at their cursor
// If the client's cursor is beyond the end of the event buffer, then // If the client's cursor is beyond the end of the event buffer, then
// it's probably left over from an old game. (Or is invalid anyway) // it's probably left over from an old game. (Or is invalid anyway)
// So reset it back to 0 // So reset it back to 0
if (m_sockets[peer_id]->m_cursor > m_event_buffer.size()) if (m_sockets[peer_id]->m_cursor > m_event_buffer.size())
{ {
m_sockets[peer_id]->m_cursor = 0; m_sockets[peer_id]->m_cursor = 0;
} }
for (u64 i = m_sockets[peer_id]->m_cursor; i < m_event_buffer.size(); i++) for (u64 i = m_sockets[peer_id]->m_cursor; i < m_event_buffer.size(); i++)
{ {
ENetPacket *packet = ENetPacket* packet = enet_packet_create(m_event_buffer[i].data(), m_event_buffer[i].size(),
enet_packet_create(m_event_buffer[i].data(), m_event_buffer[i].size(), ENET_PACKET_FLAG_RELIABLE); ENET_PACKET_FLAG_RELIABLE);
// Batch for sending // Batch for sending
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet); enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
m_sockets[peer_id]->m_cursor++; m_sockets[peer_id]->m_cursor++;
} }
} }
// CALLED FROM SERVER THREAD // CALLED FROM SERVER THREAD
void SlippiSpectateServer::popEvents() void SlippiSpectateServer::popEvents()
{ {
// Loop through the event queue and keep popping off events and handling them // Loop through the event queue and keep popping off events and handling them
while (!m_event_queue.Empty()) while (!m_event_queue.Empty())
{ {
std::string event; std::string event;
m_event_queue.Pop(event); m_event_queue.Pop(event);
// These two are meta-events, used to signify the start/end of a game // These two are meta-events, used to signify the start/end of a game
// They are not sent over the wire // They are not sent over the wire
if (event == "END_GAME") if (event == "END_GAME")
{ {
m_menu_cursor = 0; m_menu_cursor = 0;
if (m_event_buffer.size() > 0) if (m_event_buffer.size() > 0)
{ {
m_cursor_offset += m_event_buffer.size(); m_cursor_offset += m_event_buffer.size();
} }
m_menu_event.clear(); m_menu_event.clear();
m_in_game = false; m_in_game = false;
continue; continue;
} }
if (event == "START_GAME") if (event == "START_GAME")
{ {
m_event_buffer.clear(); m_event_buffer.clear();
m_in_game = true; m_in_game = true;
continue; continue;
} }
// Make json wrapper for game event // Make json wrapper for game event
json game_event; json game_event;
// An SLP event with an empty payload is a quasi-event that signifies // An SLP event with an empty payload is a quasi-event that signifies
// the unclean exit of a game. Send this out as its own event // the unclean exit of a game. Send this out as its own event
// (Since you can't meaningfully concat it with other events) // (Since you can't meaningfully concat it with other events)
if (event.empty()) if (event.empty())
{ {
game_event["payload"] = ""; game_event["payload"] = "";
game_event["type"] = "game_event"; game_event["type"] = "game_event";
m_event_buffer.push_back(game_event.dump()); m_event_buffer.push_back(game_event.dump());
continue; continue;
} }
if (!m_in_game) if (!m_in_game)
{ {
game_event["payload"] = base64::Base64::Encode(event); game_event["payload"] = base64::Base64::Encode(event);
m_menu_cursor += 1; m_menu_cursor += 1;
game_event["type"] = "menu_event"; game_event["type"] = "menu_event";
m_menu_event = game_event.dump(); m_menu_event = game_event.dump();
continue; continue;
} }
u8 command = (u8)event[0]; u8 command = (u8)event[0];
m_event_concat = m_event_concat + event; m_event_concat = m_event_concat + event;
static std::unordered_map<u8, bool> sendEvents = { static std::unordered_map<u8, bool> sendEvents = {
{0x36, true}, // GAME_INIT {0x36, true}, // GAME_INIT
{0x3C, true}, // FRAME_END {0x3C, true}, // FRAME_END
{0x39, true}, // GAME_END {0x39, true}, // GAME_END
{0x10, true}, // SPLIT_MESSAGE {0x10, true}, // SPLIT_MESSAGE
}; };
if (sendEvents.count(command)) if (sendEvents.count(command))
{ {
u32 cursor = (u32)(m_event_buffer.size() + m_cursor_offset); u32 cursor = (u32)(m_event_buffer.size() + m_cursor_offset);
game_event["payload"] = base64::Base64::Encode(m_event_concat); game_event["payload"] = base64::Base64::Encode(m_event_concat);
game_event["type"] = "game_event"; game_event["type"] = "game_event";
game_event["cursor"] = cursor; game_event["cursor"] = cursor;
game_event["next_cursor"] = cursor + 1; game_event["next_cursor"] = cursor + 1;
m_event_buffer.push_back(game_event.dump()); m_event_buffer.push_back(game_event.dump());
m_event_concat = ""; m_event_concat = "";
} }
} }
} }
// CALLED ONCE EVER, DOLPHIN MAIN THREAD // CALLED ONCE EVER, DOLPHIN MAIN THREAD
SlippiSpectateServer::SlippiSpectateServer() SlippiSpectateServer::SlippiSpectateServer()
{ {
if (isSpectatorEnabled()) if (isSpectatorEnabled())
{ {
m_in_game = false; m_in_game = false;
m_menu_cursor = 0; m_menu_cursor = 0;
@ -170,196 +172,197 @@ SlippiSpectateServer::SlippiSpectateServer()
// Spawn thread for socket listener // Spawn thread for socket listener
m_stop_socket_thread = false; m_stop_socket_thread = false;
m_socketThread = std::thread(&SlippiSpectateServer::SlippicommSocketThread, this); m_socketThread = std::thread(&SlippiSpectateServer::SlippicommSocketThread, this);
} }
} }
// CALLED FROM DOLPHIN MAIN THREAD // CALLED FROM DOLPHIN MAIN THREAD
SlippiSpectateServer::~SlippiSpectateServer() SlippiSpectateServer::~SlippiSpectateServer()
{ {
// The socket thread will be blocked waiting for input // The socket thread will be blocked waiting for input
// So to wake it up, let's connect to the socket! // So to wake it up, let's connect to the socket!
m_stop_socket_thread = true; m_stop_socket_thread = true;
if (m_socketThread.joinable()) if (m_socketThread.joinable())
{ {
m_socketThread.join(); m_socketThread.join();
} }
} }
// CALLED FROM SERVER THREAD // CALLED FROM SERVER THREAD
void SlippiSpectateServer::handleMessage(u8 *buffer, u32 length, u16 peer_id) void SlippiSpectateServer::handleMessage(u8* buffer, u32 length, u16 peer_id)
{ {
// Unpack the message // Unpack the message
std::string message((char *)buffer, length); std::string message((char*)buffer, length);
json json_message = json::parse(message); json json_message = json::parse(message);
if (!json_message.is_discarded() && (json_message.find("type") != json_message.end())) if (!json_message.is_discarded() && (json_message.find("type") != json_message.end()))
{ {
// Check what type of message this is // Check what type of message this is
if (!json_message["type"].is_string()) if (!json_message["type"].is_string())
{ {
return; return;
} }
if (json_message["type"] == "connect_request") if (json_message["type"] == "connect_request")
{ {
// Get the requested cursor // Get the requested cursor
if (json_message.find("cursor") == json_message.end()) if (json_message.find("cursor") == json_message.end())
{ {
return; return;
} }
if (!json_message["cursor"].is_number_integer()) if (!json_message["cursor"].is_number_integer())
{ {
return; return;
} }
u32 requested_cursor = json_message["cursor"]; u32 requested_cursor = json_message["cursor"];
u32 sent_cursor = 0; u32 sent_cursor = 0;
// Set the user's cursor position // Set the user's cursor position
if (requested_cursor >= m_cursor_offset) if (requested_cursor >= m_cursor_offset)
{ {
// If the requested cursor is past what events we even have, then just tell them to start over // If the requested cursor is past what events we even have, then just tell them to start
if (requested_cursor > m_event_buffer.size() + m_cursor_offset) // over
{ if (requested_cursor > m_event_buffer.size() + m_cursor_offset)
m_sockets[peer_id]->m_cursor = 0; {
} m_sockets[peer_id]->m_cursor = 0;
// Requested cursor is in the middle of a live match, events that we have }
else // Requested cursor is in the middle of a live match, events that we have
{ else
m_sockets[peer_id]->m_cursor = requested_cursor - m_cursor_offset; {
} m_sockets[peer_id]->m_cursor = requested_cursor - m_cursor_offset;
} }
else }
{ else
// The client requested a cursor that was too low. Bring them up to the present {
m_sockets[peer_id]->m_cursor = 0; // The client requested a cursor that was too low. Bring them up to the present
} m_sockets[peer_id]->m_cursor = 0;
}
sent_cursor = (u32)m_sockets[peer_id]->m_cursor + (u32)m_cursor_offset; sent_cursor = (u32)m_sockets[peer_id]->m_cursor + (u32)m_cursor_offset;
// If someone joins while at the menu, don't catch them up // If someone joins while at the menu, don't catch them up
// set their cursor to the end // set their cursor to the end
if (!m_in_game) if (!m_in_game)
{ {
m_sockets[peer_id]->m_cursor = m_event_buffer.size(); m_sockets[peer_id]->m_cursor = m_event_buffer.size();
} }
json reply; json reply;
reply["type"] = "connect_reply"; reply["type"] = "connect_reply";
reply["nick"] = "Slippi Online"; reply["nick"] = "Slippi Online";
reply["version"] = Common::scm_slippi_semver_str; reply["version"] = Common::scm_slippi_semver_str;
reply["cursor"] = sent_cursor; reply["cursor"] = sent_cursor;
std::string packet_buffer = reply.dump(); std::string packet_buffer = reply.dump();
ENetPacket *packet = ENetPacket* packet = enet_packet_create(packet_buffer.data(), (u32)packet_buffer.length(),
enet_packet_create(packet_buffer.data(), (u32)packet_buffer.length(), ENET_PACKET_FLAG_RELIABLE); ENET_PACKET_FLAG_RELIABLE);
// Batch for sending // Batch for sending
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet); enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
// Put the client in the right in_game state // Put the client in the right in_game state
m_sockets[peer_id]->m_shook_hands = true; m_sockets[peer_id]->m_shook_hands = true;
} }
} }
} }
void SlippiSpectateServer::SlippicommSocketThread(void) void SlippiSpectateServer::SlippicommSocketThread(void)
{ {
if (enet_initialize() != 0) if (enet_initialize() != 0)
{ {
WARN_LOG(SLIPPI, "An error occurred while initializing spectator server."); WARN_LOG(SLIPPI, "An error occurred while initializing spectator server.");
return; return;
} }
ENetAddress server_address = {0}; ENetAddress server_address = {0};
server_address.host = ENET_HOST_ANY; server_address.host = ENET_HOST_ANY;
server_address.port = SConfig::GetInstance().m_spectator_local_port; server_address.port = SConfig::GetInstance().m_spectator_local_port;
// Create the spectator server // Create the spectator server
// This call can fail if the system is already listening on the specified port // This call can fail if the system is already listening on the specified port
// or for some period of time after it closes down. You basically have to just // or for some period of time after it closes down. You basically have to just
// retry until the OS lets go of the port and we can claim it again // retry until the OS lets go of the port and we can claim it again
// This typically only takes a few seconds // This typically only takes a few seconds
ENetHost *server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0); ENetHost* server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
int tries = 0; int tries = 0;
while (server == nullptr && tries < 20) while (server == nullptr && tries < 20)
{ {
server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0); server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
tries += 1; tries += 1;
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); std::this_thread::sleep_for(std::chrono::milliseconds(1000));
} }
if (server == nullptr) if (server == nullptr)
{ {
WARN_LOG(SLIPPI, "Could not create spectator server"); WARN_LOG(SLIPPI, "Could not create spectator server");
enet_deinitialize(); enet_deinitialize();
return; return;
} }
// Main slippicomm server loop // Main slippicomm server loop
while (1) while (1)
{ {
// If we're told to stop, then quit // If we're told to stop, then quit
if (m_stop_socket_thread) if (m_stop_socket_thread)
{ {
enet_host_destroy(server); enet_host_destroy(server);
enet_deinitialize(); enet_deinitialize();
return; return;
} }
// Pop off any events in the queue // Pop off any events in the queue
popEvents(); popEvents();
std::map<u16, std::shared_ptr<SlippiSocket>>::iterator it = m_sockets.begin(); std::map<u16, std::shared_ptr<SlippiSocket>>::iterator it = m_sockets.begin();
for (; it != m_sockets.end(); it++) for (; it != m_sockets.end(); it++)
{ {
if (it->second->m_shook_hands) if (it->second->m_shook_hands)
{ {
writeEvents(it->first); writeEvents(it->first);
} }
} }
ENetEvent event; ENetEvent event;
while (enet_host_service(server, &event, 1) > 0) while (enet_host_service(server, &event, 1) > 0)
{ {
switch (event.type) switch (event.type)
{ {
case ENET_EVENT_TYPE_CONNECT: case ENET_EVENT_TYPE_CONNECT:
{ {
INFO_LOG(SLIPPI, "A new spectator connected from %x:%u.\n", event.peer->address.host,
event.peer->address.port);
INFO_LOG(SLIPPI, "A new spectator connected from %x:%u.\n", event.peer->address.host, std::shared_ptr<SlippiSocket> newSlippiSocket(new SlippiSocket());
event.peer->address.port); newSlippiSocket->m_peer = event.peer;
m_sockets[event.peer->incomingPeerID] = newSlippiSocket;
break;
}
case ENET_EVENT_TYPE_RECEIVE:
{
handleMessage(event.packet->data, (u32)event.packet->dataLength,
event.peer->incomingPeerID);
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
std::shared_ptr<SlippiSocket> newSlippiSocket(new SlippiSocket()); break;
newSlippiSocket->m_peer = event.peer; }
m_sockets[event.peer->incomingPeerID] = newSlippiSocket; case ENET_EVENT_TYPE_DISCONNECT:
break; {
} INFO_LOG(SLIPPI, "A spectator disconnected from %x:%u.\n", event.peer->address.host,
case ENET_EVENT_TYPE_RECEIVE: event.peer->address.port);
{
handleMessage(event.packet->data, (u32)event.packet->dataLength, event.peer->incomingPeerID);
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
break; // Delete the item in the m_sockets map
} m_sockets.erase(event.peer->incomingPeerID);
case ENET_EVENT_TYPE_DISCONNECT: /* Reset the peer's client information. */
{ event.peer->data = NULL;
INFO_LOG(SLIPPI, "A spectator disconnected from %x:%u.\n", event.peer->address.host, break;
event.peer->address.port); }
default:
{
INFO_LOG(SLIPPI, "Spectator sent an unknown ENet event type");
break;
}
}
}
}
// Delete the item in the m_sockets map enet_host_destroy(server);
m_sockets.erase(event.peer->incomingPeerID); enet_deinitialize();
/* Reset the peer's client information. */
event.peer->data = NULL;
break;
}
default:
{
INFO_LOG(SLIPPI, "Spectator sent an unknown ENet event type");
break;
}
}
}
}
enet_host_destroy(server);
enet_deinitialize();
} }

View file

@ -5,9 +5,9 @@
#include <map> #include <map>
#include <thread> #include <thread>
#include <enet/enet.h>
#include "Common/SPSCQueue.h" #include "Common/SPSCQueue.h"
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include <enet/enet.h>
using json = nlohmann::json; using json = nlohmann::json;
// Sockets in windows are unsigned // Sockets in windows are unsigned
@ -30,10 +30,10 @@ typedef int SOCKET;
class SlippiSocket class SlippiSocket
{ {
public: public:
u64 m_cursor = 0; // Index of the last game event this client sent u64 m_cursor = 0; // Index of the last game event this client sent
u64 m_menu_cursor = 0; // The latest menu event that this socket has sent u64 m_menu_cursor = 0; // The latest menu event that this socket has sent
bool m_shook_hands = false; // Has this client shaken hands yet? bool m_shook_hands = false; // Has this client shaken hands yet?
ENetPeer* m_peer = NULL; // The ENet peer object for the socket ENetPeer* m_peer = NULL; // The ENet peer object for the socket
}; };
class SlippiSpectateServer class SlippiSpectateServer