mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-01 15:16:22 +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();
|
localSelections.Reset();
|
||||||
|
|
||||||
// Forces savestate to re-init regions when a new ISO is loaded
|
// Forces savestate to re-init regions when a new ISO is loaded
|
||||||
SlippiSavestate::shouldForceInit = true;
|
SlippiSavestate::shouldForceInit = true;
|
||||||
|
|
||||||
// Update user file and then listen for User
|
// Update user file and then listen for User
|
||||||
#ifndef IS_PLAYBACK
|
#ifndef IS_PLAYBACK
|
||||||
|
@ -448,15 +448,15 @@ void CEXISlippi::writeToFile(std::unique_ptr<WriteMessage> msg)
|
||||||
|
|
||||||
// Get display names and connection codes from slippi netplay client
|
// Get display names and connection codes from slippi netplay client
|
||||||
if (slippi_netplay)
|
if (slippi_netplay)
|
||||||
{
|
{
|
||||||
auto playerInfo = matchmaking->GetPlayerInfo();
|
auto playerInfo = matchmaking->GetPlayerInfo();
|
||||||
|
|
||||||
for (int i = 0; i < playerInfo.size(); i++)
|
for (int i = 0; i < playerInfo.size(); i++)
|
||||||
{
|
{
|
||||||
slippi_names[i] = playerInfo[i].display_name;
|
slippi_names[i] = playerInfo[i].display_name;
|
||||||
slippi_connect_codes[i] = playerInfo[i].connect_code;
|
slippi_connect_codes[i] = playerInfo[i].connect_code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no file, do nothing
|
// If no file, do nothing
|
||||||
|
@ -1516,11 +1516,11 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDisconnected())
|
if (isDisconnected())
|
||||||
{
|
{
|
||||||
auto status = slippi_netplay->GetSlippiConnectStatus();
|
auto status = slippi_netplay->GetSlippiConnectStatus();
|
||||||
m_read_queue.push_back(3); // Indicate we disconnected
|
m_read_queue.push_back(3); // Indicate we disconnected
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSkipOnlineFrame(frame))
|
if (shouldSkipOnlineFrame(frame))
|
||||||
{
|
{
|
||||||
|
@ -1620,6 +1620,17 @@ void CEXISlippi::handleSendInputs(u8* payload)
|
||||||
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
|
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
|
||||||
u8 delay = payload[4];
|
u8 delay = payload[4];
|
||||||
|
|
||||||
|
// On the first frame sent, we need to queue up empty dummy pads for as many
|
||||||
|
// frames as we have delay
|
||||||
|
if (frame == 1)
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= delay; i++)
|
||||||
|
{
|
||||||
|
auto empty = std::make_unique<SlippiPad>(i);
|
||||||
|
slippi_netplay->SendSlippiPad(std::move(empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[5]);
|
auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[5]);
|
||||||
|
|
||||||
slippi_netplay->SendSlippiPad(std::move(pad));
|
slippi_netplay->SendSlippiPad(std::move(pad));
|
||||||
|
@ -1641,55 +1652,56 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
|
||||||
m_read_queue.push_back(frameResult); // Indicate a continue frame
|
m_read_queue.push_back(frameResult); // Indicate a continue frame
|
||||||
|
|
||||||
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
||||||
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
|
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
|
||||||
|
|
||||||
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
|
int32_t frame = payload[0] << 24 | payload[1] << 16 | payload[2] << 8 | payload[3];
|
||||||
|
|
||||||
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
|
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
|
||||||
int offset[SLIPPI_REMOTE_PLAYER_MAX];
|
int offset[SLIPPI_REMOTE_PLAYER_MAX];
|
||||||
INFO_LOG(SLIPPI_ONLINE, "Preparing pad data for frame %d", frame);
|
INFO_LOG(SLIPPI_ONLINE, "Preparing pad data for frame %d", frame);
|
||||||
|
|
||||||
// Get pad data for each remote player and write each of their latest frame nums to the buf
|
// Get pad data for each remote player and write each of their latest frame nums to the buf
|
||||||
for (int i = 0; i < remotePlayerCount; i++)
|
for (int i = 0; i < remotePlayerCount; i++)
|
||||||
{
|
{
|
||||||
results[i] = slippi_netplay->GetSlippiRemotePad(frame, i);
|
results[i] = slippi_netplay->GetSlippiRemotePad(frame, i);
|
||||||
|
|
||||||
// determine offset from which to copy data
|
// determine offset from which to copy data
|
||||||
offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
|
offset[i] = (results[i]->latestFrame - frame) * SLIPPI_PAD_FULL_SIZE;
|
||||||
offset[i] = offset[i] < 0 ? 0 : offset[i];
|
offset[i] = offset[i] < 0 ? 0 : offset[i];
|
||||||
|
|
||||||
// add latest frame we are transfering to begining of return buf
|
// add latest frame we are transfering to begining of return buf
|
||||||
int32_t latestFrame = results[i]->latestFrame;
|
int32_t latestFrame = results[i]->latestFrame;
|
||||||
if (latestFrame > frame)
|
if (latestFrame > frame)
|
||||||
latestFrame = frame;
|
latestFrame = frame;
|
||||||
appendWordToBuffer(&m_read_queue, *(u32 *)&latestFrame);
|
appendWordToBuffer(&m_read_queue, *(u32*)&latestFrame);
|
||||||
// INFO_LOG(SLIPPI_ONLINE, "Sending frame num %d for pIdx %d (offset: %d)", latestFrame, i, offset[i]);
|
// INFO_LOG(SLIPPI_ONLINE, "Sending frame num %d for pIdx %d (offset: %d)", latestFrame, i,
|
||||||
}
|
// offset[i]);
|
||||||
// Send the current frame for any unused player slots.
|
}
|
||||||
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
// Send the current frame for any unused player slots.
|
||||||
{
|
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
||||||
appendWordToBuffer(&m_read_queue, *(u32 *)&frame);
|
{
|
||||||
}
|
appendWordToBuffer(&m_read_queue, *(u32*)&frame);
|
||||||
|
}
|
||||||
|
|
||||||
// copy pad data over
|
// copy pad data over
|
||||||
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
||||||
{
|
{
|
||||||
std::vector<u8> tx;
|
std::vector<u8> tx;
|
||||||
|
|
||||||
// Get pad data if this remote player exists
|
// Get pad data if this remote player exists
|
||||||
if (i < remotePlayerCount)
|
if (i < remotePlayerCount)
|
||||||
{
|
{
|
||||||
auto txStart = results[i]->data.begin() + offset[i];
|
auto txStart = results[i]->data.begin() + offset[i];
|
||||||
auto txEnd = results[i]->data.end();
|
auto txEnd = results[i]->data.end();
|
||||||
tx.insert(tx.end(), txStart, txEnd);
|
tx.insert(tx.end(), txStart, txEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0);
|
tx.resize(SLIPPI_PAD_FULL_SIZE * ROLLBACK_MAX_FRAMES, 0);
|
||||||
|
|
||||||
m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end());
|
m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
slippi_netplay->DropOldRemoteInputs(frame);
|
slippi_netplay->DropOldRemoteInputs(frame);
|
||||||
|
|
||||||
// ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5],
|
// ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5],
|
||||||
// m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10],
|
// m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10],
|
||||||
|
@ -1821,28 +1833,31 @@ void CEXISlippi::startFindMatch(u8* payload)
|
||||||
|
|
||||||
void CEXISlippi::prepareOnlineMatchState()
|
void CEXISlippi::prepareOnlineMatchState()
|
||||||
{
|
{
|
||||||
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young Link
|
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser vs P3 Young Link vs P4 Young
|
||||||
// on Battlefield. The proper values will be overwritten
|
// Link on Battlefield. The proper values will be overwritten
|
||||||
static std::vector<u8> onlineMatchBlock = {
|
static std::vector<u8> onlineMatchBlock = {
|
||||||
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00, 0x1F, 0x00, 0x00,
|
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00,
|
||||||
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
|
0x1F, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00,
|
||||||
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
||||||
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x15, 0x03, 0x04, 0x00, 0x00, 0xFF,
|
||||||
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
|
0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
|
||||||
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04,
|
||||||
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
|
||||||
};
|
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09,
|
||||||
|
0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
m_read_queue.clear();
|
m_read_queue.clear();
|
||||||
|
|
||||||
|
@ -1896,21 +1911,21 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
remotePlayersReady = true;
|
remotePlayersReady = true;
|
||||||
#else
|
#else
|
||||||
remotePlayersReady = 1;
|
remotePlayersReady = 1;
|
||||||
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
||||||
for (int i = 0; i < remotePlayerCount; i++)
|
for (int i = 0; i < remotePlayerCount; i++)
|
||||||
{
|
{
|
||||||
if (!matchInfo->remotePlayerSelections[i].isCharacterSelected)
|
if (!matchInfo->remotePlayerSelections[i].isCharacterSelected)
|
||||||
{
|
{
|
||||||
remotePlayersReady = 0;
|
remotePlayersReady = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remotePlayerCount == 1)
|
if (remotePlayerCount == 1)
|
||||||
{
|
{
|
||||||
auto isDecider = slippi_netplay->IsDecider();
|
auto isDecider = slippi_netplay->IsDecider();
|
||||||
localPlayerIndex = isDecider ? 0 : 1;
|
localPlayerIndex = isDecider ? 0 : 1;
|
||||||
remotePlayerIndex = isDecider ? 1 : 0;
|
remotePlayerIndex = isDecider ? 1 : 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto isDecider = slippi_netplay->IsDecider();
|
auto isDecider = slippi_netplay->IsDecider();
|
||||||
|
@ -1922,25 +1937,25 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
#ifndef LOCAL_TESTING
|
#ifndef LOCAL_TESTING
|
||||||
// If we get here, our opponent likely disconnected. Let's trigger a clean up
|
// If we get here, our opponent likely disconnected. Let's trigger a clean up
|
||||||
handleConnectionCleanup();
|
handleConnectionCleanup();
|
||||||
prepareOnlineMatchState(); // run again with new state
|
prepareOnlineMatchState(); // run again with new state
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// Here we are connected, check to see if we should init play session
|
// Here we are connected, check to see if we should init play session
|
||||||
if (!is_play_session_active)
|
if (!is_play_session_active)
|
||||||
{
|
{
|
||||||
std::vector<std::string> uids;
|
std::vector<std::string> uids;
|
||||||
|
|
||||||
auto mmPlayers = matchmaking->GetPlayerInfo();
|
auto mmPlayers = matchmaking->GetPlayerInfo();
|
||||||
for (auto mmp : mmPlayers)
|
for (auto mmp : mmPlayers)
|
||||||
{
|
{
|
||||||
uids.push_back(mmp.uid);
|
uids.push_back(mmp.uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
game_reporter->StartNewSession(uids);
|
game_reporter->StartNewSession(uids);
|
||||||
|
|
||||||
is_play_session_active = true;
|
is_play_session_active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1949,7 +1964,7 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
|
|
||||||
u32 rngOffset = 0;
|
u32 rngOffset = 0;
|
||||||
std::string localPlayerName = "";
|
std::string localPlayerName = "";
|
||||||
std::string oppName = "";
|
std::string oppName = "";
|
||||||
std::string p1Name = "";
|
std::string p1Name = "";
|
||||||
std::string p2Name = "";
|
std::string p2Name = "";
|
||||||
u8 chatMessageId = 0;
|
u8 chatMessageId = 0;
|
||||||
|
@ -1957,83 +1972,83 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
u8 sentChatMessageId = 0;
|
u8 sentChatMessageId = 0;
|
||||||
|
|
||||||
#ifdef LOCAL_TESTING
|
#ifdef LOCAL_TESTING
|
||||||
localPlayerIndex = 0;
|
localPlayerIndex = 0;
|
||||||
chatMessageId = localChatMessageId;
|
chatMessageId = localChatMessageId;
|
||||||
chatMessagePlayerIdx = 0;
|
chatMessagePlayerIdx = 0;
|
||||||
localChatMessageId = 0;
|
localChatMessageId = 0;
|
||||||
// in CSS p1 is always current player and p2 is opponent
|
// in CSS p1 is always current player and p2 is opponent
|
||||||
localPlayerName = p1Name = "Player 1";
|
localPlayerName = p1Name = "Player 1";
|
||||||
oppName = p2Name = "Player 2";
|
oppName = p2Name = "Player 2";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_read_queue.push_back(localPlayerReady); // Local player ready
|
m_read_queue.push_back(localPlayerReady); // Local player ready
|
||||||
m_read_queue.push_back(remotePlayersReady); // Remote players ready
|
m_read_queue.push_back(remotePlayersReady); // Remote players ready
|
||||||
m_read_queue.push_back(localPlayerIndex); // Local player index
|
m_read_queue.push_back(localPlayerIndex); // Local player index
|
||||||
m_read_queue.push_back(remotePlayerIndex); // Remote player index
|
m_read_queue.push_back(remotePlayerIndex); // Remote player index
|
||||||
|
|
||||||
// Set chat message if any
|
// Set chat message if any
|
||||||
if (slippi_netplay)
|
if (slippi_netplay)
|
||||||
{
|
{
|
||||||
auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage();
|
auto remoteMessageSelection = slippi_netplay->GetSlippiRemoteChatMessage();
|
||||||
chatMessageId = remoteMessageSelection.messageId;
|
chatMessageId = remoteMessageSelection.messageId;
|
||||||
chatMessagePlayerIdx = remoteMessageSelection.playerIdx;
|
chatMessagePlayerIdx = remoteMessageSelection.playerIdx;
|
||||||
sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage();
|
sentChatMessageId = slippi_netplay->GetSlippiRemoteSentChatMessage();
|
||||||
// in CSS p1 is always current player and p2 is opponent
|
// in CSS p1 is always current player and p2 is opponent
|
||||||
localPlayerName = p1Name = userInfo.display_name;
|
localPlayerName = p1Name = userInfo.display_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
|
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
|
||||||
|
|
||||||
std::vector<u8> leftTeamPlayers = {};
|
std::vector<u8> leftTeamPlayers = {};
|
||||||
std::vector<u8> rightTeamPlayers = {};
|
std::vector<u8> rightTeamPlayers = {};
|
||||||
|
|
||||||
if (localPlayerReady && remotePlayersReady)
|
if (localPlayerReady && remotePlayersReady)
|
||||||
{
|
{
|
||||||
auto isDecider = slippi_netplay->IsDecider();
|
auto isDecider = slippi_netplay->IsDecider();
|
||||||
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
u8 remotePlayerCount = matchmaking->RemotePlayerCount();
|
||||||
auto matchInfo = slippi_netplay->GetMatchInfo();
|
auto matchInfo = slippi_netplay->GetMatchInfo();
|
||||||
SlippiPlayerSelections lps = matchInfo->localPlayerSelections;
|
SlippiPlayerSelections lps = matchInfo->localPlayerSelections;
|
||||||
auto rps = matchInfo->remotePlayerSelections;
|
auto rps = matchInfo->remotePlayerSelections;
|
||||||
|
|
||||||
#ifdef LOCAL_TESTING
|
#ifdef LOCAL_TESTING
|
||||||
lps.playerIdx = 0;
|
lps.playerIdx = 0;
|
||||||
|
|
||||||
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
for (int i = 0; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
|
||||||
{
|
{
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
{
|
{
|
||||||
rps[i].characterColor = 1;
|
rps[i].characterColor = 1;
|
||||||
rps[i].teamId = 1;
|
rps[i].teamId = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rps[i].characterColor = 2;
|
rps[i].characterColor = 2;
|
||||||
rps[i].teamId = 2;
|
rps[i].teamId = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
rps[i].characterId = 0x2 + i;
|
rps[i].characterId = 0x2 + i;
|
||||||
rps[i].playerIdx = i + 1;
|
rps[i].playerIdx = i + 1;
|
||||||
rps[i].isCharacterSelected = true;
|
rps[i].isCharacterSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
|
||||||
{
|
{
|
||||||
remotePlayerCount = 4;
|
remotePlayerCount = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
oppName = std::string("Player");
|
oppName = std::string("Player");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Check if someone is picking dumb characters in non-direct
|
// Check if someone is picking dumb characters in non-direct
|
||||||
auto localCharOk = lps.characterId < 26;
|
auto localCharOk = lps.characterId < 26;
|
||||||
auto remoteCharOk = true;
|
auto remoteCharOk = true;
|
||||||
INFO_LOG(SLIPPI_ONLINE, "remotePlayerCount: %d", remotePlayerCount);
|
INFO_LOG(SLIPPI_ONLINE, "remotePlayerCount: %d", remotePlayerCount);
|
||||||
for (int i = 0; i < remotePlayerCount; i++)
|
for (int i = 0; i < remotePlayerCount; i++)
|
||||||
{
|
{
|
||||||
if (rps[i].characterId >= 26)
|
if (rps[i].characterId >= 26)
|
||||||
remoteCharOk = false;
|
remoteCharOk = false;
|
||||||
}
|
}
|
||||||
if (lastSearch.mode < directMode && (!localCharOk || !remoteCharOk))
|
if (lastSearch.mode < directMode && (!localCharOk || !remoteCharOk))
|
||||||
{
|
{
|
||||||
// If we get here, someone is doing something bad, clear the lobby
|
// If we get here, someone is doing something bad, clear the lobby
|
||||||
handleConnectionCleanup();
|
handleConnectionCleanup();
|
||||||
|
@ -2045,98 +2060,99 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
|
|
||||||
// Overwrite local player character
|
// Overwrite local player character
|
||||||
onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId;
|
onlineMatchBlock[0x60 + (lps.playerIdx) * 0x24] = lps.characterId;
|
||||||
onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor;
|
onlineMatchBlock[0x63 + (lps.playerIdx) * 0x24] = lps.characterColor;
|
||||||
onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0;
|
onlineMatchBlock[0x67 + (lps.playerIdx) * 0x24] = 0;
|
||||||
onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId;
|
onlineMatchBlock[0x69 + (lps.playerIdx) * 0x24] = lps.teamId;
|
||||||
|
|
||||||
// Overwrite remote player character
|
// Overwrite remote player character
|
||||||
for (int i = 0; i < remotePlayerCount; i++)
|
for (int i = 0; i < remotePlayerCount; i++)
|
||||||
{
|
{
|
||||||
u8 idx = matchInfo->remotePlayerSelections[i].playerIdx;
|
u8 idx = matchInfo->remotePlayerSelections[i].playerIdx;
|
||||||
onlineMatchBlock[0x60 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterId;
|
onlineMatchBlock[0x60 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterId;
|
||||||
|
|
||||||
// Set Char Colors
|
// Set Char Colors
|
||||||
onlineMatchBlock[0x63 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterColor;
|
onlineMatchBlock[0x63 + idx * 0x24] = matchInfo->remotePlayerSelections[i].characterColor;
|
||||||
|
|
||||||
// Set Team Ids
|
// Set Team Ids
|
||||||
onlineMatchBlock[0x69 + idx * 0x24] = matchInfo->remotePlayerSelections[i].teamId;
|
onlineMatchBlock[0x69 + idx * 0x24] = matchInfo->remotePlayerSelections[i].teamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Singles/Teams specific logic
|
// Handle Singles/Teams specific logic
|
||||||
if (remotePlayerCount < 3)
|
if (remotePlayerCount < 3)
|
||||||
{
|
{
|
||||||
onlineMatchBlock[0x8] = 0; // is Teams = false
|
onlineMatchBlock[0x8] = 0; // is Teams = false
|
||||||
|
|
||||||
// Set p3/p4 player type to none
|
// Set p3/p4 player type to none
|
||||||
onlineMatchBlock[0x61 + 2 * 0x24] = 3;
|
onlineMatchBlock[0x61 + 2 * 0x24] = 3;
|
||||||
onlineMatchBlock[0x61 + 3 * 0x24] = 3;
|
onlineMatchBlock[0x61 + 3 * 0x24] = 3;
|
||||||
|
|
||||||
// Make one character lighter if same character, same color
|
// Make one character lighter if same character, same color
|
||||||
bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 ||
|
bool isSheikVsZelda = lps.characterId == 0x12 && rps[0].characterId == 0x13 ||
|
||||||
lps.characterId == 0x13 && rps[0].characterId == 0x12;
|
lps.characterId == 0x13 && rps[0].characterId == 0x12;
|
||||||
bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda;
|
bool charMatch = lps.characterId == rps[0].characterId || isSheikVsZelda;
|
||||||
bool colMatch = lps.characterColor == rps[0].characterColor;
|
bool colMatch = lps.characterColor == rps[0].characterColor;
|
||||||
|
|
||||||
onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0;
|
onlineMatchBlock[0x67 + 0x24] = charMatch && colMatch ? 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
onlineMatchBlock[0x8] = 1; // is Teams = true
|
onlineMatchBlock[0x8] = 1; // is Teams = true
|
||||||
|
|
||||||
// Set p3/p4 player type to human
|
// Set p3/p4 player type to human
|
||||||
onlineMatchBlock[0x61 + 2 * 0x24] = 0;
|
onlineMatchBlock[0x61 + 2 * 0x24] = 0;
|
||||||
onlineMatchBlock[0x61 + 3 * 0x24] = 0;
|
onlineMatchBlock[0x61 + 3 * 0x24] = 0;
|
||||||
|
|
||||||
// Set alt color to light/dark costume for multiples of the same character on a team
|
// Set alt color to light/dark costume for multiples of the same character on a team
|
||||||
int characterCount[26][3] = {0};
|
int characterCount[26][3] = {0};
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
int charId = onlineMatchBlock[0x60 + i * 0x24];
|
int charId = onlineMatchBlock[0x60 + i * 0x24];
|
||||||
int teamId = onlineMatchBlock[0x69 + i * 0x24];
|
int teamId = onlineMatchBlock[0x69 + i * 0x24];
|
||||||
onlineMatchBlock[0x67 + i * 0x24] = characterCount[charId][teamId];
|
onlineMatchBlock[0x67 + i * 0x24] = characterCount[charId][teamId];
|
||||||
characterCount[charId][teamId]++;
|
characterCount[charId][teamId]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite stage
|
// Overwrite stage
|
||||||
u16 stageId;
|
u16 stageId;
|
||||||
if (isDecider)
|
if (isDecider)
|
||||||
{
|
{
|
||||||
stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId;
|
stageId = lps.isStageSelected ? lps.stageId : rps[0].stageId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId;
|
stageId = rps[0].isStageSelected ? rps[0].stageId : lps.stageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 *stage = (u16 *)&onlineMatchBlock[0xE];
|
u16* stage = (u16*)&onlineMatchBlock[0xE];
|
||||||
*stage = Common::swap16(stageId);
|
*stage = Common::swap16(stageId);
|
||||||
|
|
||||||
// Set rng offset
|
// Set rng offset
|
||||||
rngOffset = isDecider ? lps.rngOffset : rps[0].rngOffset;
|
rngOffset = isDecider ? lps.rngOffset : rps[0].rngOffset;
|
||||||
WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset);
|
WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset);
|
||||||
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], onlineMatchBlock[0x84]);
|
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60],
|
||||||
|
onlineMatchBlock[0x84]);
|
||||||
|
|
||||||
// Turn pause on in direct, off in everything else
|
// Turn pause on in direct, off in everything else
|
||||||
u8 *gameBitField3 = (u8 *)&onlineMatchBlock[2];
|
u8* gameBitField3 = (u8*)&onlineMatchBlock[2];
|
||||||
*gameBitField3 = lastSearch.mode >= directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
|
*gameBitField3 = lastSearch.mode >= directMode ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
|
||||||
//*gameBitField3 = *gameBitField3 | 0x8;
|
//*gameBitField3 = *gameBitField3 | 0x8;
|
||||||
|
|
||||||
// Group players into left/right side for team splash screen display
|
// Group players into left/right side for team splash screen display
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
int teamId = onlineMatchBlock[0x69 + i * 0x24];
|
int teamId = onlineMatchBlock[0x69 + i * 0x24];
|
||||||
if (teamId == lps.teamId)
|
if (teamId == lps.teamId)
|
||||||
leftTeamPlayers.push_back(i);
|
leftTeamPlayers.push_back(i);
|
||||||
else
|
else
|
||||||
rightTeamPlayers.push_back(i);
|
rightTeamPlayers.push_back(i);
|
||||||
}
|
}
|
||||||
int leftTeamSize = leftTeamPlayers.size();
|
int leftTeamSize = leftTeamPlayers.size();
|
||||||
int rightTeamSize = rightTeamPlayers.size();
|
int rightTeamSize = rightTeamPlayers.size();
|
||||||
leftTeamPlayers.resize(4, 0);
|
leftTeamPlayers.resize(4, 0);
|
||||||
rightTeamPlayers.resize(4, 0);
|
rightTeamPlayers.resize(4, 0);
|
||||||
leftTeamPlayers[3] = leftTeamSize;
|
leftTeamPlayers[3] = leftTeamSize;
|
||||||
rightTeamPlayers[3] = rightTeamSize;
|
rightTeamPlayers[3] = rightTeamSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add rng offset to output
|
// Add rng offset to output
|
||||||
|
@ -2148,52 +2164,52 @@ void CEXISlippi::prepareOnlineMatchState()
|
||||||
// Add chat messages id
|
// Add chat messages id
|
||||||
m_read_queue.push_back((u8)sentChatMessageId);
|
m_read_queue.push_back((u8)sentChatMessageId);
|
||||||
m_read_queue.push_back((u8)chatMessageId);
|
m_read_queue.push_back((u8)chatMessageId);
|
||||||
m_read_queue.push_back((u8)chatMessagePlayerIdx);
|
m_read_queue.push_back((u8)chatMessagePlayerIdx);
|
||||||
|
|
||||||
// Add player groupings for VS splash screen
|
// Add player groupings for VS splash screen
|
||||||
leftTeamPlayers.resize(4, 0);
|
leftTeamPlayers.resize(4, 0);
|
||||||
rightTeamPlayers.resize(4, 0);
|
rightTeamPlayers.resize(4, 0);
|
||||||
m_read_queue.insert(m_read_queue.end(), leftTeamPlayers.begin(), leftTeamPlayers.end());
|
m_read_queue.insert(m_read_queue.end(), leftTeamPlayers.begin(), leftTeamPlayers.end());
|
||||||
m_read_queue.insert(m_read_queue.end(), rightTeamPlayers.begin(), rightTeamPlayers.end());
|
m_read_queue.insert(m_read_queue.end(), rightTeamPlayers.begin(), rightTeamPlayers.end());
|
||||||
|
|
||||||
// Add names to output
|
// Add names to output
|
||||||
// Always send static local player name
|
// Always send static local player name
|
||||||
localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH);
|
localPlayerName = ConvertStringForGame(localPlayerName, MAX_NAME_LENGTH);
|
||||||
m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end());
|
m_read_queue.insert(m_read_queue.end(), localPlayerName.begin(), localPlayerName.end());
|
||||||
|
|
||||||
#ifdef LOCAL_TESTING
|
#ifdef LOCAL_TESTING
|
||||||
std::string defaultNames[] = {"Player 1", "Player 2", "Player 3", "Player 4"};
|
std::string defaultNames[] = {"Player 1", "Player 2", "Player 3", "Player 4"};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
std::string name = matchmaking->GetPlayerName(i);
|
std::string name = matchmaking->GetPlayerName(i);
|
||||||
#ifdef LOCAL_TESTING
|
#ifdef LOCAL_TESTING
|
||||||
name = defaultNames[i];
|
name = defaultNames[i];
|
||||||
#endif
|
#endif
|
||||||
name = ConvertStringForGame(name, MAX_NAME_LENGTH);
|
name = ConvertStringForGame(name, MAX_NAME_LENGTH);
|
||||||
m_read_queue.insert(m_read_queue.end(), name.begin(), name.end());
|
m_read_queue.insert(m_read_queue.end(), name.begin(), name.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the opponent string using the names of all players on opposing teams
|
// Create the opponent string using the names of all players on opposing teams
|
||||||
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
|
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
|
||||||
std::string oppText = "";
|
std::string oppText = "";
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
if (i == localPlayerIndex)
|
if (i == localPlayerIndex)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
|
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
|
||||||
{
|
{
|
||||||
if (oppText != "")
|
if (oppText != "")
|
||||||
oppText += "/";
|
oppText += "/";
|
||||||
|
|
||||||
oppText += matchmaking->GetPlayerName(i);
|
oppText += matchmaking->GetPlayerName(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (matchmaking->RemotePlayerCount() == 1)
|
if (matchmaking->RemotePlayerCount() == 1)
|
||||||
oppText = matchmaking->GetPlayerName(remotePlayerIndex);
|
oppText = matchmaking->GetPlayerName(remotePlayerIndex);
|
||||||
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
|
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
|
||||||
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
|
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
|
||||||
|
|
||||||
// Add error message if there is one
|
// Add error message if there is one
|
||||||
|
@ -2237,12 +2253,12 @@ void CEXISlippi::setMatchSelections(u8* payload)
|
||||||
SlippiPlayerSelections s;
|
SlippiPlayerSelections s;
|
||||||
|
|
||||||
s.teamId = payload[0];
|
s.teamId = payload[0];
|
||||||
s.characterId = payload[1];
|
s.characterId = payload[1];
|
||||||
s.characterColor = payload[2];
|
s.characterColor = payload[2];
|
||||||
s.isCharacterSelected = payload[3];
|
s.isCharacterSelected = payload[3];
|
||||||
|
|
||||||
s.stageId = Common::swap16(&payload[4]);
|
s.stageId = Common::swap16(&payload[4]);
|
||||||
u8 stageSelectOption = payload[6];
|
u8 stageSelectOption = payload[6];
|
||||||
|
|
||||||
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
|
s.isStageSelected = stageSelectOption == 1 || stageSelectOption == 3;
|
||||||
if (stageSelectOption == 3)
|
if (stageSelectOption == 3)
|
||||||
|
@ -2251,16 +2267,16 @@ void CEXISlippi::setMatchSelections(u8* payload)
|
||||||
s.stageId = getRandomStage();
|
s.stageId = getRandomStage();
|
||||||
}
|
}
|
||||||
|
|
||||||
INFO_LOG(SLIPPI, "LPS set char: %d, iSS: %d, %d, stage: %d, team: %d", s.isCharacterSelected, stageSelectOption,
|
INFO_LOG(SLIPPI, "LPS set char: %d, iSS: %d, %d, stage: %d, team: %d", s.isCharacterSelected,
|
||||||
s.isStageSelected, s.stageId, s.teamId);
|
stageSelectOption, s.isStageSelected, s.stageId, s.teamId);
|
||||||
|
|
||||||
s.rngOffset = generator() % 0xFFFF;
|
s.rngOffset = generator() % 0xFFFF;
|
||||||
|
|
||||||
if (matchmaking->LocalPlayerIndex() == 1 && firstMatch)
|
if (matchmaking->LocalPlayerIndex() == 1 && firstMatch)
|
||||||
{
|
{
|
||||||
firstMatch = false;
|
firstMatch = false;
|
||||||
s.stageId = getRandomStage();
|
s.stageId = getRandomStage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge these selections
|
// Merge these selections
|
||||||
localSelections.Merge(s);
|
localSelections.Merge(s);
|
||||||
|
@ -2432,7 +2448,7 @@ void CEXISlippi::handleConnectionCleanup()
|
||||||
forcedError.clear();
|
forcedError.clear();
|
||||||
|
|
||||||
// Reset play session
|
// Reset play session
|
||||||
is_play_session_active = false;
|
is_play_session_active = false;
|
||||||
firstMatch = true;
|
firstMatch = true;
|
||||||
|
|
||||||
#ifdef LOCAL_TESTING
|
#ifdef LOCAL_TESTING
|
||||||
|
@ -2451,28 +2467,28 @@ void CEXISlippi::prepareNewSeed()
|
||||||
appendWordToBuffer(&m_read_queue, newSeed);
|
appendWordToBuffer(&m_read_queue, newSeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXISlippi::handleReportGame(u8 *payload)
|
void CEXISlippi::handleReportGame(u8* payload)
|
||||||
{
|
{
|
||||||
SlippiGameReporter::GameReport r;
|
SlippiGameReporter::GameReport r;
|
||||||
r.duration_frames = Common::swap32(&payload[0]);
|
r.duration_frames = Common::swap32(&payload[0]);
|
||||||
|
|
||||||
//ERROR_LOG(SLIPPI_ONLINE, "Frames: %d", r.duration_frames);
|
// ERROR_LOG(SLIPPI_ONLINE, "Frames: %d", r.duration_frames);
|
||||||
|
|
||||||
for (auto i = 0; i < 2; ++i)
|
for (auto i = 0; i < 2; ++i)
|
||||||
{
|
{
|
||||||
SlippiGameReporter::PlayerReport p;
|
SlippiGameReporter::PlayerReport p;
|
||||||
auto offset = i * 6;
|
auto offset = i * 6;
|
||||||
p.stocks_remaining = payload[5 + offset];
|
p.stocks_remaining = payload[5 + offset];
|
||||||
|
|
||||||
auto swappedDamageDone = Common::swap32(&payload[6 + offset]);
|
auto swappedDamageDone = Common::swap32(&payload[6 + offset]);
|
||||||
p.damage_done = *(float *)&swappedDamageDone;
|
p.damage_done = *(float*)&swappedDamageDone;
|
||||||
|
|
||||||
//ERROR_LOG(SLIPPI_ONLINE, "Stocks: %d, DamageDone: %f", p.stocks_remaining, p.damage_done);
|
// ERROR_LOG(SLIPPI_ONLINE, "Stocks: %d, DamageDone: %f", p.stocks_remaining, p.damage_done);
|
||||||
|
|
||||||
r.players.push_back(p);
|
r.players.push_back(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
game_reporter->StartReport(r);
|
game_reporter->StartReport(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
||||||
|
@ -2602,8 +2618,8 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
|
||||||
prepareNewSeed();
|
prepareNewSeed();
|
||||||
break;
|
break;
|
||||||
case CMD_REPORT_GAME:
|
case CMD_REPORT_GAME:
|
||||||
handleReportGame(&memPtr[bufLoc + 1]);
|
handleReportGame(&memPtr[bufLoc + 1]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
|
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
|
||||||
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
|
||||||
|
|
|
@ -16,123 +16,123 @@
|
||||||
#include <json.hpp>
|
#include <json.hpp>
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
static size_t receive(char *ptr, size_t size, size_t nmemb, void *rcvBuf)
|
static size_t receive(char* ptr, size_t size, size_t nmemb, void* rcvBuf)
|
||||||
{
|
{
|
||||||
size_t len = size * nmemb;
|
size_t len = size * nmemb;
|
||||||
INFO_LOG(SLIPPI_ONLINE, "[User] Received data: %d", len);
|
INFO_LOG(SLIPPI_ONLINE, "[User] Received data: %d", len);
|
||||||
|
|
||||||
std::string *buf = (std::string *)rcvBuf;
|
std::string* buf = (std::string*)rcvBuf;
|
||||||
|
|
||||||
buf->insert(buf->end(), ptr, ptr + len);
|
buf->insert(buf->end(), ptr, ptr + len);
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiGameReporter::SlippiGameReporter(SlippiUser *user)
|
SlippiGameReporter::SlippiGameReporter(SlippiUser* user)
|
||||||
{
|
{
|
||||||
CURL *curl = curl_easy_init();
|
CURL* curl = curl_easy_init();
|
||||||
if (curl)
|
if (curl)
|
||||||
{
|
{
|
||||||
// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive);
|
// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receive);
|
||||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000);
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 10000);
|
||||||
|
|
||||||
// Set up HTTP Headers
|
// Set up HTTP Headers
|
||||||
m_curl_header_list = curl_slist_append(m_curl_header_list, "Content-Type: application/json");
|
m_curl_header_list = curl_slist_append(m_curl_header_list, "Content-Type: application/json");
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, m_curl_header_list);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, m_curl_header_list);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// ALPN support is enabled by default but requires Windows >= 8.1.
|
// ALPN support is enabled by default but requires Windows >= 8.1.
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false);
|
curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_curl = curl;
|
m_curl = curl;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_user = user;
|
m_user = user;
|
||||||
|
|
||||||
run_thread = true;
|
run_thread = true;
|
||||||
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
|
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiGameReporter::~SlippiGameReporter()
|
SlippiGameReporter::~SlippiGameReporter()
|
||||||
{
|
{
|
||||||
run_thread = false;
|
run_thread = false;
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
if (reportingThread.joinable())
|
if (reportingThread.joinable())
|
||||||
reportingThread.join();
|
reportingThread.join();
|
||||||
|
|
||||||
if (m_curl)
|
if (m_curl)
|
||||||
{
|
{
|
||||||
curl_slist_free_all(m_curl_header_list);
|
curl_slist_free_all(m_curl_header_list);
|
||||||
curl_easy_cleanup(m_curl);
|
curl_easy_cleanup(m_curl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiGameReporter::StartReport(GameReport report)
|
void SlippiGameReporter::StartReport(GameReport report)
|
||||||
{
|
{
|
||||||
game_report_queue.emplace(report);
|
game_report_queue.emplace(report);
|
||||||
cv.notify_one();
|
cv.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiGameReporter::StartNewSession(std::vector<std::string> player_uids)
|
void SlippiGameReporter::StartNewSession(std::vector<std::string> player_uids)
|
||||||
{
|
{
|
||||||
this->player_uids = player_uids;
|
this->player_uids = player_uids;
|
||||||
gameIndex = 1;
|
gameIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiGameReporter::ReportThreadHandler()
|
void SlippiGameReporter::ReportThreadHandler()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lck(mtx);
|
std::unique_lock<std::mutex> lck(mtx);
|
||||||
|
|
||||||
while (run_thread)
|
while (run_thread)
|
||||||
{
|
{
|
||||||
// Wait for report to come in
|
// Wait for report to come in
|
||||||
cv.wait(lck);
|
cv.wait(lck);
|
||||||
|
|
||||||
// Process all messages
|
// Process all messages
|
||||||
while (!game_report_queue.empty())
|
while (!game_report_queue.empty())
|
||||||
{
|
{
|
||||||
auto report = game_report_queue.front();
|
auto report = game_report_queue.front();
|
||||||
game_report_queue.pop();
|
game_report_queue.pop();
|
||||||
|
|
||||||
auto userInfo = m_user->GetUserInfo();
|
auto userInfo = m_user->GetUserInfo();
|
||||||
|
|
||||||
// Prepare report
|
// Prepare report
|
||||||
json request;
|
json request;
|
||||||
request["uid"] = userInfo.uid;
|
request["uid"] = userInfo.uid;
|
||||||
request["playKey"] = userInfo.play_key;
|
request["playKey"] = userInfo.play_key;
|
||||||
request["gameIndex"] = gameIndex;
|
request["gameIndex"] = gameIndex;
|
||||||
request["gameDurationFrames"] = report.duration_frames;
|
request["gameDurationFrames"] = report.duration_frames;
|
||||||
|
|
||||||
json players = json::array();
|
json players = json::array();
|
||||||
for (int i = 0; i < report.players.size(); i++)
|
for (int i = 0; i < report.players.size(); i++)
|
||||||
{
|
{
|
||||||
json p;
|
json p;
|
||||||
p["uid"] = player_uids[i];
|
p["uid"] = player_uids[i];
|
||||||
p["damage_done"] = report.players[i].damage_done;
|
p["damage_done"] = report.players[i].damage_done;
|
||||||
p["stocks_remaining"] = report.players[i].stocks_remaining;
|
p["stocks_remaining"] = report.players[i].stocks_remaining;
|
||||||
|
|
||||||
players[i] = p;
|
players[i] = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
request["players"] = players;
|
request["players"] = players;
|
||||||
|
|
||||||
auto requestString = request.dump();
|
auto requestString = request.dump();
|
||||||
|
|
||||||
// Send report
|
// Send report
|
||||||
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
curl_easy_setopt(m_curl, CURLOPT_POST, true);
|
||||||
curl_easy_setopt(m_curl, CURLOPT_URL, REPORT_URL.c_str());
|
curl_easy_setopt(m_curl, CURLOPT_URL, REPORT_URL.c_str());
|
||||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str());
|
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str());
|
||||||
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length());
|
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length());
|
||||||
CURLcode res = curl_easy_perform(m_curl);
|
CURLcode res = curl_easy_perform(m_curl);
|
||||||
|
|
||||||
if (res != 0)
|
if (res != 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG(SLIPPI_ONLINE, "[GameReport] Got error executing request. Err code: %d", res);
|
ERROR_LOG(SLIPPI_ONLINE, "[GameReport] Got error executing request. Err code: %d", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
gameIndex++;
|
gameIndex++;
|
||||||
Common::SleepCurrentThread(0);
|
Common::SleepCurrentThread(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
|
||||||
#include "Core/Slippi/SlippiUser.h"
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable> // std::condition_variable
|
#include <condition_variable> // std::condition_variable
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <mutex> // std::mutex, std::unique_lock
|
#include <mutex> // std::mutex, std::unique_lock
|
||||||
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <queue>
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Core/Slippi/SlippiUser.h"
|
||||||
|
|
||||||
class SlippiGameReporter
|
class SlippiGameReporter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct PlayerReport
|
struct PlayerReport
|
||||||
{
|
{
|
||||||
float damage_done;
|
float damage_done;
|
||||||
u8 stocks_remaining;
|
u8 stocks_remaining;
|
||||||
};
|
};
|
||||||
struct GameReport
|
struct GameReport
|
||||||
{
|
{
|
||||||
u32 duration_frames = 0;
|
u32 duration_frames = 0;
|
||||||
std::vector<PlayerReport> players;
|
std::vector<PlayerReport> players;
|
||||||
};
|
};
|
||||||
|
|
||||||
SlippiGameReporter(SlippiUser *user);
|
SlippiGameReporter(SlippiUser* user);
|
||||||
~SlippiGameReporter();
|
~SlippiGameReporter();
|
||||||
|
|
||||||
void StartReport(GameReport report);
|
void StartReport(GameReport report);
|
||||||
void StartNewSession(std::vector<std::string> player_uids);
|
void StartNewSession(std::vector<std::string> player_uids);
|
||||||
void ReportThreadHandler();
|
void ReportThreadHandler();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
|
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
|
||||||
CURL *m_curl = nullptr;
|
CURL* m_curl = nullptr;
|
||||||
struct curl_slist *m_curl_header_list = nullptr;
|
struct curl_slist* m_curl_header_list = nullptr;
|
||||||
|
|
||||||
u32 gameIndex = 1;
|
u32 gameIndex = 1;
|
||||||
std::vector<std::string> player_uids;
|
std::vector<std::string> player_uids;
|
||||||
|
|
||||||
SlippiUser *m_user;
|
SlippiUser* m_user;
|
||||||
std::queue<GameReport> game_report_queue;
|
std::queue<GameReport> game_report_queue;
|
||||||
std::thread reportingThread;
|
std::thread reportingThread;
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
std::atomic<bool> run_thread;
|
std::atomic<bool> run_thread;
|
||||||
};
|
};
|
||||||
|
|
|
@ -244,9 +244,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
|
||||||
// Check that the packet actually contains the data it claims to
|
// Check that the packet actually contains the data it claims to
|
||||||
if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize())
|
if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize())
|
||||||
{
|
{
|
||||||
ERROR_LOG(SLIPPI_ONLINE,
|
ERROR_LOG_FMT(
|
||||||
"Netplay packet too small to read pad buffer. Size: %d, Inputs: %d, MinSize: %d",
|
SLIPPI_ONLINE,
|
||||||
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
|
"Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
|
||||||
|
(int)packet.getDataSize(), inputsToCopy, 5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ SlippiPad::SlippiPad(int32_t frame, u8* padBuf) : SlippiPad(frame)
|
||||||
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf) : SlippiPad(frame)
|
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf) : SlippiPad(frame)
|
||||||
{
|
{
|
||||||
this->frame = frame;
|
this->frame = frame;
|
||||||
this->playerIdx = playerIdx;
|
this->playerIdx = playerIdx;
|
||||||
// Overwrite the data portion of the pad
|
// Overwrite the data portion of the pad
|
||||||
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
SlippiPad::~SlippiPad()
|
SlippiPad::~SlippiPad()
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SlippiPad
|
||||||
public:
|
public:
|
||||||
SlippiPad(int32_t frame);
|
SlippiPad(int32_t frame);
|
||||||
SlippiPad(int32_t frame, u8* padBuf);
|
SlippiPad(int32_t frame, u8* padBuf);
|
||||||
SlippiPad(int32_t frame, u8 playerIdx, u8 *padBuf);
|
SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf);
|
||||||
~SlippiPad();
|
~SlippiPad();
|
||||||
|
|
||||||
int32_t frame;
|
int32_t frame;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#include "SlippiSpectate.h"
|
#include "SlippiSpectate.h"
|
||||||
|
#include <Core/ConfigManager.h>
|
||||||
#include "Common/Base64.hpp"
|
#include "Common/Base64.hpp"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/Version.h"
|
#include "Common/Version.h"
|
||||||
#include <Core/ConfigManager.h>
|
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -21,12 +21,13 @@ inline bool isSpectatorEnabled()
|
||||||
SlippiSpectateServer& SlippiSpectateServer::getInstance()
|
SlippiSpectateServer& SlippiSpectateServer::getInstance()
|
||||||
{
|
{
|
||||||
static SlippiSpectateServer instance;
|
static SlippiSpectateServer instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiSpectateServer::write(u8 *payload, u32 length)
|
void SlippiSpectateServer::write(u8* payload, u32 length)
|
||||||
{
|
{
|
||||||
if (isSpectatorEnabled()) {
|
if (isSpectatorEnabled())
|
||||||
|
{
|
||||||
std::string str_payload((char*)payload, length);
|
std::string str_payload((char*)payload, length);
|
||||||
m_event_queue.Push(str_payload);
|
m_event_queue.Push(str_payload);
|
||||||
}
|
}
|
||||||
|
@ -35,134 +36,135 @@ void SlippiSpectateServer::write(u8 *payload, u32 length)
|
||||||
// CALLED FROM DOLPHIN MAIN THREAD
|
// CALLED FROM DOLPHIN MAIN THREAD
|
||||||
void SlippiSpectateServer::startGame()
|
void SlippiSpectateServer::startGame()
|
||||||
{
|
{
|
||||||
if (isSpectatorEnabled())
|
if (isSpectatorEnabled())
|
||||||
{
|
{
|
||||||
m_event_queue.Push("START_GAME");
|
m_event_queue.Push("START_GAME");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED FROM DOLPHIN MAIN THREAD
|
// CALLED FROM DOLPHIN MAIN THREAD
|
||||||
void SlippiSpectateServer::endGame()
|
void SlippiSpectateServer::endGame()
|
||||||
{
|
{
|
||||||
if (isSpectatorEnabled())
|
if (isSpectatorEnabled())
|
||||||
{
|
{
|
||||||
m_event_queue.Push("END_GAME");
|
m_event_queue.Push("END_GAME");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED FROM SERVER THREAD
|
// CALLED FROM SERVER THREAD
|
||||||
void SlippiSpectateServer::writeEvents(u16 peer_id)
|
void SlippiSpectateServer::writeEvents(u16 peer_id)
|
||||||
{
|
{
|
||||||
// Send menu events
|
// Send menu events
|
||||||
if (!m_in_game && (m_sockets[peer_id]->m_menu_cursor != m_menu_cursor))
|
if (!m_in_game && (m_sockets[peer_id]->m_menu_cursor != m_menu_cursor))
|
||||||
{
|
{
|
||||||
ENetPacket *packet = enet_packet_create(m_menu_event.data(), m_menu_event.length(), ENET_PACKET_FLAG_RELIABLE);
|
ENetPacket* packet =
|
||||||
// Batch for sending
|
enet_packet_create(m_menu_event.data(), m_menu_event.length(), ENET_PACKET_FLAG_RELIABLE);
|
||||||
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
// Batch for sending
|
||||||
// Record for the peer that it was sent
|
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
||||||
m_sockets[peer_id]->m_menu_cursor = m_menu_cursor;
|
// Record for the peer that it was sent
|
||||||
}
|
m_sockets[peer_id]->m_menu_cursor = m_menu_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
// Send game events
|
// Send game events
|
||||||
// Loop through each event that needs to be sent
|
// Loop through each event that needs to be sent
|
||||||
// send all the events starting at their cursor
|
// send all the events starting at their cursor
|
||||||
// If the client's cursor is beyond the end of the event buffer, then
|
// If the client's cursor is beyond the end of the event buffer, then
|
||||||
// it's probably left over from an old game. (Or is invalid anyway)
|
// it's probably left over from an old game. (Or is invalid anyway)
|
||||||
// So reset it back to 0
|
// So reset it back to 0
|
||||||
if (m_sockets[peer_id]->m_cursor > m_event_buffer.size())
|
if (m_sockets[peer_id]->m_cursor > m_event_buffer.size())
|
||||||
{
|
{
|
||||||
m_sockets[peer_id]->m_cursor = 0;
|
m_sockets[peer_id]->m_cursor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u64 i = m_sockets[peer_id]->m_cursor; i < m_event_buffer.size(); i++)
|
for (u64 i = m_sockets[peer_id]->m_cursor; i < m_event_buffer.size(); i++)
|
||||||
{
|
{
|
||||||
ENetPacket *packet =
|
ENetPacket* packet = enet_packet_create(m_event_buffer[i].data(), m_event_buffer[i].size(),
|
||||||
enet_packet_create(m_event_buffer[i].data(), m_event_buffer[i].size(), ENET_PACKET_FLAG_RELIABLE);
|
ENET_PACKET_FLAG_RELIABLE);
|
||||||
// Batch for sending
|
// Batch for sending
|
||||||
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
||||||
m_sockets[peer_id]->m_cursor++;
|
m_sockets[peer_id]->m_cursor++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED FROM SERVER THREAD
|
// CALLED FROM SERVER THREAD
|
||||||
void SlippiSpectateServer::popEvents()
|
void SlippiSpectateServer::popEvents()
|
||||||
{
|
{
|
||||||
// Loop through the event queue and keep popping off events and handling them
|
// Loop through the event queue and keep popping off events and handling them
|
||||||
while (!m_event_queue.Empty())
|
while (!m_event_queue.Empty())
|
||||||
{
|
{
|
||||||
std::string event;
|
std::string event;
|
||||||
m_event_queue.Pop(event);
|
m_event_queue.Pop(event);
|
||||||
// These two are meta-events, used to signify the start/end of a game
|
// These two are meta-events, used to signify the start/end of a game
|
||||||
// They are not sent over the wire
|
// They are not sent over the wire
|
||||||
if (event == "END_GAME")
|
if (event == "END_GAME")
|
||||||
{
|
{
|
||||||
m_menu_cursor = 0;
|
m_menu_cursor = 0;
|
||||||
if (m_event_buffer.size() > 0)
|
if (m_event_buffer.size() > 0)
|
||||||
{
|
{
|
||||||
m_cursor_offset += m_event_buffer.size();
|
m_cursor_offset += m_event_buffer.size();
|
||||||
}
|
}
|
||||||
m_menu_event.clear();
|
m_menu_event.clear();
|
||||||
m_in_game = false;
|
m_in_game = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (event == "START_GAME")
|
if (event == "START_GAME")
|
||||||
{
|
{
|
||||||
m_event_buffer.clear();
|
m_event_buffer.clear();
|
||||||
m_in_game = true;
|
m_in_game = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make json wrapper for game event
|
// Make json wrapper for game event
|
||||||
json game_event;
|
json game_event;
|
||||||
|
|
||||||
// An SLP event with an empty payload is a quasi-event that signifies
|
// An SLP event with an empty payload is a quasi-event that signifies
|
||||||
// the unclean exit of a game. Send this out as its own event
|
// the unclean exit of a game. Send this out as its own event
|
||||||
// (Since you can't meaningfully concat it with other events)
|
// (Since you can't meaningfully concat it with other events)
|
||||||
if (event.empty())
|
if (event.empty())
|
||||||
{
|
{
|
||||||
game_event["payload"] = "";
|
game_event["payload"] = "";
|
||||||
game_event["type"] = "game_event";
|
game_event["type"] = "game_event";
|
||||||
m_event_buffer.push_back(game_event.dump());
|
m_event_buffer.push_back(game_event.dump());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_in_game)
|
if (!m_in_game)
|
||||||
{
|
{
|
||||||
game_event["payload"] = base64::Base64::Encode(event);
|
game_event["payload"] = base64::Base64::Encode(event);
|
||||||
m_menu_cursor += 1;
|
m_menu_cursor += 1;
|
||||||
game_event["type"] = "menu_event";
|
game_event["type"] = "menu_event";
|
||||||
m_menu_event = game_event.dump();
|
m_menu_event = game_event.dump();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 command = (u8)event[0];
|
u8 command = (u8)event[0];
|
||||||
m_event_concat = m_event_concat + event;
|
m_event_concat = m_event_concat + event;
|
||||||
|
|
||||||
static std::unordered_map<u8, bool> sendEvents = {
|
static std::unordered_map<u8, bool> sendEvents = {
|
||||||
{0x36, true}, // GAME_INIT
|
{0x36, true}, // GAME_INIT
|
||||||
{0x3C, true}, // FRAME_END
|
{0x3C, true}, // FRAME_END
|
||||||
{0x39, true}, // GAME_END
|
{0x39, true}, // GAME_END
|
||||||
{0x10, true}, // SPLIT_MESSAGE
|
{0x10, true}, // SPLIT_MESSAGE
|
||||||
};
|
};
|
||||||
|
|
||||||
if (sendEvents.count(command))
|
if (sendEvents.count(command))
|
||||||
{
|
{
|
||||||
u32 cursor = (u32)(m_event_buffer.size() + m_cursor_offset);
|
u32 cursor = (u32)(m_event_buffer.size() + m_cursor_offset);
|
||||||
game_event["payload"] = base64::Base64::Encode(m_event_concat);
|
game_event["payload"] = base64::Base64::Encode(m_event_concat);
|
||||||
game_event["type"] = "game_event";
|
game_event["type"] = "game_event";
|
||||||
game_event["cursor"] = cursor;
|
game_event["cursor"] = cursor;
|
||||||
game_event["next_cursor"] = cursor + 1;
|
game_event["next_cursor"] = cursor + 1;
|
||||||
m_event_buffer.push_back(game_event.dump());
|
m_event_buffer.push_back(game_event.dump());
|
||||||
|
|
||||||
m_event_concat = "";
|
m_event_concat = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED ONCE EVER, DOLPHIN MAIN THREAD
|
// CALLED ONCE EVER, DOLPHIN MAIN THREAD
|
||||||
SlippiSpectateServer::SlippiSpectateServer()
|
SlippiSpectateServer::SlippiSpectateServer()
|
||||||
{
|
{
|
||||||
if (isSpectatorEnabled())
|
if (isSpectatorEnabled())
|
||||||
{
|
{
|
||||||
m_in_game = false;
|
m_in_game = false;
|
||||||
m_menu_cursor = 0;
|
m_menu_cursor = 0;
|
||||||
|
@ -170,196 +172,197 @@ SlippiSpectateServer::SlippiSpectateServer()
|
||||||
// Spawn thread for socket listener
|
// Spawn thread for socket listener
|
||||||
m_stop_socket_thread = false;
|
m_stop_socket_thread = false;
|
||||||
m_socketThread = std::thread(&SlippiSpectateServer::SlippicommSocketThread, this);
|
m_socketThread = std::thread(&SlippiSpectateServer::SlippicommSocketThread, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED FROM DOLPHIN MAIN THREAD
|
// CALLED FROM DOLPHIN MAIN THREAD
|
||||||
SlippiSpectateServer::~SlippiSpectateServer()
|
SlippiSpectateServer::~SlippiSpectateServer()
|
||||||
{
|
{
|
||||||
// The socket thread will be blocked waiting for input
|
// The socket thread will be blocked waiting for input
|
||||||
// So to wake it up, let's connect to the socket!
|
// So to wake it up, let's connect to the socket!
|
||||||
m_stop_socket_thread = true;
|
m_stop_socket_thread = true;
|
||||||
if (m_socketThread.joinable())
|
if (m_socketThread.joinable())
|
||||||
{
|
{
|
||||||
m_socketThread.join();
|
m_socketThread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CALLED FROM SERVER THREAD
|
// CALLED FROM SERVER THREAD
|
||||||
void SlippiSpectateServer::handleMessage(u8 *buffer, u32 length, u16 peer_id)
|
void SlippiSpectateServer::handleMessage(u8* buffer, u32 length, u16 peer_id)
|
||||||
{
|
{
|
||||||
// Unpack the message
|
// Unpack the message
|
||||||
std::string message((char *)buffer, length);
|
std::string message((char*)buffer, length);
|
||||||
json json_message = json::parse(message);
|
json json_message = json::parse(message);
|
||||||
if (!json_message.is_discarded() && (json_message.find("type") != json_message.end()))
|
if (!json_message.is_discarded() && (json_message.find("type") != json_message.end()))
|
||||||
{
|
{
|
||||||
// Check what type of message this is
|
// Check what type of message this is
|
||||||
if (!json_message["type"].is_string())
|
if (!json_message["type"].is_string())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json_message["type"] == "connect_request")
|
if (json_message["type"] == "connect_request")
|
||||||
{
|
{
|
||||||
// Get the requested cursor
|
// Get the requested cursor
|
||||||
if (json_message.find("cursor") == json_message.end())
|
if (json_message.find("cursor") == json_message.end())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!json_message["cursor"].is_number_integer())
|
if (!json_message["cursor"].is_number_integer())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
u32 requested_cursor = json_message["cursor"];
|
u32 requested_cursor = json_message["cursor"];
|
||||||
u32 sent_cursor = 0;
|
u32 sent_cursor = 0;
|
||||||
// Set the user's cursor position
|
// Set the user's cursor position
|
||||||
if (requested_cursor >= m_cursor_offset)
|
if (requested_cursor >= m_cursor_offset)
|
||||||
{
|
{
|
||||||
// If the requested cursor is past what events we even have, then just tell them to start over
|
// If the requested cursor is past what events we even have, then just tell them to start
|
||||||
if (requested_cursor > m_event_buffer.size() + m_cursor_offset)
|
// over
|
||||||
{
|
if (requested_cursor > m_event_buffer.size() + m_cursor_offset)
|
||||||
m_sockets[peer_id]->m_cursor = 0;
|
{
|
||||||
}
|
m_sockets[peer_id]->m_cursor = 0;
|
||||||
// Requested cursor is in the middle of a live match, events that we have
|
}
|
||||||
else
|
// Requested cursor is in the middle of a live match, events that we have
|
||||||
{
|
else
|
||||||
m_sockets[peer_id]->m_cursor = requested_cursor - m_cursor_offset;
|
{
|
||||||
}
|
m_sockets[peer_id]->m_cursor = requested_cursor - m_cursor_offset;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
// The client requested a cursor that was too low. Bring them up to the present
|
{
|
||||||
m_sockets[peer_id]->m_cursor = 0;
|
// The client requested a cursor that was too low. Bring them up to the present
|
||||||
}
|
m_sockets[peer_id]->m_cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
sent_cursor = (u32)m_sockets[peer_id]->m_cursor + (u32)m_cursor_offset;
|
sent_cursor = (u32)m_sockets[peer_id]->m_cursor + (u32)m_cursor_offset;
|
||||||
|
|
||||||
// If someone joins while at the menu, don't catch them up
|
// If someone joins while at the menu, don't catch them up
|
||||||
// set their cursor to the end
|
// set their cursor to the end
|
||||||
if (!m_in_game)
|
if (!m_in_game)
|
||||||
{
|
{
|
||||||
m_sockets[peer_id]->m_cursor = m_event_buffer.size();
|
m_sockets[peer_id]->m_cursor = m_event_buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
json reply;
|
json reply;
|
||||||
reply["type"] = "connect_reply";
|
reply["type"] = "connect_reply";
|
||||||
reply["nick"] = "Slippi Online";
|
reply["nick"] = "Slippi Online";
|
||||||
reply["version"] = Common::scm_slippi_semver_str;
|
reply["version"] = Common::scm_slippi_semver_str;
|
||||||
reply["cursor"] = sent_cursor;
|
reply["cursor"] = sent_cursor;
|
||||||
|
|
||||||
std::string packet_buffer = reply.dump();
|
std::string packet_buffer = reply.dump();
|
||||||
|
|
||||||
ENetPacket *packet =
|
ENetPacket* packet = enet_packet_create(packet_buffer.data(), (u32)packet_buffer.length(),
|
||||||
enet_packet_create(packet_buffer.data(), (u32)packet_buffer.length(), ENET_PACKET_FLAG_RELIABLE);
|
ENET_PACKET_FLAG_RELIABLE);
|
||||||
|
|
||||||
// Batch for sending
|
// Batch for sending
|
||||||
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
enet_peer_send(m_sockets[peer_id]->m_peer, 0, packet);
|
||||||
// Put the client in the right in_game state
|
// Put the client in the right in_game state
|
||||||
m_sockets[peer_id]->m_shook_hands = true;
|
m_sockets[peer_id]->m_shook_hands = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlippiSpectateServer::SlippicommSocketThread(void)
|
void SlippiSpectateServer::SlippicommSocketThread(void)
|
||||||
{
|
{
|
||||||
if (enet_initialize() != 0)
|
if (enet_initialize() != 0)
|
||||||
{
|
{
|
||||||
WARN_LOG(SLIPPI, "An error occurred while initializing spectator server.");
|
WARN_LOG(SLIPPI, "An error occurred while initializing spectator server.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ENetAddress server_address = {0};
|
ENetAddress server_address = {0};
|
||||||
server_address.host = ENET_HOST_ANY;
|
server_address.host = ENET_HOST_ANY;
|
||||||
server_address.port = SConfig::GetInstance().m_spectator_local_port;
|
server_address.port = SConfig::GetInstance().m_spectator_local_port;
|
||||||
|
|
||||||
// Create the spectator server
|
// Create the spectator server
|
||||||
// This call can fail if the system is already listening on the specified port
|
// This call can fail if the system is already listening on the specified port
|
||||||
// or for some period of time after it closes down. You basically have to just
|
// or for some period of time after it closes down. You basically have to just
|
||||||
// retry until the OS lets go of the port and we can claim it again
|
// retry until the OS lets go of the port and we can claim it again
|
||||||
// This typically only takes a few seconds
|
// This typically only takes a few seconds
|
||||||
ENetHost *server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
|
ENetHost* server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
while (server == nullptr && tries < 20)
|
while (server == nullptr && tries < 20)
|
||||||
{
|
{
|
||||||
server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
|
server = enet_host_create(&server_address, MAX_CLIENTS, 2, 0, 0);
|
||||||
tries += 1;
|
tries += 1;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server == nullptr)
|
if (server == nullptr)
|
||||||
{
|
{
|
||||||
WARN_LOG(SLIPPI, "Could not create spectator server");
|
WARN_LOG(SLIPPI, "Could not create spectator server");
|
||||||
enet_deinitialize();
|
enet_deinitialize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main slippicomm server loop
|
// Main slippicomm server loop
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
// If we're told to stop, then quit
|
// If we're told to stop, then quit
|
||||||
if (m_stop_socket_thread)
|
if (m_stop_socket_thread)
|
||||||
{
|
{
|
||||||
enet_host_destroy(server);
|
enet_host_destroy(server);
|
||||||
enet_deinitialize();
|
enet_deinitialize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop off any events in the queue
|
// Pop off any events in the queue
|
||||||
popEvents();
|
popEvents();
|
||||||
|
|
||||||
std::map<u16, std::shared_ptr<SlippiSocket>>::iterator it = m_sockets.begin();
|
std::map<u16, std::shared_ptr<SlippiSocket>>::iterator it = m_sockets.begin();
|
||||||
for (; it != m_sockets.end(); it++)
|
for (; it != m_sockets.end(); it++)
|
||||||
{
|
{
|
||||||
if (it->second->m_shook_hands)
|
if (it->second->m_shook_hands)
|
||||||
{
|
{
|
||||||
writeEvents(it->first);
|
writeEvents(it->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ENetEvent event;
|
ENetEvent event;
|
||||||
while (enet_host_service(server, &event, 1) > 0)
|
while (enet_host_service(server, &event, 1) > 0)
|
||||||
{
|
{
|
||||||
switch (event.type)
|
switch (event.type)
|
||||||
{
|
{
|
||||||
case ENET_EVENT_TYPE_CONNECT:
|
case ENET_EVENT_TYPE_CONNECT:
|
||||||
{
|
{
|
||||||
|
INFO_LOG(SLIPPI, "A new spectator connected from %x:%u.\n", event.peer->address.host,
|
||||||
|
event.peer->address.port);
|
||||||
|
|
||||||
INFO_LOG(SLIPPI, "A new spectator connected from %x:%u.\n", event.peer->address.host,
|
std::shared_ptr<SlippiSocket> newSlippiSocket(new SlippiSocket());
|
||||||
event.peer->address.port);
|
newSlippiSocket->m_peer = event.peer;
|
||||||
|
m_sockets[event.peer->incomingPeerID] = newSlippiSocket;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ENET_EVENT_TYPE_RECEIVE:
|
||||||
|
{
|
||||||
|
handleMessage(event.packet->data, (u32)event.packet->dataLength,
|
||||||
|
event.peer->incomingPeerID);
|
||||||
|
/* Clean up the packet now that we're done using it. */
|
||||||
|
enet_packet_destroy(event.packet);
|
||||||
|
|
||||||
std::shared_ptr<SlippiSocket> newSlippiSocket(new SlippiSocket());
|
break;
|
||||||
newSlippiSocket->m_peer = event.peer;
|
}
|
||||||
m_sockets[event.peer->incomingPeerID] = newSlippiSocket;
|
case ENET_EVENT_TYPE_DISCONNECT:
|
||||||
break;
|
{
|
||||||
}
|
INFO_LOG(SLIPPI, "A spectator disconnected from %x:%u.\n", event.peer->address.host,
|
||||||
case ENET_EVENT_TYPE_RECEIVE:
|
event.peer->address.port);
|
||||||
{
|
|
||||||
handleMessage(event.packet->data, (u32)event.packet->dataLength, event.peer->incomingPeerID);
|
|
||||||
/* Clean up the packet now that we're done using it. */
|
|
||||||
enet_packet_destroy(event.packet);
|
|
||||||
|
|
||||||
break;
|
// Delete the item in the m_sockets map
|
||||||
}
|
m_sockets.erase(event.peer->incomingPeerID);
|
||||||
case ENET_EVENT_TYPE_DISCONNECT:
|
/* Reset the peer's client information. */
|
||||||
{
|
event.peer->data = NULL;
|
||||||
INFO_LOG(SLIPPI, "A spectator disconnected from %x:%u.\n", event.peer->address.host,
|
break;
|
||||||
event.peer->address.port);
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
INFO_LOG(SLIPPI, "Spectator sent an unknown ENet event type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the item in the m_sockets map
|
enet_host_destroy(server);
|
||||||
m_sockets.erase(event.peer->incomingPeerID);
|
enet_deinitialize();
|
||||||
/* Reset the peer's client information. */
|
|
||||||
event.peer->data = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
INFO_LOG(SLIPPI, "Spectator sent an unknown ENet event type");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enet_host_destroy(server);
|
|
||||||
enet_deinitialize();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include <enet/enet.h>
|
||||||
#include "Common/SPSCQueue.h"
|
#include "Common/SPSCQueue.h"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include <enet/enet.h>
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// Sockets in windows are unsigned
|
// Sockets in windows are unsigned
|
||||||
|
@ -30,10 +30,10 @@ typedef int SOCKET;
|
||||||
class SlippiSocket
|
class SlippiSocket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
u64 m_cursor = 0; // Index of the last game event this client sent
|
u64 m_cursor = 0; // Index of the last game event this client sent
|
||||||
u64 m_menu_cursor = 0; // The latest menu event that this socket has sent
|
u64 m_menu_cursor = 0; // The latest menu event that this socket has sent
|
||||||
bool m_shook_hands = false; // Has this client shaken hands yet?
|
bool m_shook_hands = false; // Has this client shaken hands yet?
|
||||||
ENetPeer* m_peer = NULL; // The ENet peer object for the socket
|
ENetPeer* m_peer = NULL; // The ENet peer object for the socket
|
||||||
};
|
};
|
||||||
|
|
||||||
class SlippiSpectateServer
|
class SlippiSpectateServer
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue