pull in enormous ranked commit e7adf9f79ef2c3d60066bbb36d672da841560dab. add missing data/sys files from current head, f68f6f6cbf56b3e4e43f27475763f1d15b6d7c67

This commit is contained in:
R2DLiu 2023-06-22 05:35:57 -04:00
commit 69b72a86de
24 changed files with 735 additions and 163 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -19,8 +19,8 @@
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
#include <unordered_map>
#include <vector>
#include <fmt/format.h>
@ -441,17 +441,20 @@ void StringPopBackIf(std::string* s, char c)
// SLIPPITODO: look into boost locale maybe
void ConvertNarrowSpecialSHIFTJIS(std::string& input)
{
// Melee doesn't correctly display special characters in narrow form We need to convert them to wide form.
// I couldn't find a library to do this so for now let's just do it manually
// Melee doesn't correctly display special characters in narrow form We need to convert them to
// wide form. I couldn't find a library to do this so for now let's just do it manually
static std::unordered_map<char, char16_t> specialCharConvert = {
{'!', (char16_t)0x8149}, {'"', (char16_t)0x8168}, {'#', (char16_t)0x8194}, {'$', (char16_t)0x8190},
{'%', (char16_t)0x8193}, {'&', (char16_t)0x8195}, {'\'', (char16_t)0x8166}, {'(', (char16_t)0x8169},
{')', (char16_t)0x816a}, {'*', (char16_t)0x8196}, {'+', (char16_t)0x817b}, {',', (char16_t)0x8143},
{'-', (char16_t)0x817c}, {'.', (char16_t)0x8144}, {'/', (char16_t)0x815e}, {':', (char16_t)0x8146},
{';', (char16_t)0x8147}, {'<', (char16_t)0x8183}, {'=', (char16_t)0x8181}, {'>', (char16_t)0x8184},
{'?', (char16_t)0x8148}, {'@', (char16_t)0x8197}, {'[', (char16_t)0x816d}, {'\\', (char16_t)0x815f},
{']', (char16_t)0x816e}, {'^', (char16_t)0x814f}, {'_', (char16_t)0x8151}, {'`', (char16_t)0x814d},
{'{', (char16_t)0x816f}, {'|', (char16_t)0x8162}, {'}', (char16_t)0x8170}, {'~', (char16_t)0x8160},
{'!', (char16_t)0x8149}, {'"', (char16_t)0x8168}, {'#', (char16_t)0x8194},
{'$', (char16_t)0x8190}, {'%', (char16_t)0x8193}, {'&', (char16_t)0x8195},
{'\'', (char16_t)0x8166}, {'(', (char16_t)0x8169}, {')', (char16_t)0x816a},
{'*', (char16_t)0x8196}, {'+', (char16_t)0x817b}, {',', (char16_t)0x8143},
{'-', (char16_t)0x817c}, {'.', (char16_t)0x8144}, {'/', (char16_t)0x815e},
{':', (char16_t)0x8146}, {';', (char16_t)0x8147}, {'<', (char16_t)0x8183},
{'=', (char16_t)0x8181}, {'>', (char16_t)0x8184}, {'?', (char16_t)0x8148},
{'@', (char16_t)0x8197}, {'[', (char16_t)0x816d}, {'\\', (char16_t)0x815f},
{']', (char16_t)0x816e}, {'^', (char16_t)0x814f}, {'_', (char16_t)0x8151},
{'`', (char16_t)0x814d}, {'{', (char16_t)0x816f}, {'|', (char16_t)0x8162},
{'}', (char16_t)0x8170}, {'~', (char16_t)0x8160},
};
int pos = 0;
@ -476,13 +479,13 @@ void ConvertNarrowSpecialSHIFTJIS(std::string& input)
input.erase(pos, 1);
// Add new chars to pos to replace
auto newChars = (char*)& specialCharConvert[c];
auto newChars = (char*)&specialCharConvert[c];
input.insert(input.begin() + pos, 1, newChars[0]);
input.insert(input.begin() + pos, 1, newChars[1]);
}
}
std::string ConvertStringForGame(const std::string& input, int length)
std::string TruncateLengthChar(const std::string& input, int length)
{
auto utf32 = UTF8ToUTF32(input);
@ -492,7 +495,12 @@ std::string ConvertStringForGame(const std::string& input, int length)
utf32.resize(length);
}
auto utf8 = UTF32toUTF8(utf32);
return UTF32toUTF8(utf32);
}
std::string ConvertStringForGame(const std::string& input, int length)
{
auto utf8 = TruncateLengthChar(input, length);
auto shiftJis = UTF8ToSHIFTJIS(utf8);
ConvertNarrowSpecialSHIFTJIS(shiftJis);

View file

@ -178,6 +178,7 @@ std::u32string UTF8ToUTF32(const std::string& input);
std::u32string UTF8ToUTF32(const std::string& input);
#endif
std::string UTF32toUTF8(const std::u32string& input);
std::string TruncateLengthChar(const std::string& input, int length);
std::string ConvertStringForGame(const std::string& input, int length);
std::string CP1252ToUTF8(std::string_view str);
std::string SHIFTJISToUTF8(std::string_view str);

View file

@ -175,12 +175,21 @@ const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel)
const Info<bool>& GetInfoForAdapterRumble(int channel)
{
#ifndef IS_PLAYBACK
static const std::array<const Info<bool>, 4> infos{
Info<bool>{{System::Main, "Core", "AdapterRumble0"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble1"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble2"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble3"}, true},
};
#else
static const std::array<const Info<bool>, 4> infos{
Info<bool>{{System::Main, "Core", "AdapterRumble0"}, false},
Info<bool>{{System::Main, "Core", "AdapterRumble1"}, false},
Info<bool>{{System::Main, "Core", "AdapterRumble2"}, false},
Info<bool>{{System::Main, "Core", "AdapterRumble3"}, false},
};
#endif
return infos[channel];
}

View file

@ -170,6 +170,13 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
if (SConfig::GetSlippiConfig().melee_version == Melee::Version::NTSC ||
SConfig::GetSlippiConfig().melee_version == Melee::Version::MEX)
{
// Here we are replacing a line in the codehandler with a blr.
// The reason for this is that this is the section of the codehandler
// that attempts to read/write commands for the USB Gecko. These calls
// were sometimes interfering with the Slippi EXI calls and causing
// the game to loop infinitely in EXISync.
PowerPC::HostWrite_U32(guard, 0x4E800020, 0x80001D6C);
// Write GCT loader into memory which will eventually load the real GCT into the heap
std::string bootloaderData;
std::string _bootloaderFilename = File::GetSysDirectory() + GCT_BOOTLOADER;
@ -218,7 +225,7 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
"not write: \"{}\". Need {} bytes, only {} remain.",
active_code.name, active_code.codes.size() * CODE_SIZE,
end_address - next_address);
continue;
return Installation::Failed;
}
for (const GeckoCode::Code& code : active_code.codes)

View file

@ -255,6 +255,17 @@ CEXISlippi::~CEXISlippi()
SlippiSpectateServer::getInstance().endGame(true);
// Try to determine whether we were playing an in-progress ranked match, if so
// indicate to server that this client has abandoned. Anyone trying to modify
// this behavior to game their rating is subject to get banned.
auto active_match_id = matchmaking->GetMatchmakeResult().id;
if (activeMatchId.find("mode.ranked") != std::string::npos)
{
ERROR_LOG(SLIPPI_ONLINE, "Exit during in-progress ranked game: %s", activeMatchId.c_str());
gameReporter->ReportAbandonment(activeMatchId);
}
handleConnectionCleanup();
localSelections.Reset();
g_playbackStatus->resetPlayback();
@ -1538,7 +1549,10 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
m_read_queue.clear();
s32 frame = Common::swap32(&payload[0]);
s32 finalizedFrame = Common::swap32(&payload[4]);
s32 finalized_frame = Common::swap32(&payload[4]);
u32 finalized_frame_checksum = Common::swap32(&payload[8]);
u8 delay = payload[12];
u8* inputs = &payload[13];
if (frame == 1)
{
@ -1565,7 +1579,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
fallBehindCounter = 0;
fallFarBehindCounter = 0;
// Reset character selections as they are no longer needed
// Reset character selections such that they are cleared for next game
localSelections.Reset();
if (slippi_netplay)
slippi_netplay->StartSlippiGame();
@ -1578,9 +1592,9 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
}
// Drop inputs that we no longer need (inputs older than the finalized frame passed in)
slippi_netplay->DropOldRemoteInputs(finalizedFrame);
slippi_netplay->DropOldRemoteInputs(finalized_frame);
bool shouldSkip = shouldSkipOnlineFrame(frame, finalizedFrame);
bool shouldSkip = shouldSkipOnlineFrame(frame, finalized_frame);
if (shouldSkip)
{
// Send inputs that have not yet been acked
@ -1589,7 +1603,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
else
{
// Send the input for this frame along with everything that has yet to be acked
handleSendInputs(payload);
handleSendInputs(frame, delay, finalized_frame, finalized_frame_checksum, inputs);
}
prepareOpponentInputs(frame, shouldSkip);
@ -1764,8 +1778,9 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
// don't understand atm.
OSD::AddTypedMessage(
OSD::MessageType::PerformanceWarning,
"Your computer is running slow and is impacting the performance of the match.", 10000,
OSD::Color::RED);
"Possible poor match performance detected.\nIf this message appears with most opponents, "
"your computer or network is likely impacting match performance for the other players.",
10000, OSD::Color::RED);
}
if (offsetUs < -t2 && !isCurrentlyAdvancing)
@ -1800,14 +1815,11 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
return false;
}
void CEXISlippi::handleSendInputs(u8* payload)
void CEXISlippi::handleSendInputs(s32 frame, u8 delay, s32 checksum_frame, u32 checksum, u8* inputs)
{
if (isConnectionStalled)
return;
s32 frame = Common::swap32(&payload[0]);
u8 delay = payload[8];
// On the first frame sent, we need to queue up empty dummy pads for as many
// frames as we have delay
if (frame == 1)
@ -1819,7 +1831,7 @@ void CEXISlippi::handleSendInputs(u8* payload)
}
}
auto pad = std::make_unique<SlippiPad>(frame + delay, &payload[9]);
auto pad = std::make_unique<SlippiPad>(frame + delay, checksum_frame, checksum, inputs);
slippi_netplay->SendSlippiPad(std::move(pad));
}
@ -1854,6 +1866,24 @@ void CEXISlippi::prepareOpponentInputs(s32 frame, bool shouldSkip)
m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX];
for (int i = 0; i < remotePlayerCount; i++)
{
results[i] = slippi_netplay->GetSlippiRemotePad(i, ROLLBACK_MAX_FRAMES);
// results[i] = slippi_netplay->GetFakePadOutput(frame);
// INFO_LOG(SLIPPI_ONLINE, "Sending checksum values: [%d] %08x", results[i]->checksumFrame,
// results[i]->checksum);
appendWordToBuffer(&m_read_queue, static_cast<u32>(results[i]->checksumFrame));
appendWordToBuffer(&m_read_queue, results[i]->checksum);
}
for (int i = remotePlayerCount; i < SLIPPI_REMOTE_PLAYER_MAX; i++)
{
// Send dummy data for unused players
appendWordToBuffer(&m_read_queue, 0);
appendWordToBuffer(&m_read_queue, 0);
}
int offset[SLIPPI_REMOTE_PLAYER_MAX];
int32_t latestFrameRead[SLIPPI_REMOTE_PLAYER_MAX]{};
@ -1893,7 +1923,7 @@ void CEXISlippi::prepareOpponentInputs(s32 frame, bool shouldSkip)
std::vector<u8> tx;
// Get pad data if this remote player exists
if (i < remotePlayerCount)
if (i < remotePlayerCount && offset[i] < results[i]->data.size())
{
auto txStart = results[i]->data.begin() + offset[i];
auto txEnd = results[i]->data.end();
@ -2185,12 +2215,71 @@ void CEXISlippi::handleNameEntryLoad(u8* payload)
appendWordToBuffer(&m_read_queue, curIndex);
}
// teamId 0 = red, 1 = blue, 2 = green
int CEXISlippi::getCharColor(u8 charId, u8 teamId)
{
switch (charId)
{
case 0x0: // Falcon
switch (teamId)
{
case 0:
return 2;
case 1:
return 5;
case 2:
return 4;
}
case 0x2: // Fox
switch (teamId)
{
case 0:
return 1;
case 1:
return 2;
case 2:
return 3;
}
case 0xC: // Peach
switch (teamId)
{
case 0:
return 0;
case 1:
return 3;
case 2:
return 4;
}
case 0x13: // Sheik
switch (teamId)
{
case 0:
return 1;
case 1:
return 2;
case 2:
return 3;
}
case 0x14: // Falco
switch (teamId)
{
case 0:
return 1;
case 1:
return 2;
case 2:
return 3;
}
}
return 0;
}
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,
0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x01, 0x01, 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,
@ -2247,10 +2336,11 @@ void CEXISlippi::prepareOnlineMatchState()
slippi_netplay = matchmaking->GetNetplayClient();
#endif
// This happens on the initial connection to a player. Let's now grab the allowed stages
// returned to us from the matchmaking service and pick a new random stage before sending
// the selections to the opponent
allowedStages = matchmaking->GetStages();
// This happens on the initial connection to a player. The matchmaking object is ephemeral, it
// gets re-created when a connection is terminated, that said, it can still be useful to know
// who we were connected to after they disconnect from us, for example in the case of
// reporting a match. So let's copy the results.
allowedStages = recentMmResult.stages;
// Clear stage pool so that when we call getRandomStage it will use full list
stagePool.clear();
localSelections.stageId = getRandomStage();
@ -2305,16 +2395,7 @@ void CEXISlippi::prepareOnlineMatchState()
// Here we are connected, check to see if we should init play session
if (!is_play_session_active)
{
std::vector<std::string> uids;
auto mmPlayers = matchmaking->GetPlayerInfo();
for (auto mmp : mmPlayers)
{
uids.push_back(mmp.uid);
}
game_reporter->StartNewSession(uids);
game_reporter->StartNewSession();
is_play_session_active = true;
}
}
@ -2415,10 +2496,7 @@ void CEXISlippi::prepareOnlineMatchState()
rps[i].isCharacterSelected = true;
}
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS)
{
remotePlayerCount = 3;
}
remotePlayerCount = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS ? 3 : 1;
oppName = std::string("Player");
#endif
@ -2433,25 +2511,39 @@ void CEXISlippi::prepareOnlineMatchState()
remoteCharOk = false;
}
// TODO: This is annoying, ideally remotePlayerSelections would just include everyone including
// TODO: Ideally remotePlayerSelections would just include everyone including the local player
// the local player
// TODO: Would also simplify some logic in the Netplay class
std::vector<SlippiPlayerSelections> orderedSelections(4);
orderedSelections[lps.playerIdx] = lps;
// Here we are storing pointers to the player selections. That means that we can technically
// modify the values from here, which is probably not the cleanest thing since they're coming
// from the netplay class. Unfortunately, I think it might be required for the overwrite stuff
// to work correctly though, maybe on a tiebreak in ranked?
std::vector<SlippiPlayerSelections*> orderedSelections(remotePlayerCount + 1);
orderedSelections[lps.playerIdx] = &lps;
for (int i = 0; i < remotePlayerCount; i++)
{
orderedSelections[rps[i].playerIdx] = rps[i];
orderedSelections[rps[i].playerIdx] = &rps[i];
}
// Overwrite selections
for (int i = 0; i < overwrite_selections.size(); i++)
{
const auto& ow = overwrite_selections[i];
orderedSelections[i]->characterId = ow.characterId;
orderedSelections[i]->characterColor = ow.characterColor;
orderedSelections[i]->stageId = ow.stageId;
}
// Overwrite stage information. Make sure everyone loads the same stage
u16 stageId = 0x1F; // Default to battlefield if there was no selection
for (const auto& selections : orderedSelections)
{
if (!selections.isStageSelected)
if (!selections->isStageSelected)
continue;
// Stage selected by this player, use that selection
stageId = selections.stageId;
stageId = selections->stageId;
break;
}
@ -2505,11 +2597,11 @@ void CEXISlippi::prepareOnlineMatchState()
INFO_LOG_FMT(SLIPPI_ONLINE, "Rng Offset: {:#x}", rngOffset);
// Check if everyone is the same color
auto color = orderedSelections[0].teamId;
auto color = orderedSelections[0]->teamId;
bool areAllSameTeam = true;
for (const auto& s : orderedSelections)
{
if (s.teamId != color)
if (s->teamId != color)
areAllSameTeam = false;
}
@ -2526,21 +2618,21 @@ void CEXISlippi::prepareOnlineMatchState()
// Overwrite player character choices
for (auto& s : orderedSelections)
{
if (!s.isCharacterSelected)
if (!s->isCharacterSelected)
{
continue;
}
if (areAllSameTeam)
{
// Overwrite teamId. Color is overwritten by ASM
s.teamId = teamAssignments[s.playerIdx];
s->teamId = teamAssignments[s->playerIdx];
}
// Overwrite player character
onlineMatchBlock[0x60 + (s.playerIdx) * 0x24] = s.characterId;
onlineMatchBlock[0x63 + (s.playerIdx) * 0x24] = s.characterColor;
onlineMatchBlock[0x67 + (s.playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (s.playerIdx) * 0x24] = s.teamId;
onlineMatchBlock[0x60 + (s->playerIdx) * 0x24] = s->characterId;
onlineMatchBlock[0x63 + (s->playerIdx) * 0x24] = s->characterColor;
onlineMatchBlock[0x67 + (s->playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (s->playerIdx) * 0x24] = s->teamId;
}
// Handle Singles/Teams specific logic
@ -2573,8 +2665,7 @@ void CEXISlippi::prepareOnlineMatchState()
*stage = Common::swap16(stageId);
// Turn pause off in unranked/ranked, on in other modes
auto pauseAllowed = !SlippiMatchmaking::IsFixedRulesMode(lastSearch.mode) &&
lastSearch.mode != SlippiMatchmaking::OnlinePlayMode::TEAMS;
auto pauseAllowed = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::DIRECT;
u8* gameBitField3 = static_cast<u8*>(&onlineMatchBlock[2]);
*gameBitField3 = pauseAllowed ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
//*gameBitField3 = *gameBitField3 | 0x8;
@ -2633,24 +2724,35 @@ void CEXISlippi::prepareOnlineMatchState()
}
// Create the opponent string using the names of all players on opposing teams
int teamIdx = onlineMatchBlock[0x69 + m_local_player_index * 0x24];
std::string oppText = "";
for (int i = 0; i < 4; i++)
std::vector<std::string> opponentNames = {};
if (matchmaking->RemotePlayerCount() == 1)
{
if (i == m_local_player_index)
continue;
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx)
opponentNames.push_back(matchmaking->GetPlayerName(remotePlayerIndex));
}
else
{
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
for (int i = 0; i < 4; i++)
{
if (oppText != "")
oppText += "/";
if (localPlayerIndex == i || onlineMatchBlock[0x69 + i * 0x24] == teamIdx)
continue;
oppText += matchmaking->GetPlayerName(i);
opponentNames.push_back(matchmaking->GetPlayerName(i));
}
}
if (matchmaking->RemotePlayerCount() == 1)
oppText = matchmaking->GetPlayerName(m_remote_player_index);
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1);
auto numOpponents = opponentNames.size() == 0 ? 1 : opponentNames.size();
auto charsPerName = (MAX_NAME_LENGTH - (numOpponents - 1)) / numOpponents;
std::string oppText = "";
for (auto& name : opponentNames)
{
if (oppText != "")
oppText += "/";
oppText += TruncateLengthChar(name, charsPerName);
}
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH);
m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
#ifdef LOCAL_TESTING
@ -3003,6 +3105,9 @@ void CEXISlippi::handleConnectionCleanup()
// Reset any forced errors
forcedError.clear();
// Reset any selection overwrites
overwrite_selections.clear();
// Reset play session
is_play_session_active = false;
@ -3022,29 +3127,50 @@ void CEXISlippi::prepareNewSeed()
appendWordToBuffer(&m_read_queue, newSeed);
}
void CEXISlippi::handleReportGame(u8* payload)
void CEXISlippi::handleReportGame(const SlippiExiTypes::ReportGameQuery& query)
{
#ifndef LOCAL_TESTING
SlippiGameReporter::GameReport r;
r.duration_frames = Common::swap32(&payload[0]);
r.match_id = recentMmResult.id;
r.mode = static_cast<SlippiMatchmaking::OnlinePlayMode>(query.mode);
r.duration_frames = query.frame_length;
r.game_index = query.game_index;
r.tiebreak_index = query.tiebreak_index;
r.winner_idx = query.winner_idx;
r.stage_id = Common::FromBigEndian(*(u16*)&query.game_info_block[0xE]);
r.game_end_method = query.game_end_method;
r.lras_initiator = query.lras_initiator;
// ERROR_LOG_FMT(SLIPPI_ONLINE, "Frames: {}", r.duration_frames);
ERROR_LOG_FMT(SLIPPI_ONLINE,
"Mode: {} / {}, Frames: {}, GameIdx: {}, TiebreakIdx: {}, WinnerIdx: {}, "
"StageId: {}, GameEndMethod: {}, LRASInitiator: {}",
r.mode, query.mode, r.duration_frames, r.game_index, r.tiebreak_index, r.winner_idx,
r.stage_id, r.game_end_method, r.lras_initiator);
for (auto i = 0; i < 2; ++i)
auto mm_players = recentMmResult.players;
for (auto i = 0; i < 4; ++i)
{
SlippiGameReporter::PlayerReport p;
auto offset = i * 6;
p.stocks_remaining = payload[5 + offset];
p.uid = mm_players.size() > i ? mm_players[i].uid : "";
p.slot_type = query.players[i].slot_type;
p.stocks_remaining = query.players[i].stocks_remaining;
p.damage_done = query.players[i].damage_done;
p.char_id = query.game_info_block[0x60 + 0x24 * i];
p.color_id = query.game_info_block[0x63 + 0x24 * i];
p.starting_stocks = query.game_info_block[0x62 + 0x24 * i];
p.starting_percent = Common::FromBigEndian(*(u16*)&query.game_info_block[0x70 + 0x24 * i]);
auto swappedDamageDone = Common::swap32(&payload[6 + offset]);
p.damage_done = *(float*)&swappedDamageDone;
// ERROR_LOG_FMT(SLIPPI_ONLINE, "Stocks: {}, DamageDone: %f", p.stocks_remaining,
// p.damage_done);
ERROR_LOG_FMT(SLIPPI_ONLINE,
"UID: {}, Port Type: {}, Stocks: {}, DamageDone: {}, CharId: {}, ColorId: {}, "
"StartStocks: {}, "
"StartPercent: {}",
p.uid, p.slot_type, p.stocks_remaining, p.damage_done, p.char_id, p.color_id,
p.starting_stocks, p.starting_percent);
r.players.push_back(p);
}
#ifndef LOCAL_TESTING
game_reporter->StartReport(r);
#endif
}
@ -3063,6 +3189,84 @@ void CEXISlippi::prepareDelayResponse()
m_read_queue.push_back(Config::Get(Config::SLIPPI_ONLINE_DELAY));
}
void CEXISlippi::handleOverwriteSelections(const SlippiExiTypes::OverwriteSelectionsQuery& query)
{
overwrite_selections.clear();
for (int i = 0; i < 4; i++)
{
// TODO: I'm pretty sure this contine would cause bugs if we tried to overwrite only player 1
// TODO: and not player 0. Right now though GamePrep always overwrites both p0 and p1 so it's
// fine
// TODO: The bug would likely happen in the prepareOnlineMatchState, it would overwrite the
// TODO: wrong players I think
if (!query.chars[i].is_set)
continue;
SlippiPlayerSelections s;
s.isCharacterSelected = true;
s.characterId = query.chars[i].char_id;
s.characterColor = query.chars[i].char_color_id;
s.isStageSelected = true;
s.stageId = query.stage_id;
s.playerIdx = i;
overwrite_selections.push_back(s);
}
}
void CEXISlippi::handleGamePrepStepComplete(const SlippiExiTypes::GpCompleteStepQuery& query)
{
if (!slippi_netplay)
return;
SlippiGamePrepStepResults res;
res.step_idx = query.step_idx;
res.char_selection = query.char_selection;
res.char_color_selection = query.char_color_selection;
memcpy(res.stage_selections, query.stage_selections, 2);
slippi_netplay->SendGamePrepStep(res);
}
void CEXISlippi::prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery& query)
{
SlippiExiTypes::GpFetchStepResponse resp;
m_read_queue.clear();
// Start by indicating not found
resp.is_found = false;
#ifdef LOCAL_TESTING
static int delay_count = 0;
delay_count++;
if (delay_count >= 90)
{
resp.is_found = true;
resp.is_skip = true; // Will make client just pick the next available options
delay_count = 0;
}
#else
SlippiGamePrepStepResults res;
if (slippi_netplay && slippi_netplay->GetGamePrepResults(query.step_idx, res))
{
// If we have received a response from the opponent, prepare the values for response
resp.is_found = true;
resp.is_skip = false;
resp.char_selection = res.char_selection;
resp.char_color_selection = res.char_color_selection;
memcpy(resp.stage_selections, res.stage_selections, 2);
}
#endif
auto data_ptr = (u8*)&resp;
m_read_queue.insert(m_read_queue.end(), data_ptr,
data_ptr + sizeof(SlippiExiTypes::GpFetchStepResponse));
}
void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
{
auto& system = Core::System::GetInstance();
@ -3106,13 +3310,16 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
memPtr[bufLoc], memPtr[bufLoc + 1], memPtr[bufLoc + 2], memPtr[bufLoc + 3],
memPtr[bufLoc + 4]);
u8 prevCommandByte = 0;
while (bufLoc < _uSize)
{
byte = memPtr[bufLoc];
if (!payloadSizes.count(byte))
{
// This should never happen. Do something else if it does?
WARN_LOG_FMT(EXPANSIONINTERFACE, "EXI SLIPPI: Invalid command byte: {:#x}", byte);
ERROR_LOG_FMT(SLIPPI, "EXI SLIPPI: Invalid command byte: {:#x}. Prev command: {:#x}", byte,
prevCommandByte);
return;
}
@ -3204,7 +3411,7 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
prepareNewSeed();
break;
case CMD_REPORT_GAME:
handleReportGame(&memPtr[bufLoc + 1]);
handleReportGame(SlippiExiTypes::Convert<SlippiExiTypes::ReportGameQuery>(&memPtr[bufLoc]));
break;
case CMD_GCT_LENGTH:
prepareGctLength();
@ -3215,12 +3422,25 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
case CMD_GET_DELAY:
prepareDelayResponse();
break;
case CMD_OVERWRITE_SELECTIONS:
handleOverwriteSelections(
SlippiExiTypes::Convert<SlippiExiTypes::OverwriteSelectionsQuery>(&memPtr[bufLoc]));
break;
case CMD_GP_FETCH_STEP:
prepareGamePrepOppStep(
SlippiExiTypes::Convert<SlippiExiTypes::GpFetchStepQuery>(&memPtr[bufLoc]));
break;
case CMD_GP_COMPLETE_STEP:
handleGamePrepStepComplete(
SlippiExiTypes::Convert<SlippiExiTypes::GpCompleteStepQuery>(&memPtr[bufLoc]));
break;
default:
writeToFileAsync(&memPtr[bufLoc], payloadLen + 1, "");
SlippiSpectateServer::getInstance().write(&memPtr[bufLoc], payloadLen + 1);
break;
}
prevCommandByte = byte;
bufLoc += payloadLen + 1;
}
}

