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

View file

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

View file

@ -1,49 +1,49 @@
#pragma once
#include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiUser.h"
#include <atomic>
#include <condition_variable> // std::condition_variable
#include <condition_variable> // std::condition_variable
#include <curl/curl.h>
#include <mutex> // std::mutex, std::unique_lock
#include <mutex> // std::mutex, std::unique_lock
#include <queue>
#include <string>
#include <thread>
#include <vector>
#include <queue>
#include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiUser.h"
class SlippiGameReporter
{
public:
struct PlayerReport
{
float damage_done;
u8 stocks_remaining;
};
struct GameReport
{
u32 duration_frames = 0;
std::vector<PlayerReport> players;
};
public:
struct PlayerReport
{
float damage_done;
u8 stocks_remaining;
};
struct GameReport
{
u32 duration_frames = 0;
std::vector<PlayerReport> players;
};
SlippiGameReporter(SlippiUser *user);
~SlippiGameReporter();
SlippiGameReporter(SlippiUser* user);
~SlippiGameReporter();
void StartReport(GameReport report);
void StartNewSession(std::vector<std::string> player_uids);
void ReportThreadHandler();
void StartReport(GameReport report);
void StartNewSession(std::vector<std::string> player_uids);
void ReportThreadHandler();
protected:
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
CURL *m_curl = nullptr;
struct curl_slist *m_curl_header_list = nullptr;
protected:
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
CURL* m_curl = nullptr;
struct curl_slist* m_curl_header_list = nullptr;
u32 gameIndex = 1;
std::vector<std::string> player_uids;
u32 gameIndex = 1;
std::vector<std::string> player_uids;
SlippiUser *m_user;
std::queue<GameReport> game_report_queue;
std::thread reportingThread;
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> run_thread;
SlippiUser* m_user;
std::queue<GameReport> game_report_queue;
std::thread reportingThread;
std::mutex mtx;
std::condition_variable cv;
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
if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize())
{
ERROR_LOG(SLIPPI_ONLINE,
"Netplay packet too small to read pad buffer. Size: %d, Inputs: %d, MinSize: %d",
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
ERROR_LOG_FMT(
SLIPPI_ONLINE,
"Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
break;
}

View file

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

View file

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

View file

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

View file

@ -5,9 +5,9 @@
#include <map>
#include <thread>
#include <enet/enet.h>
#include "Common/SPSCQueue.h"
#include "nlohmann/json.hpp"
#include <enet/enet.h>
using json = nlohmann::json;
// Sockets in windows are unsigned
@ -30,10 +30,10 @@ typedef int SOCKET;
class SlippiSocket
{
public:
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
bool m_shook_hands = false; // Has this client shaken hands yet?
ENetPeer* m_peer = NULL; // The ENet peer object for the socket
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
bool m_shook_hands = false; // Has this client shaken hands yet?
ENetPeer* m_peer = NULL; // The ENet peer object for the socket
};
class SlippiSpectateServer