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

View file

@ -178,6 +178,7 @@ std::u32string UTF8ToUTF32(const std::string& input);
std::u32string UTF8ToUTF32(const std::string& input); std::u32string UTF8ToUTF32(const std::string& input);
#endif #endif
std::string UTF32toUTF8(const std::u32string& input); 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 ConvertStringForGame(const std::string& input, int length);
std::string CP1252ToUTF8(std::string_view str); std::string CP1252ToUTF8(std::string_view str);
std::string SHIFTJISToUTF8(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) const Info<bool>& GetInfoForAdapterRumble(int channel)
{ {
#ifndef IS_PLAYBACK
static const std::array<const Info<bool>, 4> infos{ static const std::array<const Info<bool>, 4> infos{
Info<bool>{{System::Main, "Core", "AdapterRumble0"}, true}, Info<bool>{{System::Main, "Core", "AdapterRumble0"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble1"}, true}, Info<bool>{{System::Main, "Core", "AdapterRumble1"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble2"}, true}, Info<bool>{{System::Main, "Core", "AdapterRumble2"}, true},
Info<bool>{{System::Main, "Core", "AdapterRumble3"}, 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]; return infos[channel];
} }

View file

@ -170,6 +170,13 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
if (SConfig::GetSlippiConfig().melee_version == Melee::Version::NTSC || if (SConfig::GetSlippiConfig().melee_version == Melee::Version::NTSC ||
SConfig::GetSlippiConfig().melee_version == Melee::Version::MEX) 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 // Write GCT loader into memory which will eventually load the real GCT into the heap
std::string bootloaderData; std::string bootloaderData;
std::string _bootloaderFilename = File::GetSysDirectory() + GCT_BOOTLOADER; std::string _bootloaderFilename = File::GetSysDirectory() + GCT_BOOTLOADER;
@ -218,7 +225,7 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
"not write: \"{}\". Need {} bytes, only {} remain.", "not write: \"{}\". Need {} bytes, only {} remain.",
active_code.name, active_code.codes.size() * CODE_SIZE, active_code.name, active_code.codes.size() * CODE_SIZE,
end_address - next_address); end_address - next_address);
continue; return Installation::Failed;
} }
for (const GeckoCode::Code& code : active_code.codes) for (const GeckoCode::Code& code : active_code.codes)

View file