View file

@ -8,6 +8,7 @@
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Core/Slippi/SlippiDirectCodes.h"
#include "Core/Slippi/SlippiExiTypes.h"
#include "Core/Slippi/SlippiGame.h"
#include "Core/Slippi/SlippiGameFileLoader.h"
#include "Core/Slippi/SlippiGameReporter.h"
@ -75,6 +76,9 @@ private:
CMD_GET_NEW_SEED = 0xBC,
CMD_REPORT_GAME = 0xBD,
CMD_FETCH_CODE_SUGGESTION = 0xBE,
CMD_OVERWRITE_SELECTIONS = 0xBF,
CMD_GP_COMPLETE_STEP = 0xC0,
CMD_GP_FETCH_STEP = 0xC1,
// Misc
CMD_LOG_MESSAGE = 0xD0,
@ -102,8 +106,8 @@ private:
{CMD_RECEIVE_COMMANDS, 1},
// The following are all commands used to play back a replay and
// have fixed sizes
{CMD_PREPARE_REPLAY, 0},
// have fixed sizes unless otherwise specified
{CMD_PREPARE_REPLAY, 0xFFFF}, // Variable size... will only work if by itself
{CMD_READ_FRAME, 4},
{CMD_IS_STOCK_STEAL, 5},
{CMD_GET_LOCATION, 6},
@ -111,7 +115,7 @@ private:
{CMD_GET_GECKO_CODES, 0},
// The following are used for Slippi online and also have fixed sizes
{CMD_ONLINE_INPUTS, 21},
{CMD_ONLINE_INPUTS, 25},
{CMD_CAPTURE_SAVESTATE, 32},
{CMD_LOAD_SAVESTATE, 32},
{CMD_GET_MATCH_STATE, 0},
@ -124,8 +128,12 @@ private:
{CMD_GET_ONLINE_STATUS, 0},
{CMD_CLEANUP_CONNECTION, 0},
{CMD_GET_NEW_SEED, 0},
{CMD_REPORT_GAME, 16},
{CMD_REPORT_GAME, static_cast<u32>(sizeof(SlippiExiTypes::ReportGameQuery) - 1)},
{CMD_FETCH_CODE_SUGGESTION, 31},
{CMD_OVERWRITE_SELECTIONS,
static_cast<u32>(sizeof(SlippiExiTypes::OverwriteSelectionsQuery) - 1)},
{CMD_GP_COMPLETE_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpCompleteStepQuery) - 1)},
{CMD_GP_FETCH_STEP, static_cast<u32>(sizeof(SlippiExiTypes::GpFetchStepQuery) - 1)},
// Misc
{CMD_LOG_MESSAGE, 0xFFFF}, // Variable size... will only work if by itself
@ -173,7 +181,7 @@ private:
bool isDisconnected();
void handleOnlineInputs(u8* payload);
void prepareOpponentInputs(s32 frame, bool shouldSkip);
void handleSendInputs(u8* payload);
void handleSendInputs(s32 frame, u8 delay, s32 checksum_frame, u32 checksum, u8* inputs);
void handleCaptureSavestate(u8* payload);
void handleLoadSavestate(u8* payload);
void handleNameEntryLoad(u8* payload);
@ -187,7 +195,10 @@ private:
void prepareOnlineStatus();
void handleConnectionCleanup();
void prepareNewSeed();
void handleReportGame(u8* payload);
void handleReportGame(const SlippiExiTypes::ReportGameQuery& query);
void handleOverwriteSelections(const SlippiExiTypes::OverwriteSelectionsQuery& query);
void handleGamePrepStepComplete(const SlippiExiTypes::GpCompleteStepQuery& query);
void prepareGamePrepOppStep(const SlippiExiTypes::GpFetchStepQuery& query);
// replay playback stuff
void prepareGameInfo(u8* payload);
@ -231,9 +242,13 @@ private:
std::unique_ptr<Slippi::SlippiGame> m_current_game = nullptr;
SlippiSpectateServer* m_slippiserver = nullptr;
SlippiMatchmaking::MatchSearchSettings lastSearch;
SlippiMatchmaking::MatchmakeResult recentMmResult;
std::vector<u16> stagePool;
// Used by ranked to set game prep selections
std::vector<SlippiPlayerSelections> overwrite_selections;
u32 frameSeqIdx = 0;
bool isEnetInitialized = false;

View file

@ -849,7 +849,7 @@ void VideoInterfaceManager::Update(u64 ticks)
// Try calling SI Poll every time update is called
SerialInterface::UpdateDevices();
Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT),
Config::Get(Config::MAIN_LOCK_CURSOR));
Config::Get(Config::MAIN_LOCK_CURSOR));
// Movie's frame counter should be updated before actually rendering the frame,
// in case frame counter display is enabled
@ -884,7 +884,8 @@ void VideoInterfaceManager::Update(u64 ticks)
if (m_half_line_count == 0 || m_half_line_count == GetHalfLinesPerEvenField())
Core::Callback_NewField(m_system);
// SLIPPINOTES: this section is disable because we would rather poll every chance we get to reduce lag
// SLIPPINOTES: this section is disable because we would rather poll every chance we get to reduce
// lag
// // If an SI poll is scheduled to happen on this half-line, do it!
// if (m_half_line_of_next_si_poll == m_half_line_count)

View file

@ -157,6 +157,7 @@ enum class MessageID : u8
SLIPPI_MATCH_SELECTIONS = 0x82,
SLIPPI_CONN_SELECTED = 0x83,
SLIPPI_CHAT_MESSAGE = 0x84,
SLIPPI_COMPLETE_STEP = 0x85,
GolfRequest = 0x90,
GolfSwitch = 0x91,

View file

@ -0,0 +1,107 @@
#pragma once
#include "Common/CommonTypes.h"
#define REPORT_PLAYER_COUNT 4
namespace SlippiExiTypes
{
// Using pragma pack here will remove any structure padding which is what EXI comms expect
// https://www.geeksforgeeks.org/how-to-avoid-structure-padding-in-c/
// Note that none of these classes should be used outside of the handler function, pragma pack
// is supposedly not very efficient?
#pragma pack(1)
struct ReportGameQueryPlayer
{
u8 slot_type;
u8 stocks_remaining;
float damage_done;
};
struct ReportGameQuery
{
u8 command;
u8 mode;
u32 frame_length;
u32 game_index;
u32 tiebreak_index;
s8 winner_idx;
u8 game_end_method;
s8 lras_initiator;
ReportGameQueryPlayer players[REPORT_PLAYER_COUNT];
u8 game_info_block[312];
};
struct GpCompleteStepQuery
{
u8 command;
u8 step_idx;
u8 char_selection;
u8 char_color_selection;
u8 stage_selections[2];
};
struct GpFetchStepQuery
{
u8 command;
u8 step_idx;
};
struct GpFetchStepResponse
{
u8 is_found;
u8 is_skip;
u8 char_selection;
u8 char_color_selection;
u8 stage_selections[2];
};
struct OverwriteCharSelections
{
u8 is_set;
u8 char_id;
u8 char_color_id;
};
struct OverwriteSelectionsQuery
{
u8 command;
u16 stage_id;
OverwriteCharSelections chars[4];
};
// Not sure if resetting is strictly needed, might be contained to the file
#pragma pack()
template <typename T>
inline T Convert(u8* payload)
{
return *reinterpret_cast<T*>(payload);
}
// Here we define custom convert functions for any type that larger than u8 sized fields to convert
// from big-endian
template <>
inline ReportGameQuery Convert(u8* payload)
{
auto q = *reinterpret_cast<ReportGameQuery*>(payload);
q.frameLength = Common::FromBigEndian(q.frameLength);
q.gameIndex = Common::FromBigEndian(q.gameIndex);
q.tiebreakIndex = Common::FromBigEndian(q.tiebreakIndex);
for (int i = 0; i < REPORT_PLAYER_COUNT; i++)
{
auto* p = &q.players[i];
p->damageDone = Common::FromBigEndian(p->damageDone);
}
return q;
}
template <>
inline OverwriteSelectionsQuery Convert(u8* payload)
{
auto q = *reinterpret_cast<OverwriteSelectionsQuery*>(payload);
q.stage_id = Common::FromBigEndian(q.stage_id);
return q;
}
}; // namespace SlippiExiTypes

