From 86572ceb249424a6b3863a602518ed1dbda13524 Mon Sep 17 00:00:00 2001 From: R2DLiu Date: Tue, 14 Jul 2020 00:00:35 -0400 Subject: [PATCH] fix replays --- Externals/SlippiLib/SlippiGame.cpp | 225 ++++++++++--------- Externals/SlippiLib/SlippiGame.h | 53 ++--- Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp | 8 +- Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h | 2 +- 4 files changed, 147 insertions(+), 141 deletions(-) diff --git a/Externals/SlippiLib/SlippiGame.cpp b/Externals/SlippiLib/SlippiGame.cpp index 2db051d789..b76d004970 100644 --- a/Externals/SlippiLib/SlippiGame.cpp +++ b/Externals/SlippiLib/SlippiGame.cpp @@ -1,12 +1,15 @@ +#include +#include +#include + #include "SlippiGame.h" namespace Slippi { - // SLIPPITODO: maybe refactor with std::byte and std::filesystem //********************************************************************** - //* Event Handlers * + //* Event Handlers //********************************************************************** - // The read operators will read a value and increment the index so the next read will read in the correct location + //The read operators will read a value and increment the index so the next read will read in the correct location uint8_t readByte(uint8_t* a, int& idx, uint32_t maxSize, uint8_t defaultValue) { if (idx >= (int)maxSize) { idx += 1; @@ -43,27 +46,27 @@ namespace Slippi { return *(float*)(&bytes); } - void handleGameInit(Game &game, uint32_t maxSize) { + void handleGameInit(Game* game, uint32_t maxSize) { int idx = 0; // Read version number for (int i = 0; i < 4; i++) { - game.version[i] = readByte(data, idx, maxSize, 0); + game->version[i] = readByte(data, idx, maxSize, 0); } // Read entire game info header for (int i = 0; i < GAME_INFO_HEADER_SIZE; i++) { - game.settings.header[i] = readWord(data, idx, maxSize, 0); + game->settings.header[i] = readWord(data, idx, maxSize, 0); } // Load random seed - game.settings.randomSeed = readWord(data, idx, maxSize, 0); + game->settings.randomSeed = readWord(data, idx, maxSize, 0); // Read UCF toggle bytes - bool shouldRead = game.version[0] >= 1; + bool shouldRead = game->version[0] >= 1; for (int i = 0; i < UCF_TOGGLE_SIZE; i++) { uint32_t value = shouldRead ? readWord(data, idx, maxSize, 0) : 0; - game.settings.ucfToggles[i] = value; + game->settings.ucfToggles[i] = value; } // Read nametag for each player @@ -75,14 +78,14 @@ namespace Slippi { } // Read isPAL byte - game.settings.isPAL = readByte(data, idx, maxSize, 0); + game->settings.isPAL = readByte(data, idx, maxSize, 0); // Read isFrozenPS byte - game.settings.isFrozenPS = readByte(data, idx, maxSize, 0); + game->settings.isFrozenPS = readByte(data, idx, maxSize, 0); // Pull header data into struct int player1Pos = 24; // This is the index of the first players character info - std::array gameInfoHeader = game.settings.header; + std::array gameInfoHeader = game->settings.header; for (int i = 0; i < 4; i++) { // this is the position in the array that this player's character info is stored int pos = player1Pos + (9 * i); @@ -104,72 +107,74 @@ namespace Slippi { p.nametag = playerNametags[i]; //Add player settings to result - game.settings.players[i] = p; + game->settings.players[i] = p; } - game.settings.stage = gameInfoHeader[3] & 0xFFFF; + game->settings.stage = gameInfoHeader[3] & 0xFFFF; - auto majorVersion = game.version[0]; - auto minorVersion = game.version[1]; + auto majorVersion = game->version[0]; + auto minorVersion = game->version[1]; if (majorVersion > 3 || (majorVersion == 3 && minorVersion >= 1)) { // After version 3.1.0 we added a dynamic gecko loading process. These // are needed before starting the game. areSettingsLoaded will be set // to true when they are received - game.areSettingsLoaded = false; + game->areSettingsLoaded = false; } else if (majorVersion > 1 || (majorVersion == 1 && minorVersion >= 6)) { // Indicate settings loaded immediately if after version 1.6.0 // Sheik game info was added in this version and so we no longer // need to wait - game.areSettingsLoaded = true; + game->areSettingsLoaded = true; } } - void handleGeckoList(Game &game, uint32_t maxSize) { - game.settings.geckoCodes.clear(); - game.settings.geckoCodes.insert(game.settings.geckoCodes.end(), data, data + maxSize); + void handleGeckoList(Game* game, uint32_t maxSize) { + game->settings.geckoCodes.clear(); + game->settings.geckoCodes.insert(game->settings.geckoCodes.end(), data, data + maxSize); // File is good to load - game.areSettingsLoaded = true; + game->areSettingsLoaded = true; } - void handleFrameStart(Game &game, uint32_t maxSize) { + void handleFrameStart(Game* game, uint32_t maxSize) { int idx = 0; //Check frame count int32_t frameCount = readWord(data, idx, maxSize, 0); - game.frameCount = frameCount; + game->frameCount = frameCount; - FrameData frame; + auto frameUniquePtr = std::make_unique(); + FrameData* frame = frameUniquePtr.get(); - frame.frame = frameCount; - frame.randomSeedExists = true; - frame.randomSeed = readWord(data, idx, maxSize, 0); + frame->frame = frameCount; + frame->randomSeedExists = true; + frame->randomSeed = readWord(data, idx, maxSize, 0); // Add frame to game. The frames are stored in multiple ways because // for games with rollback, the same frame may be replayed multiple times - frame.numSinceStart = game.frames.size(); - game.frames.emplace_back(std::move(frame)); - game.framesByIndex[frameCount] = frame; + frame->numSinceStart = game->frames.size(); + game->frames.push_back(std::move(frameUniquePtr)); + game->framesByIndex[frameCount] = frame; } - void handlePreFrameUpdate(Game &game, uint8_t const preFrameUpdate, uint32_t maxSize) { + void handlePreFrameUpdate(Game* game, uint32_t maxSize) { int idx = 0; //Check frame count int32_t frameCount = readWord(data, idx, maxSize, 0); - game.frameCount = frameCount; + game->frameCount = frameCount; - FrameData frame; + auto frameUniquePtr = std::make_unique(); + FrameData* frame = frameUniquePtr.get(); bool isNewFrame = true; - if (game.framesByIndex.count(frameCount)) { + if (game->framesByIndex.count(frameCount)) { // If this frame already exists, get the current frame - frame = game.frames.back(); + frame = game->frames.back().get(); isNewFrame = false; } - frame.frame = frameCount; + frame->frame = frameCount; PlayerFrameData p; @@ -198,7 +203,7 @@ namespace Slippi { p.lTrigger = readFloat(data, idx, maxSize, 0); p.rTrigger = readFloat(data, idx, maxSize, 0); - if (preFrameUpdate >= 59) { + if (asmEvents[EVENT_PRE_FRAME_UPDATE] >= 59) { p.joystickXRaw = readByte(data, idx, maxSize, 0); } @@ -207,51 +212,51 @@ namespace Slippi { // Add player data to frame std::unordered_map* target; - target = isFollower ? &frame.followers : &frame.players; + target = isFollower ? &frame->followers : &frame->players; // Set the player data for the player or follower target->operator[](playerSlot) = p; // Add frame to game if (isNewFrame) { - frame.numSinceStart = game.frames.size(); - game.frames.emplace_back(frame); - game.framesByIndex[frameCount] = frame; + frame->numSinceStart = game->frames.size(); + game->frames.push_back(std::move(frameUniquePtr)); + game->framesByIndex[frameCount] = frame; } } - void handlePostFrameUpdate(Game &game, uint32_t maxSize) { + void handlePostFrameUpdate(Game* game, uint32_t maxSize) { int idx = 0; //Check frame count int32_t frameCount = readWord(data, idx, maxSize, 0); - FrameData frame; - if (game.framesByIndex.count(frameCount)) { + FrameData* frame; + if (game->framesByIndex.count(frameCount)) { // If this frame already exists, get the current frame - auto test = game.frames.back(); + frame = game->frames.back().get(); } // As soon as a post frame update happens, we know we have received all the inputs // This is used to determine if a frame is ready to be used for a replay (for mirroring) - frame.inputsFullyFetched = true; + frame->inputsFullyFetched = true; uint8_t playerSlot = readByte(data, idx, maxSize, 0); uint8_t isFollower = readByte(data, idx, maxSize, 0); - PlayerFrameData* p = isFollower ? &frame.followers[playerSlot] : &frame.players[playerSlot]; + PlayerFrameData* p = isFollower ? &frame->followers[playerSlot] : &frame->players[playerSlot]; p->internalCharacterId = readByte(data, idx, maxSize, 0); // Check if a player started as sheik and update if (frameCount == GAME_FIRST_FRAME && p->internalCharacterId == GAME_SHEIK_INTERNAL_ID) { - game.settings.players[playerSlot].characterId = GAME_SHEIK_EXTERNAL_ID; + game->settings.players[playerSlot].characterId = GAME_SHEIK_EXTERNAL_ID; } // Set settings loaded if this is the last character if (frameCount == GAME_FIRST_FRAME) { uint8_t lastPlayerIndex = 0; - for (auto it = frame.players.begin(); it != frame.players.end(); ++it) { + for (auto it = frame->players.begin(); it != frame->players.end(); ++it) { if (it->first <= lastPlayerIndex) { continue; } @@ -260,15 +265,15 @@ namespace Slippi { } if (playerSlot >= lastPlayerIndex) { - game.areSettingsLoaded = true; + game->areSettingsLoaded = true; } } } - void handleGameEnd(Game &game, uint32_t maxSize) { + void handleGameEnd(Game* game, uint32_t maxSize) { int idx = 0; - game.winCondition = readByte(data, idx, maxSize, 0); + game->winCondition = readByte(data, idx, maxSize, 0); } // This function gets the position where the raw data starts @@ -300,7 +305,7 @@ namespace Slippi { f->seekg(position - 4, std::ios::beg); f->read(buffer, 4); - uint8_t* byteBuf = (uint8_t*)& buffer[0]; + uint8_t* byteBuf = (uint8_t*)&buffer[0]; uint32_t length = byteBuf[0] << 24 | byteBuf[1] << 16 | byteBuf[2] << 8 | byteBuf[3]; return length; } @@ -318,6 +323,7 @@ namespace Slippi { { EVENT_PAYLOAD_SIZES, payloadLength } }; + std::vector messageSizesBuffer(payloadLength - 1); f->read(&messageSizesBuffer[0], payloadLength - 1); for (int i = 0; i < payloadLength - 1; i += 3) { @@ -334,52 +340,6 @@ namespace Slippi { return messageSizes; } - bool SlippiGame::AreSettingsLoaded() { - processData(); - return game.areSettingsLoaded; - }; - - bool SlippiGame::DoesFrameExist(int32_t frame) { - processData(); - return (bool)game.framesByIndex.count(frame); - }; - - std::array SlippiGame::GetVersion() { - return game.version; - } - - std::shared_ptr SlippiGame::GetFrame(int32_t frame) { - // Get the frame we want - return std::make_shared(game.framesByIndex.at(frame)); - }; - - std::shared_ptr SlippiGame::GetFrameAt(uint32_t pos) { - if (pos >= game.frames.size()) { - return nullptr; - } - - // Get the frame we want - return std::make_shared(game.frames[pos]); - }; - - int32_t SlippiGame::GetLatestIndex() { - processData(); - return game.frameCount; - }; - - GameSettings* SlippiGame::GetSettings() { - processData(); - return &game.settings; - }; - - bool SlippiGame::DoesPlayerExist(int8_t port) { - return game.settings.players.find(port) != game.settings.players.end(); - }; - - bool SlippiGame::IsProcessingComplete() { - return isProcessingComplete; - } - void SlippiGame::processData() { if (isProcessingComplete) { // If we have finished processing this file, return @@ -455,7 +415,7 @@ namespace Slippi { return; } - data = (uint8_t*)& newData[newDataPos + 1]; + data = (uint8_t*)&newData[newDataPos + 1]; uint8_t isSplitComplete = false; uint32_t outerPayloadSize = payloadSize; @@ -485,22 +445,22 @@ namespace Slippi { switch (command) { case EVENT_GAME_INIT: - handleGameInit(game, payloadSize); + handleGameInit(game.get(), payloadSize); break; case EVENT_GECKO_LIST: - handleGeckoList(game, payloadSize); + handleGeckoList(game.get(), payloadSize); break; case EVENT_FRAME_START: - handleFrameStart(game, payloadSize); + handleFrameStart(game.get(), payloadSize); break; case EVENT_PRE_FRAME_UPDATE: - handlePreFrameUpdate(game, asmEvents[EVENT_PRE_FRAME_UPDATE], payloadSize); + handlePreFrameUpdate(game.get(), payloadSize); break; case EVENT_POST_FRAME_UPDATE: - handlePostFrameUpdate(game, payloadSize); + handlePostFrameUpdate(game.get(), payloadSize); break; case EVENT_GAME_END: - handleGameEnd(game, payloadSize); + handleGameEnd(game.get(), payloadSize); isProcessingComplete = true; break; case 0x55: @@ -521,13 +481,11 @@ namespace Slippi { std::unique_ptr SlippiGame::FromFile(std::string path) { auto result = std::make_unique(); + result->game = std::make_unique(); result->path = path; #ifdef _WIN32 // On Windows, we need to convert paths to std::wstring to deal with UTF-8 - - // SLIPPITODO: codecvt is deprecated. C++17 msvc support std::filesystem::u8path - // SLIPPITODO: c++20 std::filesystem::path natively supports utf8 std::wstring convertedPath = std::wstring_convert>().from_bytes(path); result->file = std::make_unique(convertedPath, std::ios::in | std::ios::binary); #else @@ -552,4 +510,51 @@ namespace Slippi { return std::move(result); } + + bool SlippiGame::IsProcessingComplete() { + return isProcessingComplete; + } + + bool SlippiGame::AreSettingsLoaded() { + processData(); + return game->areSettingsLoaded; + } + + bool SlippiGame::DoesFrameExist(int32_t frame) { + processData(); + return (bool)game->framesByIndex.count(frame); + } + + std::array SlippiGame::GetVersion() + { + return game->version; + } + + FrameData* SlippiGame::GetFrame(int32_t frame) { + // Get the frame we want + return game->framesByIndex.at(frame); + } + + FrameData* SlippiGame::GetFrameAt(uint32_t pos) { + if (pos >= game->frames.size()) { + return nullptr; + } + + // Get the frame we want + return game->frames[pos].get(); + } + + int32_t SlippiGame::GetLatestIndex() { + processData(); + return game->frameCount; + } + + GameSettings* SlippiGame::GetSettings() { + processData(); + return &game->settings; + } + + bool SlippiGame::DoesPlayerExist(int8_t port) { + return game->settings.players.find(port) != game->settings.players.end(); + } } diff --git a/Externals/SlippiLib/SlippiGame.h b/Externals/SlippiLib/SlippiGame.h index 1bb6693e89..de76ea75c7 100644 --- a/Externals/SlippiLib/SlippiGame.h +++ b/Externals/SlippiLib/SlippiGame.h @@ -7,8 +7,6 @@ #include #include #include -#include -#include namespace Slippi { const uint8_t EVENT_SPLIT_MESSAGE = 0x10; @@ -32,7 +30,7 @@ namespace Slippi { static uint8_t* data; - struct PlayerFrameData { + typedef struct { // Every player update has its own rng seed because it might change in between players uint32_t randomSeed; @@ -62,9 +60,9 @@ namespace Slippi { float rTrigger; uint8_t joystickXRaw; - }; + } PlayerFrameData; - struct FrameData { + typedef struct FrameData { int32_t frame; uint32_t numSinceStart; bool randomSeedExists = false; @@ -72,18 +70,18 @@ namespace Slippi { bool inputsFullyFetched = false; std::unordered_map players; std::unordered_map followers; - }; + } FrameData; - struct PlayerSettings { + typedef struct { //Static data uint8_t characterId; uint8_t characterColor; uint8_t playerType; uint8_t controllerPort; std::array nametag; - }; + } PlayerSettings; - struct GameSettings { + typedef struct { uint16_t stage; //Stage ID uint32_t randomSeed; std::array header; @@ -92,12 +90,12 @@ namespace Slippi { uint8_t isPAL; uint8_t isFrozenPS; std::vector geckoCodes; - }; + } GameSettings; - struct Game { + typedef struct Game { std::array version; - std::unordered_map framesByIndex; - std::vector frames; + std::unordered_map framesByIndex; + std::vector> frames; GameSettings settings; bool areSettingsLoaded = false; @@ -105,38 +103,41 @@ namespace Slippi { //From OnGameEnd event uint8_t winCondition; + } Game; + + // TODO: This shouldn't be static. Doesn't matter too much atm because we always + // TODO: only read one file at a time + static std::unordered_map asmEvents = { + { EVENT_GAME_INIT, 320 }, + { EVENT_PRE_FRAME_UPDATE, 58 }, + { EVENT_POST_FRAME_UPDATE, 33 }, + { EVENT_GAME_END, 1 }, + { EVENT_FRAME_START, 8 } }; - class SlippiGame { + class SlippiGame + { public: static std::unique_ptr FromFile(std::string path); bool AreSettingsLoaded(); bool DoesFrameExist(int32_t frame); std::array GetVersion(); - std::shared_ptr GetFrame(int32_t frame); - std::shared_ptr GetFrameAt(uint32_t pos); + FrameData* GetFrame(int32_t frame); + FrameData* GetFrameAt(uint32_t pos); int32_t GetLatestIndex(); GameSettings* GetSettings(); bool DoesPlayerExist(int8_t port); bool IsProcessingComplete(); - private: - Game game; + std::unique_ptr game; std::unique_ptr file; std::vector rawData; std::string path; std::ofstream log; std::vector splitMessageBuf; - std::unordered_map asmEvents = { - { EVENT_GAME_INIT, 320 }, - { EVENT_PRE_FRAME_UPDATE, 58 }, - { EVENT_POST_FRAME_UPDATE, 33 }, - { EVENT_GAME_END, 1 }, - { EVENT_FRAME_START, 8 } - }; bool shouldResetSplitMessageBuf = false; - bool isProcessingComplete = false; + bool isProcessingComplete = false; void processData(); }; } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp index d9e6ec9ae1..eabbcfcc1f 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.cpp @@ -1071,7 +1071,7 @@ void CEXISlippi::prepareGeckoList() geckoList.insert(geckoList.end(), { 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); } -void CEXISlippi::prepareCharacterFrameData(std::shared_ptr frame, u8 port, u8 isFollower) +void CEXISlippi::prepareCharacterFrameData(Slippi::FrameData* frame, u8 port, u8 isFollower) { std::unordered_map source; source = isFollower ? frame->followers : frame->players; @@ -1120,7 +1120,7 @@ bool CEXISlippi::checkFrameFullyFetched(s32 frameIndex) if (!doesFrameExist) return false; - std::shared_ptr frame = m_current_game->GetFrame(frameIndex); + Slippi::FrameData* frame = m_current_game->GetFrame(frameIndex); // This flag is set to true after a post frame update has been received. At that point // we know we have received all of the input data for the frame @@ -1268,7 +1268,7 @@ void CEXISlippi::prepareFrameData(u8* payload) m_read_queue.push_back(requestResultCode); // Get frame - std::shared_ptr frame = m_current_game->GetFrame(frameIndex); + Slippi::FrameData* frame = m_current_game->GetFrame(frameIndex); if (commSettings.rollbackDisplayMethod != "off") { auto previousFrame = m_current_game->GetFrameAt(frameSeqIdx - 1); @@ -1355,7 +1355,7 @@ void CEXISlippi::prepareIsStockSteal(u8* payload) } // Load the data from this frame into the read buffer - std::shared_ptr frame = m_current_game->GetFrame(frameIndex); + Slippi::FrameData* frame = m_current_game->GetFrame(frameIndex); auto players = frame->players; u8 playerIsBack = players.count(playerIndex) ? 1 : 0; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h index 603a0ff59f..2daf21cfe9 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSlippi.h @@ -166,7 +166,7 @@ namespace ExpansionInterface // replay playback stuff void prepareGameInfo(u8* payload); void prepareGeckoList(); - void prepareCharacterFrameData(std::shared_ptr frame, u8 port, u8 isFollower); + void prepareCharacterFrameData(Slippi::FrameData* frame, u8 port, u8 isFollower); void prepareFrameData(u8* payload); void prepareIsStockSteal(u8* payload); void prepareIsFileReady();