diff --git a/Externals/SlippiLib/SlippiGame.cpp b/Externals/SlippiLib/SlippiGame.cpp index 31987774d7..497f9bf1d5 100644 --- a/Externals/SlippiLib/SlippiGame.cpp +++ b/Externals/SlippiLib/SlippiGame.cpp @@ -4,15 +4,17 @@ #include "SlippiGame.h" -namespace Slippi { - +namespace Slippi +{ //********************************************************************** //* Event Handlers //********************************************************************** // 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) { +uint8_t readByte(uint8_t* a, int& idx, uint32_t maxSize, uint8_t defaultValue) +{ + if (idx >= (int)maxSize) + { idx += 1; return defaultValue; } @@ -20,9 +22,10 @@ uint8_t readByte(uint8_t *a, int &idx, uint32_t maxSize, uint8_t defaultValue) { return a[idx++]; } -uint16_t readHalf(uint8_t *a, int &idx, uint32_t maxSize, - uint16_t defaultValue) { - if (idx >= (int)maxSize) { +uint16_t readHalf(uint8_t* a, int& idx, uint32_t maxSize, uint16_t defaultValue) +{ + if (idx >= (int)maxSize) + { idx += 2; return defaultValue; } @@ -32,34 +35,38 @@ uint16_t readHalf(uint8_t *a, int &idx, uint32_t maxSize, return value; } -uint32_t readWord(uint8_t *a, int &idx, uint32_t maxSize, - uint32_t defaultValue) { - if (idx >= (int)maxSize) { +uint32_t readWord(uint8_t* a, int& idx, uint32_t maxSize, uint32_t defaultValue) +{ + if (idx >= (int)maxSize) + { idx += 4; return defaultValue; } - uint32_t value = - a[idx] << 24 | a[idx + 1] << 16 | a[idx + 2] << 8 | a[idx + 3]; + uint32_t value = a[idx] << 24 | a[idx + 1] << 16 | a[idx + 2] << 8 | a[idx + 3]; idx += 4; return value; } -float readFloat(uint8_t *a, int &idx, uint32_t maxSize, float defaultValue) { - uint32_t bytes = readWord(a, idx, maxSize, *(uint32_t *)(&defaultValue)); - return *(float *)(&bytes); +float readFloat(uint8_t* a, int& idx, uint32_t maxSize, float defaultValue) +{ + uint32_t bytes = readWord(a, idx, maxSize, *(uint32_t*)(&defaultValue)); + 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++) { + for (int i = 0; i < 4; i++) + { game->version[i] = readByte(data, idx, maxSize, 0); } // Read entire game info header - for (int i = 0; i < GAME_INFO_HEADER_SIZE; i++) { + for (int i = 0; i < GAME_INFO_HEADER_SIZE; i++) + { game->settings.header[i] = readWord(data, idx, maxSize, 0); } @@ -68,15 +75,18 @@ void handleGameInit(Game *game, uint32_t maxSize) { // Read UCF toggle bytes bool shouldRead = game->version[0] >= 1; - for (int i = 0; i < UCF_TOGGLE_SIZE; i++) { + for (int i = 0; i < UCF_TOGGLE_SIZE; i++) + { uint32_t value = shouldRead ? readWord(data, idx, maxSize, 0) : 0; game->settings.ucfToggles[i] = value; } // Read nametag for each player std::array, 4> playerNametags; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < NAMETAG_SIZE; j++) { + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < NAMETAG_SIZE; j++) + { playerNametags[i][j] = readHalf(data, idx, maxSize, 0); } } @@ -88,17 +98,18 @@ void handleGameInit(Game *game, uint32_t maxSize) { 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; - for (int i = 0; i < 4; i++) { + int player1Pos = 24; // This is the index of the first players character info + 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); uint32_t playerInfo = gameInfoHeader[pos]; uint8_t playerType = (playerInfo & 0x00FF0000) >> 16; - if (playerType == 0x3) { + if (playerType == 0x3) + { // Player type 3 is an empty slot continue; } @@ -120,12 +131,15 @@ void handleGameInit(Game *game, uint32_t maxSize) { auto majorVersion = game->version[0]; auto minorVersion = game->version[1]; - if (majorVersion > 3 || (majorVersion == 3 && minorVersion >= 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; - } else if (majorVersion > 1 || (majorVersion == 1 && minorVersion >= 6)) { + } + 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 @@ -133,16 +147,17 @@ void handleGameInit(Game *game, uint32_t maxSize) { } } -void handleGeckoList(Game *game, uint32_t maxSize) { +void handleGeckoList(Game* game, uint32_t maxSize) +{ game->settings.geckoCodes.clear(); - game->settings.geckoCodes.insert(game->settings.geckoCodes.end(), data, - data + maxSize); + game->settings.geckoCodes.insert(game->settings.geckoCodes.end(), data, data + maxSize); // File is good to load game->areSettingsLoaded = true; } -void handleFrameStart(Game *game, uint32_t maxSize) { +void handleFrameStart(Game* game, uint32_t maxSize) +{ int idx = 0; // Check frame count @@ -150,7 +165,7 @@ void handleFrameStart(Game *game, uint32_t maxSize) { game->frameCount = frameCount; auto frameUniquePtr = std::make_unique(); - FrameData *frame = frameUniquePtr.get(); + FrameData* frame = frameUniquePtr.get(); frame->frame = frameCount; frame->randomSeedExists = true; @@ -163,7 +178,8 @@ void handleFrameStart(Game *game, uint32_t maxSize) { game->framesByIndex[frameCount] = frame; } -void handlePreFrameUpdate(Game *game, uint32_t maxSize) { +void handlePreFrameUpdate(Game* game, uint32_t maxSize) +{ int idx = 0; // Check frame count @@ -171,10 +187,11 @@ void handlePreFrameUpdate(Game *game, uint32_t maxSize) { game->frameCount = frameCount; auto frameUniquePtr = std::make_unique(); - FrameData *frame = frameUniquePtr.get(); + 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().get(); isNewFrame = false; @@ -209,36 +226,40 @@ void handlePreFrameUpdate(Game *game, uint32_t maxSize) { p.lTrigger = readFloat(data, idx, maxSize, 0); p.rTrigger = readFloat(data, idx, maxSize, 0); - if (asmEvents[EVENT_PRE_FRAME_UPDATE] >= 59) { + if (asmEvents[EVENT_PRE_FRAME_UPDATE] >= 59) + { p.joystickXRaw = readByte(data, idx, maxSize, 0); } uint32_t noPercent = 0xFFFFFFFF; - p.percent = readFloat(data, idx, maxSize, *(float *)(&noPercent)); + p.percent = readFloat(data, idx, maxSize, *(float*)(&noPercent)); // Add player data to frame - std::unordered_map *target; + std::unordered_map* target; 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) { + if (isNewFrame) + { 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 frame = game->frames.back().get(); } @@ -251,51 +272,58 @@ void handlePostFrameUpdate(Game *game, uint32_t maxSize) { 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) { + if (frameCount == GAME_FIRST_FRAME && p->internalCharacterId == GAME_SHEIK_INTERNAL_ID) + { game->settings.players[playerSlot].characterId = GAME_SHEIK_EXTERNAL_ID; } // Set settings loaded if this is the last character - if (frameCount == GAME_FIRST_FRAME) { + if (frameCount == GAME_FIRST_FRAME) + { uint8_t lastPlayerIndex = 0; - for (auto it = frame->players.begin(); it != frame->players.end(); ++it) { - if (it->first <= lastPlayerIndex) { + for (auto it = frame->players.begin(); it != frame->players.end(); ++it) + { + if (it->first <= lastPlayerIndex) + { continue; } lastPlayerIndex = it->first; } - if (playerSlot >= lastPlayerIndex) { + if (playerSlot >= lastPlayerIndex) + { 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); } // This function gets the position where the raw data starts -int getRawDataPosition(std::ifstream *f) { +int getRawDataPosition(std::ifstream* f) +{ char buffer[2]; f->seekg(0, std::ios::beg); f->read(buffer, 2); - if (buffer[0] == 0x36) { + if (buffer[0] == 0x36) + { return 0; } - if (buffer[0] != '{') { + if (buffer[0] != '{') + { // TODO: Do something here to cause an error return 0; } @@ -306,8 +334,10 @@ int getRawDataPosition(std::ifstream *f) { return 15; } -uint32_t getRawDataLength(std::ifstream *f, int position, int fileSize) { - if (position == 0) { +uint32_t getRawDataLength(std::ifstream* f, int position, int fileSize) +{ + if (position == 0) + { return fileSize; } @@ -315,28 +345,28 @@ uint32_t getRawDataLength(std::ifstream *f, int position, int fileSize) { f->seekg(position - 4, std::ios::beg); f->read(buffer, 4); - uint8_t *byteBuf = (uint8_t *)&buffer[0]; - uint32_t length = - byteBuf[0] << 24 | byteBuf[1] << 16 | byteBuf[2] << 8 | byteBuf[3]; + uint8_t* byteBuf = (uint8_t*)&buffer[0]; + uint32_t length = byteBuf[0] << 24 | byteBuf[1] << 16 | byteBuf[2] << 8 | byteBuf[3]; return length; } -std::unordered_map getMessageSizes(std::ifstream *f, - int position) { +std::unordered_map getMessageSizes(std::ifstream* f, int position) +{ char buffer[2]; f->seekg(position, std::ios::beg); f->read(buffer, 2); - if (buffer[0] != EVENT_PAYLOAD_SIZES) { + if (buffer[0] != EVENT_PAYLOAD_SIZES) + { return {}; } int payloadLength = buffer[1]; - std::unordered_map messageSizes = { - {EVENT_PAYLOAD_SIZES, payloadLength}}; + std::unordered_map messageSizes = {{EVENT_PAYLOAD_SIZES, payloadLength}}; std::vector messageSizesBuffer(payloadLength - 1); f->read(&messageSizesBuffer[0], payloadLength - 1); - for (int i = 0; i < payloadLength - 1; i += 3) { + for (int i = 0; i < payloadLength - 1; i += 3) + { uint8_t command = messageSizesBuffer[i]; // Extract the bytes in u8s. Without this the chars don't or together well @@ -350,8 +380,10 @@ std::unordered_map getMessageSizes(std::ifstream *f, return messageSizes; } -void SlippiGame::processData() { - if (isProcessingComplete) { +void SlippiGame::processData() +{ + if (isProcessingComplete) + { // If we have finished processing this file, return return; } @@ -359,17 +391,20 @@ void SlippiGame::processData() { // This function will process as much data as possible int startPos = (int)file->tellg(); file->seekg(startPos); - if (startPos == 0) { + if (startPos == 0) + { file->seekg(0, std::ios::end); int len = (int)file->tellg(); - if (len < 2) { + if (len < 2) + { // If we can't read message sizes payload size yet, return return; } int rawDataPos = getRawDataPosition(file.get()); int rawDataLen = len - rawDataPos; - if (rawDataLen < 2) { + if (rawDataLen < 2) + { // If we don't have enough raw data yet to read the replay file, return // Reset to begining so that the startPos condition will be hit again file->seekg(0); @@ -383,7 +418,8 @@ void SlippiGame::processData() { file->read(buffer, 2); file->seekg(startPos); auto messageSizesSize = (int)buffer[1]; - if (rawDataLen < messageSizesSize) { + if (rawDataLen < messageSizesSize) + { // If we haven't received the full payload sizes message, return // Reset to begining so that the startPos condition will be hit again file->seekg(0); @@ -401,7 +437,8 @@ void SlippiGame::processData() { // log << "Size to read: " << sizeToRead << "\n"; // log << "Start Pos: " << startPos << "\n"; // log << "End Pos: " << endPos << "\n\n"; - if (sizeToRead <= 0) { + if (sizeToRead <= 0) + { return; } @@ -409,7 +446,8 @@ void SlippiGame::processData() { file->read(&newData[0], sizeToRead); int newDataPos = 0; - while (newDataPos < sizeToRead) { + while (newDataPos < sizeToRead) + { auto command = newData[newDataPos]; auto payloadSize = asmEvents[command]; @@ -418,32 +456,35 @@ void SlippiGame::processData() { // log << "Command: " << buff << " | Payload Size: " << payloadSize << "\n"; auto remainingLen = sizeToRead - newDataPos; - if (remainingLen < ((int)payloadSize + 1)) { + if (remainingLen < ((int)payloadSize + 1)) + { // Here we don't have enough data to read the whole payload // Will be processed after getting more data (hopefully) file->seekg(-remainingLen, std::ios::cur); return; } - data = (uint8_t *)&newData[newDataPos + 1]; + data = (uint8_t*)&newData[newDataPos + 1]; uint8_t isSplitComplete = false; uint32_t outerPayloadSize = payloadSize; // Handle a split message, combining in until we possess the entire message - if (command == EVENT_SPLIT_MESSAGE) { - if (shouldResetSplitMessageBuf) { + if (command == EVENT_SPLIT_MESSAGE) + { + if (shouldResetSplitMessageBuf) + { splitMessageBuf.clear(); shouldResetSplitMessageBuf = false; } int _ = 0; - uint16_t blockSize = - readHalf(&data[SPLIT_MESSAGE_INTERNAL_DATA_LEN], _, payloadSize, 0); + uint16_t blockSize = readHalf(&data[SPLIT_MESSAGE_INTERNAL_DATA_LEN], _, payloadSize, 0); splitMessageBuf.insert(splitMessageBuf.end(), data, data + blockSize); isSplitComplete = data[SPLIT_MESSAGE_INTERNAL_DATA_LEN + 3]; - if (isSplitComplete) { + if (isSplitComplete) + { // Transform this message into a different message command = data[SPLIT_MESSAGE_INTERNAL_DATA_LEN + 2]; data = &splitMessageBuf[0]; @@ -452,7 +493,8 @@ void SlippiGame::processData() { } } - switch (command) { + switch (command) + { case EVENT_GAME_INIT: handleGameInit(game.get(), payloadSize); break; @@ -488,24 +530,23 @@ void SlippiGame::processData() { } } -std::unique_ptr SlippiGame::FromFile(std::string path) { +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 - std::wstring convertedPath = - std::wstring_convert>().from_bytes(path); - result->file = std::make_unique( - convertedPath, std::ios::in | std::ios::binary); + std::wstring convertedPath = std::wstring_convert>().from_bytes(path); + result->file = std::make_unique(convertedPath, std::ios::in | std::ios::binary); #else - result->file = - std::make_unique(path, std::ios::in | std::ios::binary); + result->file = std::make_unique(path, std::ios::in | std::ios::binary); #endif // result->log.open("log.txt"); - if (!result->file->is_open()) { + if (!result->file->is_open()) + { return nullptr; } @@ -523,27 +564,38 @@ std::unique_ptr SlippiGame::FromFile(std::string path) { return std::move(result); } -bool SlippiGame::IsProcessingComplete() { return isProcessingComplete; } +bool SlippiGame::IsProcessingComplete() +{ + return isProcessingComplete; +} -bool SlippiGame::AreSettingsLoaded() { +bool SlippiGame::AreSettingsLoaded() +{ processData(); return game->areSettingsLoaded; } -bool SlippiGame::DoesFrameExist(int32_t frame) { +bool SlippiGame::DoesFrameExist(int32_t frame) +{ processData(); return (bool)game->framesByIndex.count(frame); } -std::array SlippiGame::GetVersion() { return game->version; } +std::array SlippiGame::GetVersion() +{ + return game->version; +} -FrameData *SlippiGame::GetFrame(int32_t frame) { +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()) { +FrameData* SlippiGame::GetFrameAt(uint32_t pos) +{ + if (pos >= game->frames.size()) + { return nullptr; } @@ -551,17 +603,20 @@ FrameData *SlippiGame::GetFrameAt(uint32_t pos) { return game->frames[pos].get(); } -int32_t SlippiGame::GetLatestIndex() { +int32_t SlippiGame::GetLatestIndex() +{ processData(); return game->frameCount; } -GameSettings *SlippiGame::GetSettings() { +GameSettings* SlippiGame::GetSettings() +{ processData(); return &game->settings; } -bool SlippiGame::DoesPlayerExist(int8_t port) { +bool SlippiGame::DoesPlayerExist(int8_t port) +{ return game->settings.players.find(port) != game->settings.players.end(); } -} // namespace Slippi +} // namespace Slippi diff --git a/Externals/SlippiLib/SlippiGame.h b/Externals/SlippiLib/SlippiGame.h index bb352ec3ae..f585cb3cd8 100644 --- a/Externals/SlippiLib/SlippiGame.h +++ b/Externals/SlippiLib/SlippiGame.h @@ -8,8 +8,8 @@ #include #include -namespace Slippi { - +namespace Slippi +{ const uint8_t EVENT_SPLIT_MESSAGE = 0x10; const uint8_t EVENT_PAYLOAD_SIZES = 0x35; const uint8_t EVENT_GAME_INIT = 0x36; @@ -29,9 +29,10 @@ const uint8_t GAME_SHEIK_EXTERNAL_ID = 0x13; const uint32_t SPLIT_MESSAGE_INTERNAL_DATA_LEN = 512; -static uint8_t *data; +static uint8_t* data; -typedef struct { +typedef struct +{ // Every player update has its own rng seed because it might change in between // players uint32_t randomSeed; @@ -54,19 +55,20 @@ typedef struct { float cstickX; float cstickY; float trigger; - uint32_t buttons; // This will include multiple "buttons" pressed on special - // buttons. For example I think pressing z sets 3 bits + uint32_t buttons; // This will include multiple "buttons" pressed on special + // buttons. For example I think pressing z sets 3 bits // This is extra controller information - uint16_t physicalButtons; // A better representation of what a player is - // actually pressing + uint16_t physicalButtons; // A better representation of what a player is + // actually pressing float lTrigger; float rTrigger; uint8_t joystickXRaw; } PlayerFrameData; -typedef struct FrameData { +typedef struct FrameData +{ int32_t frame; uint32_t numSinceStart; bool randomSeedExists = false; @@ -76,7 +78,8 @@ typedef struct FrameData { std::unordered_map followers; } FrameData; -typedef struct { +typedef struct +{ // Static data uint8_t characterId; uint8_t characterColor; @@ -85,8 +88,9 @@ typedef struct { std::array nametag; } PlayerSettings; -typedef struct { - uint16_t stage; // Stage ID +typedef struct +{ + uint16_t stage; // Stage ID uint32_t randomSeed; std::array header; std::array ucfToggles; @@ -96,14 +100,15 @@ typedef struct { std::vector geckoCodes; } GameSettings; -typedef struct Game { +typedef struct Game +{ std::array version; - std::unordered_map framesByIndex; + std::unordered_map framesByIndex; std::vector> frames; GameSettings settings; bool areSettingsLoaded = false; - int32_t frameCount; // Current/last frame count + int32_t frameCount; // Current/last frame count // From OnGameEnd event uint8_t winCondition; @@ -111,23 +116,23 @@ typedef struct 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}}; +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(); - FrameData *GetFrame(int32_t frame); - FrameData *GetFrameAt(uint32_t pos); + FrameData* GetFrame(int32_t frame); + FrameData* GetFrameAt(uint32_t pos); int32_t GetLatestIndex(); - GameSettings *GetSettings(); + GameSettings* GetSettings(); bool DoesPlayerExist(int8_t port); bool IsProcessingComplete(); @@ -143,4 +148,4 @@ private: bool isProcessingComplete = false; void processData(); }; -} // namespace Slippi +} // namespace Slippi