View file

@ -9,6 +9,7 @@
#include "Common/Common.h"
#include "Core/ConfigManager.h"
#include "Core/Slippi/SlippiMatchmaking.h"
#include <codecvt>
#include <locale>
@ -39,15 +40,15 @@ SlippiGameReporter::SlippiGameReporter(SlippiUser* user)
m_user = user;
run_thread = true;
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
reporting_thread = std::thread(&SlippiGameReporter::ReportThreadHandler, this);
}
SlippiGameReporter::~SlippiGameReporter()
{
run_thread = false;
cv.notify_one();
if (reportingThread.joinable())
reportingThread.join();
if (reporting_thread.joinable())
reporting_thread.join();
if (m_curl)
{
@ -62,10 +63,9 @@ void SlippiGameReporter::StartReport(GameReport report)
cv.notify_one();
}
void SlippiGameReporter::StartNewSession(std::vector<std::string> new_player_uids)
void SlippiGameReporter::StartNewSession()
{
this->m_player_uids = new_player_uids;
gameIndex = 1;
game_index = 1;
}
void SlippiGameReporter::ReportThreadHandler()
@ -83,22 +83,45 @@ void SlippiGameReporter::ReportThreadHandler()
auto report = game_report_queue.front();
game_report_queue.pop();
auto userInfo = m_user->GetUserInfo();
auto ranked = SlippiMatchmaking::OnlinePlayMode::RANKED;
auto unranked = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
bool should_report = report.mode == ranked || report.mode == unranked;
if (!should_report)
{
break;
}
auto user_info = m_user->GetUserInfo();
WARN_LOG_FMT(SLIPPI_ONLINE, "Checking game report for game {}. Length: {}...", game_index,
report.duration_frames);
// Prepare report
json request;
request["uid"] = userInfo.uid;
request["playKey"] = userInfo.play_key;
request["gameIndex"] = gameIndex;
request["matchId"] = report.match_id;
request["uid"] = user_info.uid;
request["playKey"] = user_info.play_key;
request["mode"] = report.mode;
request["gameIndex"] = report.mode == ranked ? report.game_index : game_index;
request["tiebreakIndex"] = report.mode == ranked ? report.tiebreak_index : 0;
request["gameIndex"] = game_index;
request["gameDurationFrames"] = report.duration_frames;
request["winnerIdx"] = report.winner_idx;
request["gameEndMethod"] = report.game_end_method;
request["lrasInitiator"] = report.lras_initiator;
request["stageId"] = report.stage_id;
json players = json::array();
for (int i = 0; i < report.players.size(); i++)
{
json p;
p["uid"] = m_player_uids[i];
p["damage_done"] = report.players[i].damage_done;
p["stocks_remaining"] = report.players[i].stocks_remaining;
p["slotType"] = report.players[i].slot_type;
p["damageDone"] = report.players[i].damage_done;
p["stocksRemaining"] = report.players[i].stocks_remaining;
p["characterId"] = report.players[i].char_id;
p["colorId"] = report.players[i].color_id;
p["startingStocks"] = report.players[i].starting_stocks;
p["startingPercent"] = report.players[i].starting_percent;
players[i] = p;
}
@ -120,8 +143,34 @@ void SlippiGameReporter::ReportThreadHandler()
static_cast<u8>(res));
}
gameIndex++;
game_index++;
Common::SleepCurrentThread(0);
}
}
}
void SlippiGameReporter::ReportAbandonment(std::string match_id)
{
auto userInfo = m_user->GetUserInfo();
// Prepare report
json request;
request["matchId"] = match_id;
request["uid"] = userInfo.uid;
request["playKey"] = userInfo.play_key;
auto requestString = request.dump();
// Send report
curl_easy_setopt(m_curl, CURLOPT_POST, true);
curl_easy_setopt(m_curl, CURLOPT_URL, ABANDON_URL.c_str());
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, requestString.c_str());
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, requestString.length());
CURLcode res = curl_easy_perform(m_curl);
if (res != 0)
{
ERROR_LOG_FMT(SLIPPI_ONLINE,
"[GameReport] Got error executing abandonment request. Err code: {}", res);
}
}

View file

@ -9,6 +9,7 @@
#include <thread>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiMatchmaking.h"
#include "Core/Slippi/SlippiUser.h"
class SlippiGameReporter
@ -16,12 +17,27 @@ class SlippiGameReporter
public:
struct PlayerReport
{
std::string uid;
u8 slot_type;
float damage_done;
u8 stocks_remaining;
u8 char_id;
u8 color_id;
int starting_stocks;
int starting_percent;
};
struct GameReport
{
SlippiMatchmaking::OnlinePlayMode mode = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
std::string match_id;
u32 duration_frames = 0;
u32 game_index = 0;
u32 tiebreak_index = 0;
s8 winner_idx = 0;
u8 game_end_method = 0;
s8 lras_initiator = 0;
int stage_id;
std::vector<PlayerReport> players;
};
@ -29,20 +45,22 @@ public:
~SlippiGameReporter();
void StartReport(GameReport report);
void StartNewSession(std::vector<std::string> player_uids);
void ReportAbandonment(std::string match_id);
void StartNewSession();
void ReportThreadHandler();
protected:
const std::string REPORT_URL = "https://rankings-dot-slippi.uc.r.appspot.com/report";
const std::string ABANDON_URL = "https://rankings-dot-slippi.uc.r.appspot.com/abandon";
CURL* m_curl = nullptr;
struct curl_slist* m_curl_header_list = nullptr;
u32 gameIndex = 1;
u32 game_index = 1;
std::vector<std::string> m_player_uids;
SlippiUser* m_user;
std::queue<GameReport> game_report_queue;
std::thread reportingThread;
std::thread reporting_thread;
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> run_thread;

View file

@ -44,6 +44,7 @@ SlippiMatchmaking::SlippiMatchmaking(SlippiUser* user)
SlippiMatchmaking::~SlippiMatchmaking()
{
is_mm_terminated = true;
m_state = ProcessState::ERROR_ENCOUNTERED;
m_errorMsg = "Matchmaking shut down";
@ -148,6 +149,11 @@ void SlippiMatchmaking::MatchmakeThread()
{
while (IsSearching())
{
if (is_mm_terminated)
{
break;
}
switch (m_state)
{
case ProcessState::INITIALIZING:
@ -380,7 +386,10 @@ void SlippiMatchmaking::startMatchmaking()
// Send message to server to create ticket
json request;
request["type"] = MmMessageType::CREATE_TICKET;
request["user"] = {{"uid", userInfo.uid}, {"playKey", userInfo.play_key}};
request["user"] = {{"uid", userInfo.uid},
{"playKey", userInfo.play_key},
{"connectCode", userInfo.connect_code},
{"displayName", userInfo.display_name}};
request["search"] = {{"mode", m_searchSettings.mode}, {"connectCode", connectCodeBuf}};
request["appVersion"] = Common::GetSemVerStr();
request["ipAddressLan"] = lan_addr;
@ -477,6 +486,12 @@ void SlippiMatchmaking::handleMatchmaking()
m_remoteIps.clear();
m_playerInfo.clear();
std::string matchId = getResp.value("matchId", "");
WARN_LOG_FMT(SLIPPI_ONLINE, "Match ID: {}", matchId);
std::string match_id = getResp.value("matchId", "");
WARN_LOG_FMT(SLIPPI_ONLINE, "Match ID: {}", match_id);
auto queue = getResp["players"];
if (queue.is_array())
{
@ -564,6 +579,10 @@ void SlippiMatchmaking::handleMatchmaking()
}
}
m_mm_result.id = match_id;
m_mm_result.players = m_playerInfo;
m_mm_result.stages = m_allowedStages;
// Disconnect and destroy enet client to mm server
terminateMmConnection();
@ -596,6 +615,11 @@ std::string SlippiMatchmaking::GetPlayerName(u8 port)
return m_playerInfo[port].display_name;
}
SlippiMatchmaking::MatchmakeResult SlippiMatchmaking::GetMatchmakeResult()
{
return m_mm_result;
}
u8 SlippiMatchmaking::RemotePlayerCount()
{
if (m_playerInfo.size() == 0)

View file

@ -47,6 +47,13 @@ public:
std::string connectCode = "";
};
struct MatchmakeResult
{
std::string id = "";
std::vector<SlippiUser::UserInfo> players;
std::vector<u16> stages;
};
void FindMatch(MatchSearchSettings settings);
void MatchmakeThread();
ProcessState GetMatchmakeState();
@ -58,6 +65,7 @@ public:
std::string GetPlayerName(u8 port);
std::vector<u16> GetStages();
u8 RemotePlayerCount();
MatchmakeResult GetMatchmakeResult();
static bool IsFixedRulesMode(OnlinePlayMode mode);
protected:
@ -73,6 +81,7 @@ protected:
std::default_random_engine generator;
bool isMmConnected = false;
bool is_mm_terminated = false;
std::thread m_matchmakeThread;
@ -88,6 +97,7 @@ protected:
int m_hostPort;
int m_localPlayerIndex;
std::vector<std::string> m_remoteIps;
MatchmakeResult m_mm_result;
std::vector<SlippiUser::UserInfo> m_playerInfo;
std::vector<u16> m_allowedStages;
bool m_joinedLobby;

View file

@ -188,7 +188,10 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
// Fetch current time immediately for the most accurate timing calculations
u64 curTime = Common::Timer::NowUs();
int32_t frame;
s32 frame;
s32 checksum_frame;
u32 checksum;
if (!(packet >> frame))
{
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read frame count");
@ -201,6 +204,16 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read player index");
break;
}
if (!(packet >> checksum_frame))
{
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read checksum frame");
break;
}
if (!(packet >> checksum))
{
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read checksum value");
break;
}
u8 pIdx = PlayerIdxFromPort(packetPlayerPort);
if (pIdx >= m_remotePlayerCount)
{
@ -208,6 +221,9 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
break;
}
// This is the amount of bytes from the start of the packet where the pad data starts
int pad_data_offset = 14;
// This fetches the m_server index that stores the connection we want to overwrite (if
// necessary). Note that this index is not necessarily the same as the pIdx because if we have
// users connecting with the same WAN, the m_server indices might not match
@ -277,6 +293,7 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
frameOffsetData[pIdx].idx = (frameOffsetData[pIdx].idx + 1) % SLIPPI_ONLINE_LOCKSTEP_INTERVAL;
s64 inputs_to_copy;
{
std::lock_guard<std::mutex> lk(pad_mutex); // TODO: Is this the correct lock?
@ -289,45 +306,56 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
s64 frame64 = static_cast<s64>(frame);
s32 headFrame = remotePadQueue[pIdx].empty() ? 0 : remotePadQueue[pIdx].front()->frame;
s64 inputsToCopy = frame64 - static_cast<s64>(headFrame);
s64 inputs_to_copy = frame64 - static_cast<s64>(headFrame);
// Check that the packet actually contains the data it claims to
if ((6 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > static_cast<s64>(packet.getDataSize()))
if ((pad_data_offset + inputs_to_copy * SLIPPI_PAD_DATA_SIZE) >
static_cast<s64>(packet.getDataSize()))
{
ERROR_LOG_FMT(
SLIPPI_ONLINE,
"Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
static_cast<int>(packet.getDataSize()), inputsToCopy,
6 + inputsToCopy * SLIPPI_PAD_DATA_SIZE);
static_cast<int>(packet.getDataSize()), inputs_to_copy,
pad_data_offset + inputs_to_copy * SLIPPI_PAD_DATA_SIZE);
break;
}
// Not sure what the max is here. If we never ack frames it could get big...
if (inputsToCopy > 128)
if (inputs_to_copy > 128)
{
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet contained too many frames: {}", inputsToCopy);
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet contained too many frames: {}",
inputs_to_copy);
break;
}
for (s64 i = inputsToCopy - 1; i >= 0; i--)
for (s64 i = inputs_to_copy - 1; i >= 0; i--)
{
auto pad = std::make_unique<SlippiPad>(static_cast<s32>(frame64 - i), pIdx,
&packetData[6 + i * SLIPPI_PAD_DATA_SIZE]);
auto pad =
std::make_unique<SlippiPad>(static_cast<s32>(frame64 - i), pIdx,
&packetData[pad_data_offset + i * SLIPPI_PAD_DATA_SIZE]);
remotePadQueue[pIdx].push_front(std::move(pad));
}
}
// Send Ack
sf::Packet spac;
spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD_ACK);
spac << frame;
spac << m_player_idx;
// INFO_LOG_FMT(SLIPPI_ONLINE, "Sending ack packet for frame {} (player {}) to peer at {}:{}",
// frame, packetPlayerPort, peer->address.host, peer->address.port);
// Only ack if inputsToCopy is greater than 0. Otherwise we are receiving an old input and
// we should have already acked something in the future. This can also happen in the case
// where a new game starts quickly before the remote queue is reset and if we ack the early
// inputs we will never receive them
if (inputs_to_copy > 0)
{
// Send Ack
sf::Packet spac;
spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD_ACK);
spac << frame;
spac << m_player_idx;
// INFO_LOG(SLIPPI_ONLINE, "Sending ack packet for frame %d (player %d) to peer at %d:%d",
// frame, packetPlayerPort,
// peer->address.host, peer->address.port);
ENetPacket* epac =
enet_packet_create(spac.getData(), spac.getDataSize(), ENET_PACKET_FLAG_UNSEQUENCED);
enet_peer_send(peer, 2, epac);
ENetPacket* epac =
enet_packet_create(spac.getData(), spac.getDataSize(), ENET_PACKET_FLAG_UNSEQUENCED);
int sendResult = enet_peer_send(peer, 2, epac);
}
}
break;
@ -440,6 +468,20 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
}
break;
case NetPlay::MessageID::SLIPPI_COMPLETE_STEP:
{
SlippiGamePrepStepResults results;
packet >> results.step_idx;
packet >> results.char_selection;
packet >> results.char_color_selection;
packet >> results.stage_selections[0];
packet >> results.stage_selections[1];
game_prep_step_queue.push_back(results);
}
break;
default:
WARN_LOG_FMT(SLIPPI_ONLINE, "Unknown message received with id : {}", static_cast<u8>(mid));
break;
@ -989,6 +1031,9 @@ void SlippiNetplayClient::StartSlippiGame()
std::swap(ackTimers[i], empty);
}
// Clear game prep queue in case anything is still lingering
game_prep_step_queue.clear();
// Reset match info for next game
matchInfo.Reset();
}
@ -1046,9 +1091,11 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
*spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD);
*spac << frame;
*spac << this->m_player_idx;
*spac << localPadQueue.front()->checksum_frame;
*spac << localPadQueue.front()->checksum;
for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it)
spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
spac->append((*it)->pad_buf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
SendAsync(std::move(spac));
u64 time = Common::Timer::NowUs();
@ -1081,6 +1128,35 @@ void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
SendAsync(std::move(spac));
}
void SlippiNetplayClient::SendGamePrepStep(SlippiGamePrepStepResults& s)
{
auto spac = std::make_unique<sf::Packet>();
*spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_COMPLETE_STEP);
*spac << s.step_idx;
*spac << s.char_selection;
*spac << s.char_color_selection;
*spac << s.stage_selections[0] << s.stage_selections[1];
SendAsync(std::move(spac));
}
bool SlippiNetplayClient::GetGamePrepResults(u8 step_idx, SlippiGamePrepStepResults& res)
{
// Just pull stuff off until we find something for the right step. I think that should be fine
while (!game_prep_step_queue.empty())
{
auto front = game_prep_step_queue.front();
if (front.step_idx == step_idx)
{
res = front;
return true;
}
game_prep_step_queue.pop_front();
}
return false;
}
SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isChatEnabled)
{
SlippiPlayerSelections copiedSelection = SlippiPlayerSelections();
@ -1098,6 +1174,7 @@ SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isCh
{
copiedSelection.messageId = 0;
copiedSelection.playerIdx = 0;
// if chat is not enabled, automatically send back a message saying so.
if (remoteChatMessageSelection != nullptr && !isChatEnabled &&
(remoteChatMessageSelection->messageId > 0 &&
@ -1137,12 +1214,12 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
if (frame % 60 < 5)
{
// Return old inputs for a bit
padOutput->latestFrame = frame - (frame % 60);
padOutput->latest_frame = frame - (frame % 60);
padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0);
}
else if (frame % 60 == 5)
{
padOutput->latestFrame = frame;
padOutput->latest_frame = frame;
// Add 5 frames of 0'd inputs
padOutput->data.insert(padOutput->data.begin(), 5 * SLIPPI_PAD_FULL_SIZE, 0);
@ -1151,7 +1228,7 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
}
else
{
padOutput->latestFrame = frame;
padOutput->latest_frame = frame;
padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0);
}
@ -1169,9 +1246,9 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
{
auto emptyPad = std::make_unique<SlippiPad>(0);
padOutput->latestFrame = emptyPad->frame;
padOutput->latest_frame = emptyPad->frame;
auto emptyIt = std::begin(emptyPad->padBuf);
auto emptyIt = std::begin(emptyPad->pad_buf);
padOutput->data.insert(padOutput->data.end(), emptyIt, emptyIt + SLIPPI_PAD_FULL_SIZE);
return std::move(padOutput);
@ -1179,7 +1256,9 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
int inputCount = 0;
padOutput->latestFrame = 0;
padOutput->latest_frame = 0;
padOutput->checksum_frame = remoteChecksum[index].frame;
padOutput->checksum = remoteChecksum[index].value;
// Copy inputs from the remote pad queue to the output. We iterate backwards because
// we want to get the oldest frames possible (will have been cleared to contain the last
@ -1188,14 +1267,14 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
// game actually needed.
for (auto it = remotePadQueue[index].rbegin(); it != remotePadQueue[index].rend(); ++it)
{
if ((*it)->frame > padOutput->latestFrame)
padOutput->latestFrame = (*it)->frame;
if ((*it)->frame > padOutput->latest_frame)
padOutput->latest_frame = (*it)->frame;
// NOTICE_LOG(SLIPPI_ONLINE, "[%d] (Remote) P%d %08X %08X %08X", (*it)->frame,
// index >= playerIdx ? index + 1 : index, Common::swap32(&(*it)->padBuf[0]),
// Common::swap32(&(*it)->padBuf[4]), Common::swap32(&(*it)->padBuf[8]));
auto padIt = std::begin((*it)->padBuf);
auto padIt = std::begin((*it)->pad_buf);
padOutput->data.insert(padOutput->data.begin(), padIt, padIt + SLIPPI_PAD_FULL_SIZE);
// Limit max amount of inputs to send
@ -1231,7 +1310,7 @@ int32_t SlippiNetplayClient::GetSlippiLatestRemoteFrame(int maxFrameCount)
for (int i = 0; i < m_remotePlayerCount; i++)
{
auto rp = GetSlippiRemotePad(i, maxFrameCount);
int f = rp->latestFrame;
int f = rp->latest_frame;
if (f < lowestFrame || !isFrameSet)
{
lowestFrame = f;

View file

@ -34,11 +34,21 @@
struct SlippiRemotePadOutput
{
int32_t latestFrame{};
int32_t latest_frame{};
s32 checksum_frame;
u32 checksum;
u8 playerIdx{};
std::vector<u8> data;
};
struct SlippiGamePrepStepResults
{
u8 step_idx;
u8 char_selection;
u8 char_color_selection;
u8 stage_selections[2];
};
class SlippiPlayerSelections
{
public:
@ -90,6 +100,12 @@ public:
}
};
struct ChecksumEntry
{
s32 frame;
u32 value;
};
class SlippiMatchInfo
{
public:
@ -137,6 +153,8 @@ public:
void SendConnectionSelected();
void SendSlippiPad(std::unique_ptr<SlippiPad> pad);
void SetMatchSelections(SlippiPlayerSelections& s);
void SendGamePrepStep(SlippiGamePrepStepResults& s);
bool GetGamePrepResults(u8 stepIdx, SlippiGamePrepStepResults& res);
std::unique_ptr<SlippiRemotePadOutput> GetFakePadOutput(int frame);
std::unique_ptr<SlippiRemotePadOutput> GetSlippiRemotePad(int index, int maxFrameCount);
void DropOldRemoteInputs(int32_t finalizedFrame);
@ -201,7 +219,8 @@ protected:
std::deque<std::unique_ptr<SlippiPad>> localPadQueue; // most recent inputs at start of deque
std::deque<std::unique_ptr<SlippiPad>>
remotePadQueue[SLIPPI_REMOTE_PLAYER_MAX]; // most recent inputs at start of deque
ChecksumEntry remoteChecksum[SLIPPI_REMOTE_PLAYER_MAX];
std::deque<SlippiGamePrepStepResults> game_prep_step_queue;
u64 pingUs[SLIPPI_REMOTE_PLAYER_MAX];
int32_t lastFrameAcked[SLIPPI_REMOTE_PLAYER_MAX];
FrameOffsetData frameOffsetData[SLIPPI_REMOTE_PLAYER_MAX];

View file

@ -10,21 +10,24 @@ static u8 emptyPad[SLIPPI_PAD_FULL_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
SlippiPad::SlippiPad(int32_t frame)
{
this->frame = frame;
memcpy(this->padBuf, emptyPad, SLIPPI_PAD_FULL_SIZE);
this->checksum = 0;
this->checksum_frame = 0;
memcpy(this->pad_buf, emptyPad, SLIPPI_PAD_FULL_SIZE);
}
SlippiPad::SlippiPad(int32_t frame, u8* padBuf) : SlippiPad(frame)
SlippiPad::SlippiPad(int32_t frame, u8* pad_buf) : SlippiPad(frame)
{
// Overwrite the data portion of the pad
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
memcpy(this->pad_buf, pad_buf, SLIPPI_PAD_DATA_SIZE);
}
SlippiPad::SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf) : SlippiPad(frame)
SlippiPad::SlippiPad(int32_t frame, s32 checksum_frame, u32 checksum, u8* pad_buf)
: SlippiPad(frame, pad_buf)
{
this->frame = frame;
this->playerIdx = playerIdx;
this->checksum_frame = checksum_frame;
this->checksum = checksum;
// Overwrite the data portion of the pad
memcpy(this->padBuf, padBuf, SLIPPI_PAD_DATA_SIZE);
memcpy(this->pad_buf, pad_buf, SLIPPI_PAD_DATA_SIZE);
}
SlippiPad::~SlippiPad()

View file

@ -9,11 +9,12 @@ 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* pad_buf);
SlippiPad(int32_t frame, s32 checksum_frame, u32 checksum, u8* pad_buf);
~SlippiPad();
int32_t frame;
u8 playerIdx;
u8 padBuf[SLIPPI_PAD_FULL_SIZE];
s32 frame;
s32 checksum_frame;
u32 checksum;
u8 pad_buf[SLIPPI_PAD_FULL_SIZE];
};

View file

@ -19,16 +19,16 @@
#include <QTimer>
#include <QWindow>
//test ui stuff
#include <QVBoxLayout>
// test ui stuff
#include <QPushButton>
#include <QVBoxLayout>
#include "imgui.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/State.h"
#include "Core/Slippi/SlippiPlayback.h"
#include "Core/State.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"