@ -255,6 +255,17 @@ CEXISlippi::~CEXISlippi()
SlippiSpectateServer::getInstance().endGame(true); 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(); localSelections.Reset();
g_playbackStatus->resetPlayback(); g_playbackStatus->resetPlayback();
@ -1538,7 +1549,10 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
m_read_queue.clear(); m_read_queue.clear();
s32 frame = Common::swap32(&payload[0]); 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) if (frame == 1)
{ {
@ -1565,7 +1579,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
fallBehindCounter = 0; fallBehindCounter = 0;
fallFarBehindCounter = 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(); localSelections.Reset();
if (slippi_netplay) if (slippi_netplay)
slippi_netplay->StartSlippiGame(); 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) // 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) if (shouldSkip)
{ {
// Send inputs that have not yet been acked // Send inputs that have not yet been acked
@ -1589,7 +1603,7 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
else else
{ {
// Send the input for this frame along with everything that has yet to be acked // 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); prepareOpponentInputs(frame, shouldSkip);
@ -1764,8 +1778,9 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
// don't understand atm. // don't understand atm.
OSD::AddTypedMessage( OSD::AddTypedMessage(
OSD::MessageType::PerformanceWarning, OSD::MessageType::PerformanceWarning,
"Your computer is running slow and is impacting the performance of the match.", 10000, "Possible poor match performance detected.\nIf this message appears with most opponents, "
OSD::Color::RED); "your computer or network is likely impacting match performance for the other players.",
10000, OSD::Color::RED);
} }
if (offsetUs < -t2 && !isCurrentlyAdvancing) if (offsetUs < -t2 && !isCurrentlyAdvancing)
@ -1800,14 +1815,11 @@ bool CEXISlippi::shouldAdvanceOnlineFrame(s32 frame)
return false; return false;
} }
void CEXISlippi::handleSendInputs(u8* payload) void CEXISlippi::handleSendInputs(s32 frame, u8 delay, s32 checksum_frame, u32 checksum, u8* inputs)
{ {
if (isConnectionStalled) if (isConnectionStalled)
return; 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 // On the first frame sent, we need to queue up empty dummy pads for as many
// frames as we have delay // frames as we have delay
if (frame == 1) 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)); 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 m_read_queue.push_back(remotePlayerCount); // Indicate the number of remote players
std::unique_ptr<SlippiRemotePadOutput> results[SLIPPI_REMOTE_PLAYER_MAX]; 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]; int offset[SLIPPI_REMOTE_PLAYER_MAX];
int32_t latestFrameRead[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; 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 && offset[i] < results[i]->data.size())
{ {
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();
@ -2185,12 +2215,71 @@ void CEXISlippi::handleNameEntryLoad(u8* payload)
appendWordToBuffer(&m_read_queue, curIndex); 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() 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 // 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 // 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, 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, 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, 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, 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(); slippi_netplay = matchmaking->GetNetplayClient();
#endif #endif
// This happens on the initial connection to a player. Let's now grab the allowed stages // This happens on the initial connection to a player. The matchmaking object is ephemeral, it
// returned to us from the matchmaking service and pick a new random stage before sending // gets re-created when a connection is terminated, that said, it can still be useful to know
// the selections to the opponent // who we were connected to after they disconnect from us, for example in the case of
allowedStages = matchmaking->GetStages(); // 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 // Clear stage pool so that when we call getRandomStage it will use full list
stagePool.clear(); stagePool.clear();
localSelections.stageId = getRandomStage(); localSelections.stageId = getRandomStage();
@ -2305,16 +2395,7 @@ void CEXISlippi::prepareOnlineMatchState()
// 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; game_reporter->StartNewSession();
auto mmPlayers = matchmaking->GetPlayerInfo();
for (auto mmp : mmPlayers)
{
uids.push_back(mmp.uid);
}
game_reporter->StartNewSession(uids);
is_play_session_active = true; is_play_session_active = true;
} }
} }
@ -2415,10 +2496,7 @@ void CEXISlippi::prepareOnlineMatchState()
rps[i].isCharacterSelected = true; rps[i].isCharacterSelected = true;
} }
if (lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS) remotePlayerCount = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::TEAMS ? 3 : 1;
{
remotePlayerCount = 3;
}
oppName = std::string("Player"); oppName = std::string("Player");
#endif #endif
@ -2433,25 +2511,39 @@ void CEXISlippi::prepareOnlineMatchState()
remoteCharOk = false; 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 // the local player
// TODO: Would also simplify some logic in the Netplay class // TODO: Would also simplify some logic in the Netplay class
std::vector<SlippiPlayerSelections> orderedSelections(4); // Here we are storing pointers to the player selections. That means that we can technically
orderedSelections[lps.playerIdx] = lps; // 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++) 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 // Overwrite stage information. Make sure everyone loads the same stage
u16 stageId = 0x1F; // Default to battlefield if there was no selection u16 stageId = 0x1F; // Default to battlefield if there was no selection
for (const auto& selections : orderedSelections) for (const auto& selections : orderedSelections)
{ {
if (!selections.isStageSelected) if (!selections->isStageSelected)
continue; continue;
// Stage selected by this player, use that selection // Stage selected by this player, use that selection
stageId = selections.stageId; stageId = selections->stageId;
break; break;
} }
@ -2505,11 +2597,11 @@ void CEXISlippi::prepareOnlineMatchState()
INFO_LOG_FMT(SLIPPI_ONLINE, "Rng Offset: {:#x}", rngOffset); INFO_LOG_FMT(SLIPPI_ONLINE, "Rng Offset: {:#x}", rngOffset);
// Check if everyone is the same color // Check if everyone is the same color
auto color = orderedSelections[0].teamId; auto color = orderedSelections[0]->teamId;
bool areAllSameTeam = true; bool areAllSameTeam = true;
for (const auto& s : orderedSelections) for (const auto& s : orderedSelections)
{ {
if (s.teamId != color) if (s->teamId != color)
areAllSameTeam = false; areAllSameTeam = false;
} }
@ -2526,21 +2618,21 @@ void CEXISlippi::prepareOnlineMatchState()
// Overwrite player character choices // Overwrite player character choices
for (auto& s : orderedSelections) for (auto& s : orderedSelections)
{ {
if (!s.isCharacterSelected) if (!s->isCharacterSelected)
{ {
continue; continue;
} }
if (areAllSameTeam) if (areAllSameTeam)
{ {
// Overwrite teamId. Color is overwritten by ASM // Overwrite teamId. Color is overwritten by ASM
s.teamId = teamAssignments[s.playerIdx]; s->teamId = teamAssignments[s->playerIdx];
} }
// Overwrite player character // Overwrite player character
onlineMatchBlock[0x60 + (s.playerIdx) * 0x24] = s.characterId; onlineMatchBlock[0x60 + (s->playerIdx) * 0x24] = s->characterId;
onlineMatchBlock[0x63 + (s.playerIdx) * 0x24] = s.characterColor; onlineMatchBlock[0x63 + (s->playerIdx) * 0x24] = s->characterColor;
onlineMatchBlock[0x67 + (s.playerIdx) * 0x24] = 0; onlineMatchBlock[0x67 + (s->playerIdx) * 0x24] = 0;
onlineMatchBlock[0x69 + (s.playerIdx) * 0x24] = s.teamId; onlineMatchBlock[0x69 + (s->playerIdx) * 0x24] = s->teamId;
} }
// Handle Singles/Teams specific logic // Handle Singles/Teams specific logic
@ -2573,8 +2665,7 @@ void CEXISlippi::prepareOnlineMatchState()
*stage = Common::swap16(stageId); *stage = Common::swap16(stageId);
// Turn pause off in unranked/ranked, on in other modes // Turn pause off in unranked/ranked, on in other modes
auto pauseAllowed = !SlippiMatchmaking::IsFixedRulesMode(lastSearch.mode) && auto pauseAllowed = lastSearch.mode == SlippiMatchmaking::OnlinePlayMode::DIRECT;
lastSearch.mode != SlippiMatchmaking::OnlinePlayMode::TEAMS;
u8* gameBitField3 = static_cast<u8*>(&onlineMatchBlock[2]); u8* gameBitField3 = static_cast<u8*>(&onlineMatchBlock[2]);
*gameBitField3 = pauseAllowed ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8; *gameBitField3 = pauseAllowed ? *gameBitField3 & 0xF7 : *gameBitField3 | 0x8;
//*gameBitField3 = *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 // Create the opponent string using the names of all players on opposing teams
int teamIdx = onlineMatchBlock[0x69 + m_local_player_index * 0x24]; std::vector<std::string> opponentNames = {};
std::string oppText = ""; if (matchmaking->RemotePlayerCount() == 1)
for (int i = 0; i < 4; i++)
{ {
if (i == m_local_player_index) opponentNames.push_back(matchmaking->GetPlayerName(remotePlayerIndex));
continue; }
else
if (onlineMatchBlock[0x69 + i * 0x24] != teamIdx) {
int teamIdx = onlineMatchBlock[0x69 + localPlayerIndex * 0x24];
for (int i = 0; i < 4; i++)
{ {
if (oppText != "") if (localPlayerIndex == i || onlineMatchBlock[0x69 + i * 0x24] == teamIdx)
oppText += "/"; continue;
oppText += matchmaking->GetPlayerName(i); opponentNames.push_back(matchmaking->GetPlayerName(i));
} }
} }
if (matchmaking->RemotePlayerCount() == 1)
oppText = matchmaking->GetPlayerName(m_remote_player_index); auto numOpponents = opponentNames.size() == 0 ? 1 : opponentNames.size();
oppName = ConvertStringForGame(oppText, MAX_NAME_LENGTH * 2 + 1); 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()); m_read_queue.insert(m_read_queue.end(), oppName.begin(), oppName.end());
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
@ -3003,6 +3105,9 @@ void CEXISlippi::handleConnectionCleanup()
// Reset any forced errors // Reset any forced errors
forcedError.clear(); forcedError.clear();
// Reset any selection overwrites
overwrite_selections.clear();
// Reset play session // Reset play session
is_play_session_active = false; is_play_session_active = false;
@ -3022,29 +3127,50 @@ void CEXISlippi::prepareNewSeed()
appendWordToBuffer(&m_read_queue, newSeed); appendWordToBuffer(&m_read_queue, newSeed);
} }
void CEXISlippi::handleReportGame(u8* payload) void CEXISlippi::handleReportGame(const SlippiExiTypes::ReportGameQuery& query)
{ {
#ifndef LOCAL_TESTING
SlippiGameReporter::GameReport r; 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; SlippiGameReporter::PlayerReport p;
auto offset = i * 6; p.uid = mm_players.size() > i ? mm_players[i].uid : "";
p.stocks_remaining = payload[5 + offset]; 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]); ERROR_LOG_FMT(SLIPPI_ONLINE,
p.damage_done = *(float*)&swappedDamageDone; "UID: {}, Port Type: {}, Stocks: {}, DamageDone: {}, CharId: {}, ColorId: {}, "
"StartStocks: {}, "
// ERROR_LOG_FMT(SLIPPI_ONLINE, "Stocks: {}, DamageDone: %f", p.stocks_remaining, "StartPercent: {}",
// p.damage_done); 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); r.players.push_back(p);
} }
#ifndef LOCAL_TESTING
game_reporter->StartReport(r); game_reporter->StartReport(r);
#endif #endif
} }
@ -3063,6 +3189,84 @@ void CEXISlippi::prepareDelayResponse()
m_read_queue.push_back(Config::Get(Config::SLIPPI_ONLINE_DELAY)); 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) void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
{ {
auto& system = Core::System::GetInstance(); 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], memPtr[bufLoc + 1], memPtr[bufLoc + 2], memPtr[bufLoc + 3],
memPtr[bufLoc + 4]); memPtr[bufLoc + 4]);
u8 prevCommandByte = 0;
while (bufLoc < _uSize) while (bufLoc < _uSize)
{ {
byte = memPtr[bufLoc]; byte = memPtr[bufLoc];
if (!payloadSizes.count(byte)) if (!payloadSizes.count(byte))
{ {
// This should never happen. Do something else if it does? // 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; return;
} }
@ -3204,7 +3411,7 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
prepareNewSeed(); prepareNewSeed();
break; break;
case CMD_REPORT_GAME: case CMD_REPORT_GAME:
handleReportGame(&memPtr[bufLoc + 1]); handleReportGame(SlippiExiTypes::Convert<SlippiExiTypes::ReportGameQuery>(&memPtr[bufLoc]));
break; break;
case CMD_GCT_LENGTH: case CMD_GCT_LENGTH:
prepareGctLength(); prepareGctLength();
@ -3215,12 +3422,25 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
case CMD_GET_DELAY: case CMD_GET_DELAY:
prepareDelayResponse(); prepareDelayResponse();
break; 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: 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);
break; break;
} }
prevCommandByte = byte;
bufLoc += payloadLen + 1; bufLoc += payloadLen + 1;
} }
} }

View file

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

View file

@ -849,7 +849,7 @@ void VideoInterfaceManager::Update(u64 ticks)
// Try calling SI Poll every time update is called // Try calling SI Poll every time update is called
SerialInterface::UpdateDevices(); SerialInterface::UpdateDevices();
Core::UpdateInputGate(!Config::Get(Config::MAIN_INPUT_BACKGROUND_INPUT), 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, // Movie's frame counter should be updated before actually rendering the frame,
// in case frame counter display is enabled // 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()) if (m_half_line_count == 0 || m_half_line_count == GetHalfLinesPerEvenField())
Core::Callback_NewField(m_system); 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 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) // 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_MATCH_SELECTIONS = 0x82,
SLIPPI_CONN_SELECTED = 0x83, SLIPPI_CONN_SELECTED = 0x83,
SLIPPI_CHAT_MESSAGE = 0x84, SLIPPI_CHAT_MESSAGE = 0x84,
SLIPPI_COMPLETE_STEP = 0x85,
GolfRequest = 0x90, GolfRequest = 0x90,
GolfSwitch = 0x91, 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 "Common/Common.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Slippi/SlippiMatchmaking.h"
#include <codecvt> #include <codecvt>
#include <locale> #include <locale>
@ -39,15 +40,15 @@ SlippiGameReporter::SlippiGameReporter(SlippiUser* user)
m_user = user; m_user = user;
run_thread = true; run_thread = true;
reportingThread = std::thread(&SlippiGameReporter::ReportThreadHandler, this); reporting_thread = 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 (reporting_thread.joinable())
reportingThread.join(); reporting_thread.join();
if (m_curl) if (m_curl)
{ {
@ -62,10 +63,9 @@ void SlippiGameReporter::StartReport(GameReport report)
cv.notify_one(); cv.notify_one();
} }
void SlippiGameReporter::StartNewSession(std::vector<std::string> new_player_uids) void SlippiGameReporter::StartNewSession()
{ {
this->m_player_uids = new_player_uids; game_index = 1;
gameIndex = 1;
} }
void SlippiGameReporter::ReportThreadHandler() void SlippiGameReporter::ReportThreadHandler()
@ -83,22 +83,45 @@ void SlippiGameReporter::ReportThreadHandler()
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 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 // Prepare report
json request; json request;
request["uid"] = userInfo.uid; request["matchId"] = report.match_id;
request["playKey"] = userInfo.play_key; request["uid"] = user_info.uid;
request["gameIndex"] = gameIndex; 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["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(); 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"] = m_player_uids[i]; p["uid"] = m_player_uids[i];
p["damage_done"] = report.players[i].damage_done; p["slotType"] = report.players[i].slot_type;
p["stocks_remaining"] = report.players[i].stocks_remaining; 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; players[i] = p;
} }
@ -120,8 +143,34 @@ void SlippiGameReporter::ReportThreadHandler()
static_cast<u8>(res)); static_cast<u8>(res));
} }
gameIndex++; game_index++;
Common::SleepCurrentThread(0); 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 <thread>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/Slippi/SlippiMatchmaking.h"
#include "Core/Slippi/SlippiUser.h" #include "Core/Slippi/SlippiUser.h"
class SlippiGameReporter class SlippiGameReporter
@ -16,12 +17,27 @@ class SlippiGameReporter
public: public:
struct PlayerReport struct PlayerReport
{ {
std::string uid;
u8 slot_type;
float damage_done; float damage_done;
u8 stocks_remaining; u8 stocks_remaining;
u8 char_id;
u8 color_id;
int starting_stocks;
int starting_percent;
}; };
struct GameReport struct GameReport
{ {
SlippiMatchmaking::OnlinePlayMode mode = SlippiMatchmaking::OnlinePlayMode::UNRANKED;
std::string match_id;
u32 duration_frames = 0; 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; std::vector<PlayerReport> players;
}; };
@ -29,20 +45,22 @@ public:
~SlippiGameReporter(); ~SlippiGameReporter();
void StartReport(GameReport report); void StartReport(GameReport report);
void StartNewSession(std::vector<std::string> player_uids); void ReportAbandonment(std::string match_id);
void StartNewSession();
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";
const std::string ABANDON_URL = "https://rankings-dot-slippi.uc.r.appspot.com/abandon";
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 game_index = 1;
std::vector<std::string> m_player_uids; std::vector<std::string> m_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 reporting_thread;
std::mutex mtx; std::mutex mtx;
std::condition_variable cv; std::condition_variable cv;
std::atomic<bool> run_thread; std::atomic<bool> run_thread;

