mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-26 04:06:32 +00:00
This commit is contained in:
parent
f52c49f55f
commit
2964a86b9b
8 changed files with 715 additions and 695 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue