refactor: update and format SlippiGame

This commit is contained in:
Nikhil Narayana 2022-06-15 22:11:28 -07:00
commit 9392df6991
2 changed files with 666 additions and 687 deletions

View file

@ -4,17 +4,14 @@
#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;
}
@ -22,10 +19,9 @@ namespace Slippi
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;
}
@ -35,38 +31,34 @@ namespace Slippi
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)
{
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);
}
@ -75,18 +67,15 @@ namespace Slippi
// 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<std::array<uint16_t, NAMETAG_SIZE>, 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);
}
}
@ -97,19 +86,40 @@ namespace Slippi
// Read isFrozenPS byte
game->settings.isFrozenPS = readByte(data, idx, maxSize, 0);
// Read minorScene byte
game->settings.minorScene = readByte(data, idx, maxSize, 0);
// Read majorScene byte
game->settings.majorScene = readByte(data, idx, maxSize, 0);
// Read display name for each player
std::array<std::array<uint8_t, DISPLAY_NAME_SIZE>, 4> playerDisplayNames;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < DISPLAY_NAME_SIZE; j++) {
playerDisplayNames[i][j] = readByte(data, idx, maxSize, 0);
}
}
// Read connectCodes
std::array<std::array<uint8_t, CONNECT_CODE_SIZE>, 4> playerConnectCodes;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < CONNECT_CODE_SIZE; j++) {
playerConnectCodes[i][j] = 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<uint32_t, Slippi::GAME_INFO_HEADER_SIZE> gameInfoHeader = game->settings.header;
for (int i = 0; i < 4; i++)
{
std::array<uint32_t, Slippi::GAME_INFO_HEADER_SIZE> 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;
}
@ -122,6 +132,8 @@ namespace Slippi
p.playerType = playerType;
p.characterColor = playerInfo & 0xFF;
p.nametag = playerNametags[i];
p.displayName = playerDisplayNames[i];
p.connectCode = playerConnectCodes[i];
// Add player settings to result
game->settings.players[i] = p;
@ -131,15 +143,12 @@ namespace Slippi
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
@ -147,17 +156,16 @@ namespace Slippi
}
}
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
@ -178,8 +186,7 @@ namespace Slippi
game->framesByIndex[frameCount] = frame;
}
void handlePreFrameUpdate(Game* game, uint32_t maxSize)
{
void handlePreFrameUpdate(Game *game, uint32_t maxSize) {
int idx = 0;
// Check frame count
@ -190,8 +197,7 @@ namespace Slippi
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;
@ -226,8 +232,7 @@ namespace Slippi
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);
}
@ -242,24 +247,21 @@ namespace Slippi
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))
{
if (game->framesByIndex.count(frameCount)) {
// If this frame already exists, get the current frame
frame = game->frames.back().get();
}
@ -272,29 +274,31 @@ namespace Slippi
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) {
game->areSettingsLoaded = true;
}
}
}
@ -307,27 +311,23 @@ namespace Slippi
game->lastFinalizedFrame = lastFinalizedFrame;
}
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;
}
@ -338,10 +338,8 @@ namespace Slippi
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;
}
@ -350,27 +348,27 @@ namespace Slippi
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];
uint32_t length =
byteBuf[0] << 24 | byteBuf[1] << 16 | byteBuf[2] << 8 | byteBuf[3];
return length;
}
std::unordered_map<uint8_t, uint32_t> getMessageSizes(std::ifstream* f, int position)
{
std::unordered_map<uint8_t, uint32_t> 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<uint8_t, uint32_t> messageSizes = { {EVENT_PAYLOAD_SIZES, payloadLength} };
std::unordered_map<uint8_t, uint32_t> messageSizes = {
{EVENT_PAYLOAD_SIZES, payloadLength}};
std::vector<char> 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
@ -384,10 +382,8 @@ namespace Slippi
return messageSizes;
}
void SlippiGame::processData()
{
if (isProcessingComplete)
{
void SlippiGame::processData() {
if (isProcessingComplete) {
// If we have finished processing this file, return
return;
}
@ -395,20 +391,17 @@ namespace Slippi
// 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);
@ -422,8 +415,7 @@ namespace Slippi
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);
@ -441,8 +433,7 @@ namespace Slippi
// log << "Size to read: " << sizeToRead << "\n";
// log << "Start Pos: " << startPos << "\n";
// log << "End Pos: " << endPos << "\n\n";
if (sizeToRead <= 0)
{
if (sizeToRead <= 0) {
return;
}
@ -450,8 +441,7 @@ namespace Slippi
file->read(&newData[0], sizeToRead);
int newDataPos = 0;
while (newDataPos < sizeToRead)
{
while (newDataPos < sizeToRead) {
auto command = newData[newDataPos];
auto payloadSize = asmEvents[command];
@ -460,8 +450,7 @@ namespace Slippi
// 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);
@ -474,21 +463,19 @@ namespace Slippi
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];
@ -497,8 +484,7 @@ namespace Slippi
}
}
switch (command)
{
switch (command) {
case EVENT_GAME_INIT:
handleGameInit(game.get(), payloadSize);
break;
@ -514,6 +500,9 @@ namespace Slippi
case EVENT_POST_FRAME_UPDATE:
handlePostFrameUpdate(game.get(), payloadSize);
break;
case EVENT_FRAME_END:
handleFrameEnd(game.get(), payloadSize);
break;
case EVENT_GAME_END:
handleGameEnd(game.get(), payloadSize);
isProcessingComplete = true;
@ -534,23 +523,24 @@ namespace Slippi
}
}
std::unique_ptr<SlippiGame> SlippiGame::FromFile(std::string path)
{
std::unique_ptr<SlippiGame> SlippiGame::FromFile(std::string path) {
auto result = std::make_unique<SlippiGame>();
result->game = std::make_unique<Game>();
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<std::codecvt_utf8<wchar_t>>().from_bytes(path);
result->file = std::make_unique<std::ifstream>(convertedPath, std::ios::in | std::ios::binary);
std::wstring convertedPath =
std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(path);
result->file = std::make_unique<std::ifstream>(
convertedPath, std::ios::in | std::ios::binary);
#else
result->file = std::make_unique<std::ifstream>(path, std::ios::in | std::ios::binary);
result->file =
std::make_unique<std::ifstream>(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;
}
@ -568,32 +558,24 @@ namespace Slippi
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);
return static_cast<bool>(game->framesByIndex.count(frame));
}
std::array<uint8_t, 4> SlippiGame::GetVersion()
{
return game->version;
}
std::array<uint8_t, 4> SlippiGame::GetVersion() { return game->version; }
std::string SlippiGame::GetVersionString()
{
std::string SlippiGame::GetVersionString() {
char version[30];
sprintf(version, "%d.%d.%d", game->version[0], game->version[1], game->version[2]);
sprintf(version, "%d.%d.%d", game->version[0], game->version[1],
game->version[2]);
return std::string(version);
}
@ -603,10 +585,8 @@ namespace Slippi
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;
}
// Get the frame we want
@ -623,8 +603,7 @@ namespace Slippi
return game->frameCount;
}
GameSettings* SlippiGame::GetSettings()
{
GameSettings *SlippiGame::GetSettings() {
processData();
return &game->settings;
}
@ -633,7 +612,5 @@ namespace Slippi
return game->settings.players.find(port) != game->settings.players.end();
}
uint8_t SlippiGame::GetGameEndMethod() {
return game->winCondition;
}
uint8_t SlippiGame::GetGameEndMethod() { return game->winCondition; }
} // namespace Slippi

View file

@ -34,7 +34,8 @@ namespace Slippi {
static uint8_t *data;
typedef struct {
// Every player update has its own rng seed because it might change in between players
// Every player update has its own rng seed because it might change in between
// players
uint32_t randomSeed;
uint8_t internalCharacterId;
@ -55,10 +56,12 @@ namespace Slippi {
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;
@ -120,11 +123,9 @@ namespace Slippi {
{EVENT_PRE_FRAME_UPDATE, 58},
{EVENT_POST_FRAME_UPDATE, 33},
{EVENT_GAME_END, 1},
{ EVENT_FRAME_START, 8 }
};
{EVENT_FRAME_START, 8}};
class SlippiGame
{
class SlippiGame {
public:
static std::unique_ptr<SlippiGame> FromFile(std::string path);
bool AreSettingsLoaded();
@ -139,6 +140,7 @@ namespace Slippi {
uint8_t GetGameEndMethod();
bool DoesPlayerExist(int8_t port);
bool IsProcessingComplete();
private:
std::unique_ptr<Game> game;
std::unique_ptr<std::ifstream> file;
@ -151,4 +153,4 @@ namespace Slippi {
bool isProcessingComplete = false;
void processData();
};
}
} // namespace Slippi