View file

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

View file

@ -47,6 +47,13 @@ public:
std::string connectCode = ""; std::string connectCode = "";
}; };
struct MatchmakeResult
{
std::string id = "";
std::vector<SlippiUser::UserInfo> players;
std::vector<u16> stages;
};
void FindMatch(MatchSearchSettings settings); void FindMatch(MatchSearchSettings settings);
void MatchmakeThread(); void MatchmakeThread();
ProcessState GetMatchmakeState(); ProcessState GetMatchmakeState();
@ -58,6 +65,7 @@ public:
std::string GetPlayerName(u8 port); std::string GetPlayerName(u8 port);
std::vector<u16> GetStages(); std::vector<u16> GetStages();
u8 RemotePlayerCount(); u8 RemotePlayerCount();
MatchmakeResult GetMatchmakeResult();
static bool IsFixedRulesMode(OnlinePlayMode mode); static bool IsFixedRulesMode(OnlinePlayMode mode);
protected: protected:
@ -73,6 +81,7 @@ protected:
std::default_random_engine generator; std::default_random_engine generator;
bool isMmConnected = false; bool isMmConnected = false;
bool is_mm_terminated = false;
std::thread m_matchmakeThread; std::thread m_matchmakeThread;
@ -88,6 +97,7 @@ protected:
int m_hostPort; int m_hostPort;
int m_localPlayerIndex; int m_localPlayerIndex;
std::vector<std::string> m_remoteIps; std::vector<std::string> m_remoteIps;
MatchmakeResult m_mm_result;
std::vector<SlippiUser::UserInfo> m_playerInfo; std::vector<SlippiUser::UserInfo> m_playerInfo;
std::vector<u16> m_allowedStages; std::vector<u16> m_allowedStages;
bool m_joinedLobby; 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 // Fetch current time immediately for the most accurate timing calculations
u64 curTime = Common::Timer::NowUs(); u64 curTime = Common::Timer::NowUs();
int32_t frame; s32 frame;
s32 checksum_frame;
u32 checksum;
if (!(packet >> frame)) if (!(packet >> frame))
{ {
ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read frame count"); 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"); ERROR_LOG_FMT(SLIPPI_ONLINE, "Netplay packet too small to read player index");
break; 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); u8 pIdx = PlayerIdxFromPort(packetPlayerPort);
if (pIdx >= m_remotePlayerCount) if (pIdx >= m_remotePlayerCount)
{ {
@ -208,6 +221,9 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
break; 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 // 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 // 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 // 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; 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? 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); s64 frame64 = static_cast<s64>(frame);
s32 headFrame = remotePadQueue[pIdx].empty() ? 0 : remotePadQueue[pIdx].front()->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 // 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( ERROR_LOG_FMT(
SLIPPI_ONLINE, SLIPPI_ONLINE,
"Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}", "Netplay packet too small to read pad buffer. Size: {}, Inputs: {}, MinSize: {}",
static_cast<int>(packet.getDataSize()), inputsToCopy, static_cast<int>(packet.getDataSize()), inputs_to_copy,
6 + inputsToCopy * SLIPPI_PAD_DATA_SIZE); pad_data_offset + inputs_to_copy * SLIPPI_PAD_DATA_SIZE);
break; break;
} }
// Not sure what the max is here. If we never ack frames it could get big... // 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; 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, auto pad =
&packetData[6 + i * SLIPPI_PAD_DATA_SIZE]); 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)); remotePadQueue[pIdx].push_front(std::move(pad));
} }
} }
// Send Ack // Only ack if inputsToCopy is greater than 0. Otherwise we are receiving an old input and
sf::Packet spac; // we should have already acked something in the future. This can also happen in the case
spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD_ACK); // where a new game starts quickly before the remote queue is reset and if we ack the early
spac << frame; // inputs we will never receive them
spac << m_player_idx; if (inputs_to_copy > 0)
// INFO_LOG_FMT(SLIPPI_ONLINE, "Sending ack packet for frame {} (player {}) to peer at {}:{}", {
// frame, packetPlayerPort, peer->address.host, peer->address.port); // 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 = ENetPacket* epac =
enet_packet_create(spac.getData(), spac.getDataSize(), ENET_PACKET_FLAG_UNSEQUENCED); enet_packet_create(spac.getData(), spac.getDataSize(), ENET_PACKET_FLAG_UNSEQUENCED);
enet_peer_send(peer, 2, epac); int sendResult = enet_peer_send(peer, 2, epac);
}
} }
break; break;
@ -440,6 +468,20 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet, ENetPeer* peer)
} }
break; 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: default:
WARN_LOG_FMT(SLIPPI_ONLINE, "Unknown message received with id : {}", static_cast<u8>(mid)); WARN_LOG_FMT(SLIPPI_ONLINE, "Unknown message received with id : {}", static_cast<u8>(mid));
break; break;
@ -989,6 +1031,9 @@ void SlippiNetplayClient::StartSlippiGame()
std::swap(ackTimers[i], empty); 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 // Reset match info for next game
matchInfo.Reset(); matchInfo.Reset();
} }
@ -1046,9 +1091,11 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
*spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD); *spac << static_cast<u8>(NetPlay::MessageID::SLIPPI_PAD);
*spac << frame; *spac << frame;
*spac << this->m_player_idx; *spac << this->m_player_idx;
*spac << localPadQueue.front()->checksum_frame;
*spac << localPadQueue.front()->checksum;
for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it) 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)); SendAsync(std::move(spac));
u64 time = Common::Timer::NowUs(); u64 time = Common::Timer::NowUs();
@ -1081,6 +1128,35 @@ void SlippiNetplayClient::SetMatchSelections(SlippiPlayerSelections& s)
SendAsync(std::move(spac)); 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 SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isChatEnabled)
{ {
SlippiPlayerSelections copiedSelection = SlippiPlayerSelections(); SlippiPlayerSelections copiedSelection = SlippiPlayerSelections();
@ -1098,6 +1174,7 @@ SlippiPlayerSelections SlippiNetplayClient::GetSlippiRemoteChatMessage(bool isCh
{ {
copiedSelection.messageId = 0; copiedSelection.messageId = 0;
copiedSelection.playerIdx = 0; copiedSelection.playerIdx = 0;
// if chat is not enabled, automatically send back a message saying so. // if chat is not enabled, automatically send back a message saying so.
if (remoteChatMessageSelection != nullptr && !isChatEnabled && if (remoteChatMessageSelection != nullptr && !isChatEnabled &&
(remoteChatMessageSelection->messageId > 0 && (remoteChatMessageSelection->messageId > 0 &&
@ -1137,12 +1214,12 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
if (frame % 60 < 5) if (frame % 60 < 5)
{ {
// Return old inputs for a bit // 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); padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0);
} }
else if (frame % 60 == 5) else if (frame % 60 == 5)
{ {
padOutput->latestFrame = frame; padOutput->latest_frame = frame;
// Add 5 frames of 0'd inputs // Add 5 frames of 0'd inputs
padOutput->data.insert(padOutput->data.begin(), 5 * SLIPPI_PAD_FULL_SIZE, 0); padOutput->data.insert(padOutput->data.begin(), 5 * SLIPPI_PAD_FULL_SIZE, 0);
@ -1151,7 +1228,7 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetFakePadOutput(int
} }
else else
{ {
padOutput->latestFrame = frame; padOutput->latest_frame = frame;
padOutput->data.insert(padOutput->data.begin(), SLIPPI_PAD_FULL_SIZE, 0); 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); 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); padOutput->data.insert(padOutput->data.end(), emptyIt, emptyIt + SLIPPI_PAD_FULL_SIZE);
return std::move(padOutput); return std::move(padOutput);
@ -1179,7 +1256,9 @@ std::unique_ptr<SlippiRemotePadOutput> SlippiNetplayClient::GetSlippiRemotePad(i
int inputCount = 0; 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 // 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 // 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. // game actually needed.
for (auto it = remotePadQueue[index].rbegin(); it != remotePadQueue[index].rend(); ++it) for (auto it = remotePadQueue[index].rbegin(); it != remotePadQueue[index].rend(); ++it)
{ {
if ((*it)->frame > padOutput->latestFrame) if ((*it)->frame > padOutput->latest_frame)
padOutput->latestFrame = (*it)->frame; padOutput->latest_frame = (*it)->frame;
// NOTICE_LOG(SLIPPI_ONLINE, "[%d] (Remote) P%d %08X %08X %08X", (*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]), // index >= playerIdx ? index + 1 : index, Common::swap32(&(*it)->padBuf[0]),
// Common::swap32(&(*it)->padBuf[4]), Common::swap32(&(*it)->padBuf[8])); // 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); padOutput->data.insert(padOutput->data.begin(), padIt, padIt + SLIPPI_PAD_FULL_SIZE);
// Limit max amount of inputs to send // 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++) for (int i = 0; i < m_remotePlayerCount; i++)
{ {
auto rp = GetSlippiRemotePad(i, maxFrameCount); auto rp = GetSlippiRemotePad(i, maxFrameCount);
int f = rp->latestFrame; int f = rp->latest_frame;
if (f < lowestFrame || !isFrameSet) if (f < lowestFrame || !isFrameSet)
{ {
lowestFrame = f; lowestFrame = f;

View file

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

View file

@ -9,11 +9,12 @@ class SlippiPad
{ {
public: public:
SlippiPad(int32_t frame); SlippiPad(int32_t frame);
SlippiPad(int32_t frame, u8* padBuf); SlippiPad(int32_t frame, u8* pad_buf);
SlippiPad(int32_t frame, u8 playerIdx, u8* padBuf); SlippiPad(int32_t frame, s32 checksum_frame, u32 checksum, u8* pad_buf);
~SlippiPad(); ~SlippiPad();
int32_t frame; s32 frame;
u8 playerIdx; s32 checksum_frame;
u8 padBuf[SLIPPI_PAD_FULL_SIZE]; u32 checksum;
u8 pad_buf[SLIPPI_PAD_FULL_SIZE];
}; };

View file

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