Merge pull request #65 from bmarcott/nmarcott_format

apply clang-format to all .cpp and .h files containing the word slippi
This commit is contained in:
David Liu 2021-01-19 18:26:07 -05:00 committed by GitHub
commit a557edb283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1616 additions and 1419 deletions

View file

@ -1,17 +1,20 @@
#include <string>
#include <codecvt> #include <codecvt>
#include <locale> #include <locale>
#include <string>
#include "SlippiGame.h" #include "SlippiGame.h"
namespace Slippi { namespace Slippi
{
//********************************************************************** //**********************************************************************
//* 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
uint8_t readByte(uint8_t* a, int& idx, uint32_t maxSize, uint8_t defaultValue) { // will read in the correct location
if (idx >= (int)maxSize) { uint8_t readByte(uint8_t* a, int& idx, uint32_t maxSize, uint8_t defaultValue)
{
if (idx >= (int)maxSize)
{
idx += 1; idx += 1;
return defaultValue; return defaultValue;
} }
@ -19,8 +22,10 @@ namespace Slippi {
return a[idx++]; return a[idx++];
} }
uint16_t readHalf(uint8_t* a, int& idx, uint32_t maxSize, uint16_t defaultValue) { uint16_t readHalf(uint8_t* a, int& idx, uint32_t maxSize, uint16_t defaultValue)
if (idx >= (int)maxSize) { {
if (idx >= (int)maxSize)
{
idx += 2; idx += 2;
return defaultValue; return defaultValue;
} }
@ -30,8 +35,10 @@ namespace Slippi {
return value; return value;
} }
uint32_t readWord(uint8_t* a, int& idx, uint32_t maxSize, uint32_t defaultValue) { uint32_t readWord(uint8_t* a, int& idx, uint32_t maxSize, uint32_t defaultValue)
if (idx >= (int)maxSize) { {
if (idx >= (int)maxSize)
{
idx += 4; idx += 4;
return defaultValue; return defaultValue;
} }
@ -41,21 +48,25 @@ namespace Slippi {
return value; 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)); uint32_t bytes = readWord(a, idx, maxSize, *(uint32_t*)(&defaultValue));
return *(float*)(&bytes); return *(float*)(&bytes);
} }
void handleGameInit(Game* game, uint32_t maxSize) { void handleGameInit(Game* game, uint32_t maxSize)
{
int idx = 0; int idx = 0;
// Read version number // 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); game->version[i] = readByte(data, idx, maxSize, 0);
} }
// Read entire game info header // 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); game->settings.header[i] = readWord(data, idx, maxSize, 0);
} }
@ -64,15 +75,18 @@ namespace Slippi {
// Read UCF toggle bytes // 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++) { for (int i = 0; i < UCF_TOGGLE_SIZE; i++)
{
uint32_t value = shouldRead ? readWord(data, idx, maxSize, 0) : 0; 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 // Read nametag for each player
std::array<std::array<uint16_t, NAMETAG_SIZE>, 4> playerNametags; std::array<std::array<uint16_t, NAMETAG_SIZE>, 4> playerNametags;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++)
for (int j = 0; j < NAMETAG_SIZE; j++) { {
for (int j = 0; j < NAMETAG_SIZE; j++)
{
playerNametags[i][j] = readHalf(data, idx, maxSize, 0); playerNametags[i][j] = readHalf(data, idx, maxSize, 0);
} }
} }
@ -86,13 +100,16 @@ namespace Slippi {
// Pull header data into struct // Pull header data into struct
int player1Pos = 24; // This is the index of the first players character info 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; std::array<uint32_t, Slippi::GAME_INFO_HEADER_SIZE> gameInfoHeader = game->settings.header;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++)
// this is the position in the array that this player's character info is stored {
// this is the position in the array that this player's character info is
// stored
int pos = player1Pos + (9 * i); int pos = player1Pos + (9 * i);
uint32_t playerInfo = gameInfoHeader[pos]; uint32_t playerInfo = gameInfoHeader[pos];
uint8_t playerType = (playerInfo & 0x00FF0000) >> 16; uint8_t playerType = (playerInfo & 0x00FF0000) >> 16;
if (playerType == 0x3) { if (playerType == 0x3)
{
// Player type 3 is an empty slot // Player type 3 is an empty slot
continue; continue;
} }
@ -114,13 +131,15 @@ namespace Slippi {
auto majorVersion = game->version[0]; auto majorVersion = game->version[0];
auto minorVersion = game->version[1]; 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 // After version 3.1.0 we added a dynamic gecko loading process. These
// are needed before starting the game. areSettingsLoaded will be set // are needed before starting the game. areSettingsLoaded will be set
// to true when they are received // to true when they are received
game->areSettingsLoaded = false; 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 // Indicate settings loaded immediately if after version 1.6.0
// Sheik game info was added in this version and so we no longer // Sheik game info was added in this version and so we no longer
// need to wait // need to wait
@ -128,7 +147,8 @@ namespace Slippi {
} }
} }
void handleGeckoList(Game* game, uint32_t maxSize) { void handleGeckoList(Game* game, uint32_t maxSize)
{
game->settings.geckoCodes.clear(); 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);
@ -136,7 +156,8 @@ namespace Slippi {
game->areSettingsLoaded = true; game->areSettingsLoaded = true;
} }
void handleFrameStart(Game* game, uint32_t maxSize) { void handleFrameStart(Game* game, uint32_t maxSize)
{
int idx = 0; int idx = 0;
// Check frame count // Check frame count
@ -157,7 +178,8 @@ namespace Slippi {
game->framesByIndex[frameCount] = frame; game->framesByIndex[frameCount] = frame;
} }
void handlePreFrameUpdate(Game* game, uint32_t maxSize) { void handlePreFrameUpdate(Game* game, uint32_t maxSize)
{
int idx = 0; int idx = 0;
// Check frame count // Check frame count
@ -168,7 +190,8 @@ namespace Slippi {
FrameData* frame = frameUniquePtr.get(); FrameData* frame = frameUniquePtr.get();
bool isNewFrame = true; bool isNewFrame = true;
if (game->framesByIndex.count(frameCount)) { if (game->framesByIndex.count(frameCount))
{
// If this frame already exists, get the current frame // If this frame already exists, get the current frame
frame = game->frames.back().get(); frame = game->frames.back().get();
isNewFrame = false; isNewFrame = false;
@ -203,7 +226,8 @@ namespace Slippi {
p.lTrigger = readFloat(data, idx, maxSize, 0); p.lTrigger = readFloat(data, idx, maxSize, 0);
p.rTrigger = 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); p.joystickXRaw = readByte(data, idx, maxSize, 0);
} }
@ -218,27 +242,31 @@ namespace Slippi {
target->operator[](playerSlot) = p; target->operator[](playerSlot) = p;
// Add frame to game // Add frame to game
if (isNewFrame) { if (isNewFrame)
{
frame->numSinceStart = game->frames.size(); frame->numSinceStart = game->frames.size();
game->frames.push_back(std::move(frameUniquePtr)); game->frames.push_back(std::move(frameUniquePtr));
game->framesByIndex[frameCount] = frame; game->framesByIndex[frameCount] = frame;
} }
} }
void handlePostFrameUpdate(Game* game, uint32_t maxSize) { void handlePostFrameUpdate(Game* game, uint32_t maxSize)
{
int idx = 0; int idx = 0;
// Check frame count // Check frame count
int32_t frameCount = readWord(data, idx, maxSize, 0); int32_t frameCount = readWord(data, idx, maxSize, 0);
FrameData* frame; FrameData* frame;
if (game->framesByIndex.count(frameCount)) { if (game->framesByIndex.count(frameCount))
{
// If this frame already exists, get the current frame // If this frame already exists, get the current frame
frame = game->frames.back().get(); frame = game->frames.back().get();
} }
// As soon as a post frame update happens, we know we have received all the inputs // As soon as a post frame update happens, we know we have received all the
// This is used to determine if a frame is ready to be used for a replay (for mirroring) // 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 playerSlot = readByte(data, idx, maxSize, 0);
@ -249,55 +277,67 @@ namespace Slippi {
p->internalCharacterId = readByte(data, idx, maxSize, 0); p->internalCharacterId = readByte(data, idx, maxSize, 0);
// Check if a player started as sheik and update // 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; game->settings.players[playerSlot].characterId = GAME_SHEIK_EXTERNAL_ID;
} }
// Set settings loaded if this is the last character // Set settings loaded if this is the last character
if (frameCount == GAME_FIRST_FRAME) { if (frameCount == GAME_FIRST_FRAME)
{
uint8_t lastPlayerIndex = 0; 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) { {
if (it->first <= lastPlayerIndex)
{
continue; continue;
} }
lastPlayerIndex = it->first; lastPlayerIndex = it->first;
} }
if (playerSlot >= lastPlayerIndex) { 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; 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 // This function gets the position where the raw data starts
int getRawDataPosition(std::ifstream* f) { int getRawDataPosition(std::ifstream* f)
{
char buffer[2]; char buffer[2];
f->seekg(0, std::ios::beg); f->seekg(0, std::ios::beg);
f->read(buffer, 2); f->read(buffer, 2);
if (buffer[0] == 0x36) { if (buffer[0] == 0x36)
{
return 0; return 0;
} }
if (buffer[0] != '{') { if (buffer[0] != '{')
{
// TODO: Do something here to cause an error // TODO: Do something here to cause an error
return 0; return 0;
} }
// TODO: Read ubjson file to find the "raw" element and return the start of it // TODO: Read ubjson file to find the "raw" element and return the start of it
// TODO: For now since raw is the first element the data will always start at 15 // TODO: For now since raw is the first element the data will always start at
// 15
return 15; return 15;
} }
uint32_t getRawDataLength(std::ifstream* f, int position, int fileSize) { uint32_t getRawDataLength(std::ifstream* f, int position, int fileSize)
if (position == 0) { {
if (position == 0)
{
return fileSize; return fileSize;
} }
@ -310,23 +350,23 @@ namespace Slippi {
return length; 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]; char buffer[2];
f->seekg(position, std::ios::beg); f->seekg(position, std::ios::beg);
f->read(buffer, 2); f->read(buffer, 2);
if (buffer[0] != EVENT_PAYLOAD_SIZES) { if (buffer[0] != EVENT_PAYLOAD_SIZES)
{
return {}; return {};
} }
int payloadLength = buffer[1]; int payloadLength = buffer[1];
std::unordered_map<uint8_t, uint32_t> messageSizes = { std::unordered_map<uint8_t, uint32_t> messageSizes = {{EVENT_PAYLOAD_SIZES, payloadLength}};
{ EVENT_PAYLOAD_SIZES, payloadLength }
};
std::vector<char> messageSizesBuffer(payloadLength - 1); std::vector<char> messageSizesBuffer(payloadLength - 1);
f->read(&messageSizesBuffer[0], 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]; uint8_t command = messageSizesBuffer[i];
// Extract the bytes in u8s. Without this the chars don't or together well // Extract the bytes in u8s. Without this the chars don't or together well
@ -340,8 +380,10 @@ namespace Slippi {
return messageSizes; return messageSizes;
} }
void SlippiGame::processData() { void SlippiGame::processData()
if (isProcessingComplete) { {
if (isProcessingComplete)
{
// If we have finished processing this file, return // If we have finished processing this file, return
return; return;
} }
@ -349,17 +391,20 @@ namespace Slippi {
// This function will process as much data as possible // This function will process as much data as possible
int startPos = (int)file->tellg(); int startPos = (int)file->tellg();
file->seekg(startPos); file->seekg(startPos);
if (startPos == 0) { if (startPos == 0)
{
file->seekg(0, std::ios::end); file->seekg(0, std::ios::end);
int len = (int)file->tellg(); int len = (int)file->tellg();
if (len < 2) { if (len < 2)
{
// If we can't read message sizes payload size yet, return // If we can't read message sizes payload size yet, return
return; return;
} }
int rawDataPos = getRawDataPosition(file.get()); int rawDataPos = getRawDataPosition(file.get());
int rawDataLen = len - rawDataPos; 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 // 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 // Reset to begining so that the startPos condition will be hit again
file->seekg(0); file->seekg(0);
@ -373,7 +418,8 @@ namespace Slippi {
file->read(buffer, 2); file->read(buffer, 2);
file->seekg(startPos); file->seekg(startPos);
auto messageSizesSize = (int)buffer[1]; auto messageSizesSize = (int)buffer[1];
if (rawDataLen < messageSizesSize) { if (rawDataLen < messageSizesSize)
{
// If we haven't received the full payload sizes message, return // If we haven't received the full payload sizes message, return
// Reset to begining so that the startPos condition will be hit again // Reset to begining so that the startPos condition will be hit again
file->seekg(0); file->seekg(0);
@ -391,7 +437,8 @@ namespace Slippi {
// log << "Size to read: " << sizeToRead << "\n"; // log << "Size to read: " << sizeToRead << "\n";
// log << "Start Pos: " << startPos << "\n"; // log << "Start Pos: " << startPos << "\n";
// log << "End Pos: " << endPos << "\n\n"; // log << "End Pos: " << endPos << "\n\n";
if (sizeToRead <= 0) { if (sizeToRead <= 0)
{
return; return;
} }
@ -399,7 +446,8 @@ namespace Slippi {
file->read(&newData[0], sizeToRead); file->read(&newData[0], sizeToRead);
int newDataPos = 0; int newDataPos = 0;
while (newDataPos < sizeToRead) { while (newDataPos < sizeToRead)
{
auto command = newData[newDataPos]; auto command = newData[newDataPos];
auto payloadSize = asmEvents[command]; auto payloadSize = asmEvents[command];
@ -408,7 +456,8 @@ namespace Slippi {
// log << "Command: " << buff << " | Payload Size: " << payloadSize << "\n"; // log << "Command: " << buff << " | Payload Size: " << payloadSize << "\n";
auto remainingLen = sizeToRead - newDataPos; 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 // Here we don't have enough data to read the whole payload
// Will be processed after getting more data (hopefully) // Will be processed after getting more data (hopefully)
file->seekg(-remainingLen, std::ios::cur); file->seekg(-remainingLen, std::ios::cur);
@ -421,7 +470,8 @@ namespace Slippi {
uint32_t outerPayloadSize = payloadSize; uint32_t outerPayloadSize = payloadSize;
// Handle a split message, combining in until we possess the entire message // Handle a split message, combining in until we possess the entire message
if (command == EVENT_SPLIT_MESSAGE) { if (command == EVENT_SPLIT_MESSAGE)
{
if (shouldResetSplitMessageBuf) if (shouldResetSplitMessageBuf)
{ {
splitMessageBuf.clear(); splitMessageBuf.clear();
@ -443,7 +493,8 @@ namespace Slippi {
} }
} }
switch (command) { switch (command)
{
case EVENT_GAME_INIT: case EVENT_GAME_INIT:
handleGameInit(game.get(), payloadSize); handleGameInit(game.get(), payloadSize);
break; break;
@ -479,7 +530,8 @@ 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>(); auto result = std::make_unique<SlippiGame>();
result->game = std::make_unique<Game>(); result->game = std::make_unique<Game>();
result->path = path; result->path = path;
@ -493,7 +545,8 @@ namespace Slippi {
#endif #endif
// result->log.open("log.txt"); // result->log.open("log.txt");
if (!result->file->is_open()) { if (!result->file->is_open())
{
return nullptr; return nullptr;
} }
@ -511,16 +564,19 @@ namespace Slippi {
return std::move(result); return std::move(result);
} }
bool SlippiGame::IsProcessingComplete() { bool SlippiGame::IsProcessingComplete()
{
return isProcessingComplete; return isProcessingComplete;
} }
bool SlippiGame::AreSettingsLoaded() { bool SlippiGame::AreSettingsLoaded()
{
processData(); processData();
return game->areSettingsLoaded; return game->areSettingsLoaded;
} }
bool SlippiGame::DoesFrameExist(int32_t frame) { bool SlippiGame::DoesFrameExist(int32_t frame)
{
processData(); processData();
return (bool)game->framesByIndex.count(frame); return (bool)game->framesByIndex.count(frame);
} }
@ -530,13 +586,16 @@ namespace Slippi {
return game->version; return game->version;
} }
FrameData* SlippiGame::GetFrame(int32_t frame) { FrameData* SlippiGame::GetFrame(int32_t frame)
{
// Get the frame we want // Get the frame we want
return game->framesByIndex.at(frame); return game->framesByIndex.at(frame);
} }
FrameData* SlippiGame::GetFrameAt(uint32_t pos) { FrameData* SlippiGame::GetFrameAt(uint32_t pos)
if (pos >= game->frames.size()) { {
if (pos >= game->frames.size())
{
return nullptr; return nullptr;
} }
@ -544,17 +603,20 @@ namespace Slippi {
return game->frames[pos].get(); return game->frames[pos].get();
} }
int32_t SlippiGame::GetLatestIndex() { int32_t SlippiGame::GetLatestIndex()
{
processData(); processData();
return game->frameCount; return game->frameCount;
} }
GameSettings* SlippiGame::GetSettings() { GameSettings* SlippiGame::GetSettings()
{
processData(); processData();
return &game->settings; 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(); return game->settings.players.find(port) != game->settings.players.end();
} }
} } // namespace Slippi

View file

@ -1,14 +1,15 @@
#pragma once #pragma once
#include <string>
#include <array> #include <array>
#include <vector>
#include <unordered_map>
#include <iostream>
#include <fstream> #include <fstream>
#include <iostream>
#include <memory> #include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace Slippi { namespace Slippi
{
const uint8_t EVENT_SPLIT_MESSAGE = 0x10; const uint8_t EVENT_SPLIT_MESSAGE = 0x10;
const uint8_t EVENT_PAYLOAD_SIZES = 0x35; const uint8_t EVENT_PAYLOAD_SIZES = 0x35;
const uint8_t EVENT_GAME_INIT = 0x36; const uint8_t EVENT_GAME_INIT = 0x36;
@ -30,8 +31,10 @@ namespace Slippi {
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 {
// Every player update has its own rng seed because it might change in between
// players
uint32_t randomSeed; uint32_t randomSeed;
uint8_t internalCharacterId; uint8_t internalCharacterId;
@ -52,17 +55,20 @@ namespace Slippi {
float cstickX; float cstickX;
float cstickY; float cstickY;
float trigger; 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 // 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 lTrigger;
float rTrigger; float rTrigger;
uint8_t joystickXRaw; uint8_t joystickXRaw;
} PlayerFrameData; } PlayerFrameData;
typedef struct FrameData { typedef struct FrameData
{
int32_t frame; int32_t frame;
uint32_t numSinceStart; uint32_t numSinceStart;
bool randomSeedExists = false; bool randomSeedExists = false;
@ -72,7 +78,8 @@ namespace Slippi {
std::unordered_map<uint8_t, PlayerFrameData> followers; std::unordered_map<uint8_t, PlayerFrameData> followers;
} FrameData; } FrameData;
typedef struct { typedef struct
{
// Static data // Static data
uint8_t characterId; uint8_t characterId;
uint8_t characterColor; uint8_t characterColor;
@ -81,7 +88,8 @@ namespace Slippi {
std::array<uint16_t, NAMETAG_SIZE> nametag; std::array<uint16_t, NAMETAG_SIZE> nametag;
} PlayerSettings; } PlayerSettings;
typedef struct { typedef struct
{
uint16_t stage; // Stage ID uint16_t stage; // Stage ID
uint32_t randomSeed; uint32_t randomSeed;
std::array<uint32_t, GAME_INFO_HEADER_SIZE> header; std::array<uint32_t, GAME_INFO_HEADER_SIZE> header;
@ -92,7 +100,8 @@ namespace Slippi {
std::vector<uint8_t> geckoCodes; std::vector<uint8_t> geckoCodes;
} GameSettings; } GameSettings;
typedef struct Game { typedef struct Game
{
std::array<uint8_t, 4> version; std::array<uint8_t, 4> version;
std::unordered_map<int32_t, FrameData*> framesByIndex; std::unordered_map<int32_t, FrameData*> framesByIndex;
std::vector<std::unique_ptr<FrameData>> frames; std::vector<std::unique_ptr<FrameData>> frames;
@ -107,13 +116,11 @@ namespace Slippi {
// TODO: This shouldn't be static. Doesn't matter too much atm because we always // TODO: This shouldn't be static. Doesn't matter too much atm because we always
// TODO: only read one file at a time // TODO: only read one file at a time
static std::unordered_map<uint8_t, uint32_t> asmEvents = { static std::unordered_map<uint8_t, uint32_t> asmEvents = {{EVENT_GAME_INIT, 320},
{ EVENT_GAME_INIT, 320 },
{EVENT_PRE_FRAME_UPDATE, 58}, {EVENT_PRE_FRAME_UPDATE, 58},
{EVENT_POST_FRAME_UPDATE, 33}, {EVENT_POST_FRAME_UPDATE, 33},
{EVENT_GAME_END, 1}, {EVENT_GAME_END, 1},
{ EVENT_FRAME_START, 8 } {EVENT_FRAME_START, 8}};
};
class SlippiGame class SlippiGame
{ {
@ -128,6 +135,7 @@ namespace Slippi {
GameSettings* GetSettings(); GameSettings* GetSettings();
bool DoesPlayerExist(int8_t port); bool DoesPlayerExist(int8_t port);
bool IsProcessingComplete(); bool IsProcessingComplete();
private: private:
std::unique_ptr<Game> game; std::unique_ptr<Game> game;
std::unique_ptr<std::ifstream> file; std::unique_ptr<std::ifstream> file;
@ -140,4 +148,4 @@ namespace Slippi {
bool isProcessingComplete = false; bool isProcessingComplete = false;
void processData(); void processData();
}; };
} } // namespace Slippi

View file

@ -2,10 +2,9 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm>
#include <semver/include/semver200.h> #include <semver/include/semver200.h>
#include <utility> // std::move #include <utility> // std::move
#include <algorithm>
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -17,17 +16,17 @@
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Common/Version.h" #include "Common/Version.h"
#include "Core/Core.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/Debugger/Debugger_SymbolMap.h" #include "Core/Debugger/Debugger_SymbolMap.h"
#include "Core/Host.h"
#include "Core/HW/EXI/EXI_DeviceSlippi.h" #include "Core/HW/EXI/EXI_DeviceSlippi.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
#include "Core/Host.h"
#include "Core/NetPlayClient.h" #include "Core/NetPlayClient.h"
#include "Core/Slippi/SlippiReplayComm.h"
#include "Core/Slippi/SlippiPlayback.h" #include "Core/Slippi/SlippiPlayback.h"
#include "Core/Slippi/SlippiReplayComm.h"
#include "Core/State.h" #include "Core/State.h"
#define FRAME_INTERVAL 900 #define FRAME_INTERVAL 900
@ -43,12 +42,13 @@ extern std::unique_ptr<SlippiReplayComm> g_replayComm;
bool isLocalConnected = false; bool isLocalConnected = false;
#endif #endif
namespace ExpansionInterface { namespace ExpansionInterface
{
static std::unordered_map<u8, std::string> slippi_names; static std::unordered_map<u8, std::string> slippi_names;
static std::unordered_map<u8, std::string> slippi_connect_codes; static std::unordered_map<u8, std::string> slippi_connect_codes;
template <typename T> bool isFutureReady(std::future<T>& t) template <typename T>
bool isFutureReady(std::future<T>& t)
{ {
return t.wait_for(std::chrono::seconds(0)) == std::future_status::ready; return t.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
} }
@ -120,60 +120,76 @@ CEXISlippi::CEXISlippi()
// MnMaAll.usd // MnMaAll.usd
std::string origStr; std::string origStr;
std::string modifiedStr; std::string modifiedStr;
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.usd", origStr); File::ReadFileToString(
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll-new.usd", "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.usd", origStr);
modifiedStr); File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll-new.usd", modifiedStr);
std::vector<u8> orig(origStr.begin(), origStr.end()); std::vector<u8> orig(origStr.begin(), origStr.end());
std::vector<u8> modified(modifiedStr.begin(), modifiedStr.end()); std::vector<u8> modified(modifiedStr.begin(), modifiedStr.end());
auto diff = processDiff(orig, modified); auto diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.usd.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.usd.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnMaAll.usd.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnMaAll.usd.diff");
// MnExtAll.usd // MnExtAll.usd
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.usd", origStr); File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.usd",
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll-new.usd", modifiedStr); origStr);
File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll-new.usd", modifiedStr);
orig = std::vector<u8>(origStr.begin(), origStr.end()); orig = std::vector<u8>(origStr.begin(), origStr.end());
modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end()); modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end());
diff = processDiff(orig, modified); diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.usd.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.usd.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnExtAll.usd.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnExtAll.usd.diff");
// SdMenu.usd // SdMenu.usd
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.usd", origStr); File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.usd",
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu-new.usd", modifiedStr); origStr);
File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu-new.usd", modifiedStr);
orig = std::vector<u8>(origStr.begin(), origStr.end()); orig = std::vector<u8>(origStr.begin(), origStr.end());
modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end()); modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end());
diff = processDiff(orig, modified); diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.usd.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.usd.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\SdMenu.usd.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\SdMenu.usd.diff");
// Japanese Files // Japanese Files
// MnMaAll.dat // MnMaAll.dat
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.dat", origStr); File::ReadFileToString(
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll-new.dat", "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.dat", origStr);
modifiedStr); File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll-new.dat", modifiedStr);
orig = std::vector<u8>(origStr.begin(), origStr.end()); orig = std::vector<u8>(origStr.begin(), origStr.end());
modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end()); modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end());
diff = processDiff(orig, modified); diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.dat.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\MnMaAll.dat.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnMaAll.dat.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnMaAll.dat.diff");
// MnExtAll.dat // MnExtAll.dat
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.dat", origStr); File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.dat",
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll-new.dat", modifiedStr); origStr);
File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll-new.dat", modifiedStr);
orig = std::vector<u8>(origStr.begin(), origStr.end()); orig = std::vector<u8>(origStr.begin(), origStr.end());
modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end()); modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end());
diff = processDiff(orig, modified); diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.dat.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\CSS\\MnExtAll.dat.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnExtAll.dat.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\MnExtAll.dat.diff");
// SdMenu.dat // SdMenu.dat
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.dat", origStr); File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.dat",
File::ReadFileToString("C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu-new.dat", modifiedStr); origStr);
File::ReadFileToString(
"C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu-new.dat", modifiedStr);
orig = std::vector<u8>(origStr.begin(), origStr.end()); orig = std::vector<u8>(origStr.begin(), origStr.end());
modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end()); modified = std::vector<u8>(modifiedStr.begin(), modifiedStr.end());
diff = processDiff(orig, modified); diff = processDiff(orig, modified);
File::WriteStringToFile(diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.dat.diff"); File::WriteStringToFile(
diff, "C:\\Users\\Jas\\Documents\\Melee\\Textures\\Slippi\\MainMenu\\SdMenu.dat.diff");
File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\SdMenu.dat.diff"); File::WriteStringToFile(diff, "C:\\Dolphin\\IshiiDev\\Sys\\GameFiles\\GALE01\\SdMenu.dat.diff");
// TEMP - Restore orig // TEMP - Restore orig
@ -266,7 +282,8 @@ std::vector<u8> CEXISlippi::generateMetadata()
std::vector<char> dateTimeBuf(dateTimeStrLength); std::vector<char> dateTimeBuf(dateTimeStrLength);
strftime(&dateTimeBuf[0], dateTimeStrLength, "%FT%TZ", gmtime(&gameStartTime)); strftime(&dateTimeBuf[0], dateTimeStrLength, "%FT%TZ", gmtime(&gameStartTime));
dateTimeBuf.pop_back(); // Removes the \0 from the back of string dateTimeBuf.pop_back(); // Removes the \0 from the back of string
metadata.insert(metadata.end(), { 'U', 7, 's', 't', 'a', 'r', 't', 'A', 't', 'S', 'U', (u8)dateTimeBuf.size() }); metadata.insert(metadata.end(),
{'U', 7, 's', 't', 'a', 'r', 't', 'A', 't', 'S', 'U', (u8)dateTimeBuf.size()});
metadata.insert(metadata.end(), dateTimeBuf.begin(), dateTimeBuf.end()); metadata.insert(metadata.end(), dateTimeBuf.begin(), dateTimeBuf.end());
// Add game duration // Add game duration
@ -314,7 +331,8 @@ std::vector<u8> CEXISlippi::generateMetadata()
metadata.push_back('}'); // close names metadata.push_back('}'); // close names
// Add character element for this player // Add character element for this player
metadata.insert(metadata.end(), { 'U', 10, 'c', 'h', 'a', 'r', 'a', 'c', 't', 'e', 'r', 's', '{' }); metadata.insert(metadata.end(),
{'U', 10, 'c', 'h', 'a', 'r', 'a', 'c', 't', 'e', 'r', 's', '{'});
for (auto it2 = playerCharacterUsage.begin(); it2 != playerCharacterUsage.end(); ++it2) for (auto it2 = playerCharacterUsage.begin(); it2 != playerCharacterUsage.end(); ++it2)
{ {
metadata.push_back('U'); metadata.push_back('U');
@ -333,8 +351,8 @@ std::vector<u8> CEXISlippi::generateMetadata()
metadata.push_back('}'); metadata.push_back('}');
// Indicate this was played on dolphin // Indicate this was played on dolphin
metadata.insert(metadata.end(), metadata.insert(metadata.end(), {'U', 8, 'p', 'l', 'a', 'y', 'e', 'd', 'O', 'n',
{ 'U', 8, 'p', 'l', 'a', 'y', 'e', 'd', 'O', 'n', 'S', 'U', 7, 'd', 'o', 'l', 'p', 'h', 'i', 'n' }); 'S', 'U', 7, 'd', 'o', 'l', 'p', 'h', 'i', 'n'});
metadata.push_back('}'); metadata.push_back('}');
return metadata; return metadata;
@ -600,7 +618,8 @@ void CEXISlippi::prepareGameInfo(u8* payload)
{ {
bkpPos += 1; bkpPos += 1;
} }
playbackSavestatePayload.insert(playbackSavestatePayload.end(), payload, payload + (bkpPos * 8 + 4)); playbackSavestatePayload.insert(playbackSavestatePayload.end(), payload,
payload + (bkpPos * 8 + 4));
Slippi::GameSettings* settings = m_current_game->GetSettings(); Slippi::GameSettings* settings = m_current_game->GetSettings();
@ -732,187 +751,219 @@ void CEXISlippi::prepareGeckoList()
// TODO: How do I move this somewhere else? // TODO: How do I move this somewhere else?
// This contains all of the codes required to play legacy replays (UCF, PAL, Frz Stadium) // This contains all of the codes required to play legacy replays (UCF, PAL, Frz Stadium)
static std::vector<u8> defaultCodeList = { static std::vector<u8> defaultCodeList = {
0xC2, 0x0C, 0x9A, 0x44, 0x00, 0x00, 0x00, 0x2F, // #External/UCF + Arduino Toggle UI/UCF/UCF 0.74 0xC2, 0x0C, 0x9A, 0x44, 0x00, 0x00, 0x00, 0x2F, // #External/UCF + Arduino Toggle UI/UCF/UCF
// Dashback - Check for Toggle.asm // 0.74 Dashback - Check for Toggle.asm
0xD0, 0x1F, 0x00, 0x2C, 0x88, 0x9F, 0x06, 0x18, 0x38, 0x62, 0xF2, 0x28, 0x7C, 0x63, 0x20, 0xAE, 0x2C, 0x03, 0xD0, 0x1F, 0x00, 0x2C, 0x88, 0x9F, 0x06, 0x18, 0x38, 0x62, 0xF2, 0x28, 0x7C, 0x63, 0x20,
0x00, 0x01, 0x41, 0x82, 0x00, 0x14, 0x38, 0x62, 0xF2, 0x2C, 0x7C, 0x63, 0x20, 0xAE, 0x2C, 0x03, 0x00, 0x01, 0xAE, 0x2C, 0x03, 0x00, 0x01, 0x41, 0x82, 0x00, 0x14, 0x38, 0x62, 0xF2, 0x2C, 0x7C, 0x63,
0x40, 0x82, 0x01, 0x50, 0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x20, 0xAE, 0x2C, 0x03, 0x00, 0x01, 0x40, 0x82, 0x01, 0x50, 0x7C, 0x08, 0x02, 0xA6, 0x90,
0x00, 0x08, 0x48, 0x00, 0x01, 0x21, 0x7F, 0xC8, 0x02, 0xA6, 0xC0, 0x3F, 0x08, 0x94, 0xC0, 0x5E, 0x00, 0x00, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x00, 0x08, 0x48, 0x00, 0x01, 0x21,
0xFC, 0x01, 0x10, 0x40, 0x40, 0x82, 0x01, 0x18, 0x80, 0x8D, 0xAE, 0xB4, 0xC0, 0x3F, 0x06, 0x20, 0xFC, 0x20, 0x7F, 0xC8, 0x02, 0xA6, 0xC0, 0x3F, 0x08, 0x94, 0xC0, 0x5E, 0x00, 0x00, 0xFC, 0x01, 0x10,
0x0A, 0x10, 0xC0, 0x44, 0x00, 0x3C, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x80, 0x01, 0x00, 0x88, 0x7F, 0x06, 0x70, 0x40, 0x40, 0x82, 0x01, 0x18, 0x80, 0x8D, 0xAE, 0xB4, 0xC0, 0x3F, 0x06, 0x20, 0xFC, 0x20,
0x2C, 0x03, 0x00, 0x02, 0x40, 0x80, 0x00, 0xF4, 0x88, 0x7F, 0x22, 0x1F, 0x54, 0x60, 0x07, 0x39, 0x40, 0x82, 0x0A, 0x10, 0xC0, 0x44, 0x00, 0x3C, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x80, 0x01, 0x00, 0x88,
0x00, 0xE8, 0x3C, 0x60, 0x80, 0x4C, 0x60, 0x63, 0x1F, 0x78, 0x8B, 0xA3, 0x00, 0x01, 0x38, 0x7D, 0xFF, 0xFE, 0x7F, 0x06, 0x70, 0x2C, 0x03, 0x00, 0x02, 0x40, 0x80, 0x00, 0xF4, 0x88, 0x7F, 0x22, 0x1F,
0x88, 0x9F, 0x06, 0x18, 0x48, 0x00, 0x00, 0x8D, 0x7C, 0x7C, 0x1B, 0x78, 0x7F, 0xA3, 0xEB, 0x78, 0x88, 0x9F, 0x54, 0x60, 0x07, 0x39, 0x40, 0x82, 0x00, 0xE8, 0x3C, 0x60, 0x80, 0x4C, 0x60, 0x63, 0x1F,
0x06, 0x18, 0x48, 0x00, 0x00, 0x7D, 0x7C, 0x7C, 0x18, 0x50, 0x7C, 0x63, 0x19, 0xD6, 0x2C, 0x03, 0x15, 0xF9, 0x78, 0x8B, 0xA3, 0x00, 0x01, 0x38, 0x7D, 0xFF, 0xFE, 0x88, 0x9F, 0x06, 0x18, 0x48, 0x00,
0x40, 0x81, 0x00, 0xB0, 0x38, 0x00, 0x00, 0x01, 0x90, 0x1F, 0x23, 0x58, 0x90, 0x1F, 0x23, 0x40, 0x80, 0x9F, 0x00, 0x8D, 0x7C, 0x7C, 0x1B, 0x78, 0x7F, 0xA3, 0xEB, 0x78, 0x88, 0x9F, 0x06, 0x18, 0x48,
0x00, 0x04, 0x2C, 0x04, 0x00, 0x0A, 0x40, 0xA2, 0x00, 0x98, 0x88, 0x7F, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x01, 0x00, 0x00, 0x7D, 0x7C, 0x7C, 0x18, 0x50, 0x7C, 0x63, 0x19, 0xD6, 0x2C, 0x03, 0x15, 0xF9,
0x3D, 0x80, 0x80, 0x03, 0x61, 0x8C, 0x41, 0x8C, 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x2C, 0x03, 0x40, 0x81, 0x00, 0xB0, 0x38, 0x00, 0x00, 0x01, 0x90, 0x1F, 0x23, 0x58, 0x90, 0x1F, 0x23,
0x00, 0x00, 0x41, 0x82, 0x00, 0x78, 0x80, 0x83, 0x00, 0x2C, 0x80, 0x84, 0x1E, 0xCC, 0xC0, 0x3F, 0x00, 0x2C, 0x40, 0x80, 0x9F, 0x00, 0x04, 0x2C, 0x04, 0x00, 0x0A, 0x40, 0xA2, 0x00, 0x98, 0x88, 0x7F,
0xD0, 0x24, 0x00, 0x18, 0xC0, 0x5E, 0x00, 0x04, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x81, 0x00, 0x0C, 0x38, 0x60, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x01, 0x3D, 0x80, 0x80, 0x03, 0x61, 0x8C, 0x41, 0x8C, 0x7D,
0x00, 0x80, 0x48, 0x00, 0x00, 0x08, 0x38, 0x60, 0x00, 0x7F, 0x98, 0x64, 0x00, 0x06, 0x48, 0x00, 0x00, 0x48, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x78,
0x7C, 0x85, 0x23, 0x78, 0x38, 0x63, 0xFF, 0xFF, 0x2C, 0x03, 0x00, 0x00, 0x40, 0x80, 0x00, 0x08, 0x38, 0x63, 0x80, 0x83, 0x00, 0x2C, 0x80, 0x84, 0x1E, 0xCC, 0xC0, 0x3F, 0x00, 0x2C, 0xD0, 0x24, 0x00,
0x00, 0x05, 0x3C, 0x80, 0x80, 0x46, 0x60, 0x84, 0xB1, 0x08, 0x1C, 0x63, 0x00, 0x30, 0x7C, 0x84, 0x1A, 0x14, 0x18, 0xC0, 0x5E, 0x00, 0x04, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x81, 0x00, 0x0C, 0x38, 0x60,
0x1C, 0x65, 0x00, 0x0C, 0x7C, 0x84, 0x1A, 0x14, 0x88, 0x64, 0x00, 0x02, 0x7C, 0x63, 0x07, 0x74, 0x4E, 0x80, 0x00, 0x80, 0x48, 0x00, 0x00, 0x08, 0x38, 0x60, 0x00, 0x7F, 0x98, 0x64, 0x00, 0x06, 0x48,
0x00, 0x20, 0x4E, 0x80, 0x00, 0x21, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBA, 0x81, 0x00, 0x08, 0x00, 0x00, 0x48, 0x7C, 0x85, 0x23, 0x78, 0x38, 0x63, 0xFF, 0xFF, 0x2C, 0x03, 0x00, 0x00,
0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x09, 0x40, 0x80, 0x00, 0x08, 0x38, 0x63, 0x00, 0x05, 0x3C, 0x80, 0x80, 0x46, 0x60, 0x84, 0xB1,
0x98, 0xA4, 0x00, 0x00, 0x00, 0x2B, // #External/UCF + Arduino Toggle UI/UCF/UCF 0x08, 0x1C, 0x63, 0x00, 0x30, 0x7C, 0x84, 0x1A, 0x14, 0x1C, 0x65, 0x00, 0x0C, 0x7C, 0x84,
0x1A, 0x14, 0x88, 0x64, 0x00, 0x02, 0x7C, 0x63, 0x07, 0x74, 0x4E, 0x80, 0x00, 0x20, 0x4E,
0x80, 0x00, 0x21, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBA, 0x81, 0x00, 0x08,
0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0x00, 0x00, 0x00,
0x00, 0xC2, 0x09, 0x98, 0xA4, 0x00, 0x00, 0x00,
0x2B, // #External/UCF + Arduino Toggle UI/UCF/UCF
// 0.74 Shield Drop - Check for Toggle.asm // 0.74 Shield Drop - Check for Toggle.asm
0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x00, 0x08, 0x7C, 0x7E, 0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x00,
0x1B, 0x78, 0x83, 0xFE, 0x00, 0x2C, 0x48, 0x00, 0x01, 0x01, 0x7F, 0xA8, 0x02, 0xA6, 0x88, 0x9F, 0x06, 0x18, 0x08, 0x7C, 0x7E, 0x1B, 0x78, 0x83, 0xFE, 0x00, 0x2C, 0x48, 0x00, 0x01, 0x01, 0x7F, 0xA8,
0x38, 0x62, 0xF2, 0x28, 0x7C, 0x63, 0x20, 0xAE, 0x2C, 0x03, 0x00, 0x01, 0x41, 0x82, 0x00, 0x14, 0x38, 0x62, 0x02, 0xA6, 0x88, 0x9F, 0x06, 0x18, 0x38, 0x62, 0xF2, 0x28, 0x7C, 0x63, 0x20, 0xAE, 0x2C,
0xF2, 0x30, 0x7C, 0x63, 0x20, 0xAE, 0x2C, 0x03, 0x00, 0x01, 0x40, 0x82, 0x00, 0xF8, 0xC0, 0x3F, 0x06, 0x3C, 0x03, 0x00, 0x01, 0x41, 0x82, 0x00, 0x14, 0x38, 0x62, 0xF2, 0x30, 0x7C, 0x63, 0x20, 0xAE,
0x80, 0x6D, 0xAE, 0xB4, 0xC0, 0x03, 0x03, 0x14, 0xFC, 0x01, 0x00, 0x40, 0x40, 0x81, 0x00, 0xE4, 0xC0, 0x3F, 0x2C, 0x03, 0x00, 0x01, 0x40, 0x82, 0x00, 0xF8, 0xC0, 0x3F, 0x06, 0x3C, 0x80, 0x6D, 0xAE,
0x06, 0x20, 0x48, 0x00, 0x00, 0x71, 0xD0, 0x21, 0x00, 0x90, 0xC0, 0x3F, 0x06, 0x24, 0x48, 0x00, 0x00, 0x65, 0xB4, 0xC0, 0x03, 0x03, 0x14, 0xFC, 0x01, 0x00, 0x40, 0x40, 0x81, 0x00, 0xE4, 0xC0, 0x3F,
0xC0, 0x41, 0x00, 0x90, 0xEC, 0x42, 0x00, 0xB2, 0xEC, 0x21, 0x00, 0x72, 0xEC, 0x21, 0x10, 0x2A, 0xC0, 0x5D, 0x06, 0x20, 0x48, 0x00, 0x00, 0x71, 0xD0, 0x21, 0x00, 0x90, 0xC0, 0x3F, 0x06, 0x24, 0x48,
0x00, 0x0C, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x80, 0x00, 0xB4, 0x88, 0x9F, 0x06, 0x70, 0x2C, 0x04, 0x00, 0x03, 0x00, 0x00, 0x65, 0xC0, 0x41, 0x00, 0x90, 0xEC, 0x42, 0x00, 0xB2, 0xEC, 0x21, 0x00, 0x72,
0x40, 0x81, 0x00, 0xA8, 0xC0, 0x1D, 0x00, 0x10, 0xC0, 0x3F, 0x06, 0x24, 0xFC, 0x00, 0x08, 0x40, 0x40, 0x80, 0xEC, 0x21, 0x10, 0x2A, 0xC0, 0x5D, 0x00, 0x0C, 0xFC, 0x01, 0x10, 0x40, 0x41, 0x80, 0x00,
0x00, 0x98, 0xBA, 0x81, 0x00, 0x08, 0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0xB4, 0x88, 0x9F, 0x06, 0x70, 0x2C, 0x04, 0x00, 0x03, 0x40, 0x81, 0x00, 0xA8, 0xC0, 0x1D,
0x80, 0x61, 0x00, 0x1C, 0x83, 0xE1, 0x00, 0x14, 0x38, 0x21, 0x00, 0x18, 0x38, 0x63, 0x00, 0x08, 0x7C, 0x68, 0x00, 0x10, 0xC0, 0x3F, 0x06, 0x24, 0xFC, 0x00, 0x08, 0x40, 0x40, 0x80, 0x00, 0x98, 0xBA,
0x03, 0xA6, 0x4E, 0x80, 0x00, 0x20, 0xFC, 0x00, 0x0A, 0x10, 0xC0, 0x3D, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x72, 0x81, 0x00, 0x08, 0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6,
0xC0, 0x3D, 0x00, 0x04, 0xEC, 0x00, 0x08, 0x28, 0xFC, 0x00, 0x00, 0x1E, 0xD8, 0x01, 0x00, 0x80, 0x80, 0x61, 0x80, 0x61, 0x00, 0x1C, 0x83, 0xE1, 0x00, 0x14, 0x38, 0x21, 0x00, 0x18, 0x38, 0x63, 0x00,
0x00, 0x84, 0x38, 0x63, 0x00, 0x02, 0x3C, 0x00, 0x43, 0x30, 0xC8, 0x5D, 0x00, 0x14, 0x6C, 0x63, 0x80, 0x00, 0x08, 0x7C, 0x68, 0x03, 0xA6, 0x4E, 0x80, 0x00, 0x20, 0xFC, 0x00, 0x0A, 0x10, 0xC0, 0x3D,
0x90, 0x01, 0x00, 0x80, 0x90, 0x61, 0x00, 0x84, 0xC8, 0x21, 0x00, 0x80, 0xEC, 0x01, 0x10, 0x28, 0xC0, 0x3D, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x72, 0xC0, 0x3D, 0x00, 0x04, 0xEC, 0x00, 0x08, 0x28, 0xFC,
0x00, 0x00, 0xEC, 0x20, 0x08, 0x24, 0x4E, 0x80, 0x00, 0x20, 0x4E, 0x80, 0x00, 0x21, 0x42, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xD8, 0x01, 0x00, 0x80, 0x80, 0x61, 0x00, 0x84, 0x38, 0x63, 0x00, 0x02,
0x37, 0x27, 0x00, 0x00, 0x43, 0x30, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xBF, 0x4C, 0xCC, 0xCD, 0x43, 0x30, 0x3C, 0x00, 0x43, 0x30, 0xC8, 0x5D, 0x00, 0x14, 0x6C, 0x63, 0x80, 0x00, 0x90, 0x01, 0x00,
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xC3, 0xF3, 0x78, 0x7F, 0xE4, 0xFB, 0x78, 0xBA, 0x81, 0x00, 0x08, 0x80, 0x90, 0x61, 0x00, 0x84, 0xC8, 0x21, 0x00, 0x80, 0xEC, 0x01, 0x10, 0x28, 0xC0, 0x3D,
0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x20, 0x08, 0x24, 0x4E, 0x80, 0x00, 0x20, 0x4E, 0x80, 0x00, 0x21, 0x42,
0x00, 0x00, 0xC2, 0x16, 0xE7, 0x50, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x37, 0x27, 0x00, 0x00, 0x43, 0x30, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
0xBF, 0x4C, 0xCC, 0xCD, 0x43, 0x30, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0xC3, 0xF3,
0x78, 0x7F, 0xE4, 0xFB, 0x78, 0xBA, 0x81, 0x00, 0x08, 0x80, 0x01, 0x00, 0xB4, 0x38, 0x21,
0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2,
0x16, 0xE7, 0x50, 0x00, 0x00, 0x00,
0x33, // #Common/StaticPatches/ToggledStaticOverwrites.asm 0x33, // #Common/StaticPatches/ToggledStaticOverwrites.asm
0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x14, 0x48, 0x00, 0x00, 0x75, 0x7C, 0x68, 0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x14, 0x48, 0x00, 0x00,
0x02, 0xA6, 0x48, 0x00, 0x01, 0x3D, 0x48, 0x00, 0x00, 0x14, 0x48, 0x00, 0x00, 0x95, 0x7C, 0x68, 0x02, 0xA6, 0x75, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x3D, 0x48, 0x00, 0x00, 0x14, 0x48, 0x00,
0x48, 0x00, 0x01, 0x2D, 0x48, 0x00, 0x00, 0x04, 0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x95, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x2D, 0x48, 0x00, 0x00, 0x04, 0x88,
0x00, 0x14, 0x48, 0x00, 0x00, 0xB9, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x11, 0x48, 0x00, 0x00, 0x10, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x14, 0x48, 0x00, 0x00, 0xB9,
0x48, 0x00, 0x00, 0xC9, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x01, 0x88, 0x62, 0xF2, 0x3C, 0x2C, 0x03, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x11, 0x48, 0x00, 0x00, 0x10, 0x48, 0x00, 0x00,
0x00, 0x00, 0x41, 0x82, 0x00, 0x14, 0x48, 0x00, 0x00, 0xD1, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x00, 0xE9, 0xC9, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x01, 0x01, 0x88, 0x62, 0xF2, 0x3C, 0x2C, 0x03,
0x48, 0x00, 0x01, 0x04, 0x48, 0x00, 0x00, 0xD1, 0x7C, 0x68, 0x02, 0xA6, 0x48, 0x00, 0x00, 0xD9, 0x48, 0x00, 0x00, 0x00, 0x41, 0x82, 0x00, 0x14, 0x48, 0x00, 0x00, 0xD1, 0x7C, 0x68, 0x02, 0xA6, 0x48,
0x00, 0xF4, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x3C, 0xE4, 0xD4, 0x00, 0x24, 0x04, 0x64, 0x80, 0x07, 0x96, 0xE0, 0x00, 0x00, 0xE9, 0x48, 0x00, 0x01, 0x04, 0x48, 0x00, 0x00, 0xD1, 0x7C, 0x68, 0x02, 0xA6,
0x60, 0x00, 0x00, 0x00, 0x80, 0x2B, 0x7E, 0x54, 0x48, 0x00, 0x00, 0x88, 0x80, 0x2B, 0x80, 0x8C, 0x48, 0x00, 0x48, 0x00, 0x00, 0xD9, 0x48, 0x00, 0x00, 0xF4, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x3C, 0xE4,
0x00, 0x84, 0x80, 0x12, 0x39, 0xA8, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0xD4, 0x00, 0x24, 0x04, 0x64, 0x80, 0x07, 0x96, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x80, 0x2B,
0x80, 0x3C, 0xE4, 0xD4, 0x00, 0x20, 0x00, 0x00, 0x80, 0x07, 0x96, 0xE0, 0x3A, 0x40, 0x00, 0x01, 0x80, 0x2B, 0x7E, 0x54, 0x48, 0x00, 0x00, 0x88, 0x80, 0x2B, 0x80, 0x8C, 0x48, 0x00, 0x00, 0x84, 0x80,
0x7E, 0x54, 0x88, 0x7F, 0x22, 0x40, 0x80, 0x2B, 0x80, 0x8C, 0x2C, 0x03, 0x00, 0x02, 0x80, 0x10, 0xFC, 0x48, 0x12, 0x39, 0xA8, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21,
0x90, 0x05, 0x21, 0xDC, 0x80, 0x10, 0xFB, 0x68, 0x90, 0x05, 0x21, 0xDC, 0x80, 0x12, 0x39, 0xA8, 0x90, 0x1F, 0x80, 0x3C, 0xE4, 0xD4, 0x00, 0x20, 0x00, 0x00, 0x80, 0x07, 0x96, 0xE0, 0x3A, 0x40, 0x00,
0x1A, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x46, 0x10, 0x48, 0x00, 0x00, 0x4C, 0x01, 0x80, 0x2B, 0x7E, 0x54, 0x88, 0x7F, 0x22, 0x40, 0x80, 0x2B, 0x80, 0x8C, 0x2C, 0x03,
0x80, 0x1D, 0x47, 0x24, 0x48, 0x00, 0x00, 0x3C, 0x80, 0x1D, 0x46, 0x0C, 0x80, 0x9F, 0x00, 0xEC, 0xFF, 0xFF, 0x00, 0x02, 0x80, 0x10, 0xFC, 0x48, 0x90, 0x05, 0x21, 0xDC, 0x80, 0x10, 0xFB, 0x68, 0x90,
0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x46, 0x10, 0x38, 0x83, 0x7F, 0x9C, 0x80, 0x1D, 0x47, 0x24, 0x05, 0x21, 0xDC, 0x80, 0x12, 0x39, 0xA8, 0x90, 0x1F, 0x1A, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF,
0x88, 0x1B, 0x00, 0xC4, 0x80, 0x1D, 0x46, 0x0C, 0x3C, 0x60, 0x80, 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x46, 0x10, 0x48, 0x00, 0x00, 0x4C, 0x80, 0x1D, 0x47,
0x00, 0x21, 0x80, 0x1D, 0x45, 0xFC, 0x48, 0x00, 0x09, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x24, 0x48, 0x00, 0x00, 0x3C, 0x80, 0x1D, 0x46, 0x0C, 0x80, 0x9F, 0x00, 0xEC, 0xFF, 0xFF,
0x80, 0x1D, 0x45, 0xFC, 0x40, 0x80, 0x09, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xA3, 0xFF, 0xFC, 0x84, 0x65, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x46, 0x10, 0x38, 0x83, 0x7F, 0x9C, 0x80,
0x00, 0x04, 0x2C, 0x03, 0xFF, 0xFF, 0x41, 0x82, 0x00, 0x10, 0x84, 0x85, 0x00, 0x04, 0x90, 0x83, 0x00, 0x00, 0x1D, 0x47, 0x24, 0x88, 0x1B, 0x00, 0xC4, 0x80, 0x1D, 0x46, 0x0C, 0x3C, 0x60, 0x80, 0x3B,
0x4B, 0xFF, 0xFF, 0xEC, 0x4E, 0x80, 0x00, 0x20, 0x3C, 0x60, 0x80, 0x00, 0x3C, 0x80, 0x00, 0x3B, 0x60, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x45, 0xFC, 0x48, 0x00, 0x09,
0x72, 0x2C, 0x3D, 0x80, 0x80, 0x32, 0x61, 0x8C, 0x8F, 0x50, 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0x4E, 0x80, 0x00, 0x21, 0x80, 0x1D, 0x45, 0xFC, 0x40, 0x80,
0x3C, 0x60, 0x80, 0x17, 0x3C, 0x80, 0x80, 0x17, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x1D, 0x14, 0xC8, 0x00, 0x00, 0x09, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xA3, 0xFF, 0xFC, 0x84, 0x65, 0x00, 0x04, 0x2C,
0x00, 0x04, // #Common/Preload Stadium 0x03, 0xFF, 0xFF, 0x41, 0x82, 0x00, 0x10, 0x84, 0x85, 0x00, 0x04, 0x90, 0x83, 0x00, 0x00,
0x4B, 0xFF, 0xFF, 0xEC, 0x4E, 0x80, 0x00, 0x20, 0x3C, 0x60, 0x80, 0x00, 0x3C, 0x80, 0x00,
0x3B, 0x60, 0x84, 0x72, 0x2C, 0x3D, 0x80, 0x80, 0x32, 0x61, 0x8C, 0x8F, 0x50, 0x7D, 0x89,
0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x3C, 0x60, 0x80, 0x17, 0x3C, 0x80, 0x80, 0x17, 0x00,
0x00, 0x00, 0x00, 0xC2, 0x1D, 0x14, 0xC8, 0x00, 0x00, 0x00,
0x04, // #Common/Preload Stadium
// Transformations/Handlers/Init // Transformations/Handlers/Init
// isLoaded Bool.asm // isLoaded Bool.asm
0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x0C, 0x38, 0x60, 0x00, 0x00, 0x98, 0x7F, 0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x0C, 0x38, 0x60, 0x00,
0x00, 0xF0, 0x3B, 0xA0, 0x00, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x1D, 0x45, 0xEC, 0x00, 0x98, 0x7F, 0x00, 0xF0, 0x3B, 0xA0, 0x00, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1B, // #Common/Preload Stadium 0x00, 0x00, 0xC2, 0x1D, 0x45, 0xEC, 0x00, 0x00, 0x00, 0x1B, // #Common/Preload Stadium
// Transformations/Handlers/Load // Transformations/Handlers/Load
// Transformation.asm // Transformation.asm
0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0xC4, 0x88, 0x7F, 0x00, 0xF0, 0x2C, 0x03, 0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0xC4, 0x88, 0x7F, 0x00,
0x00, 0x00, 0x40, 0x82, 0x00, 0xB8, 0x38, 0x60, 0x00, 0x04, 0x3D, 0x80, 0x80, 0x38, 0x61, 0x8C, 0x05, 0x80, 0xF0, 0x2C, 0x03, 0x00, 0x00, 0x40, 0x82, 0x00, 0xB8, 0x38, 0x60, 0x00, 0x04, 0x3D, 0x80,
0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x54, 0x60, 0x10, 0x3A, 0xA8, 0x7F, 0x00, 0xE2, 0x3C, 0x80, 0x80, 0x38, 0x61, 0x8C, 0x05, 0x80, 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x54,
0x80, 0x3B, 0x60, 0x84, 0x7F, 0x9C, 0x7C, 0x84, 0x00, 0x2E, 0x7C, 0x03, 0x20, 0x00, 0x41, 0x82, 0xFF, 0xD4, 0x60, 0x10, 0x3A, 0xA8, 0x7F, 0x00, 0xE2, 0x3C, 0x80, 0x80, 0x3B, 0x60, 0x84, 0x7F, 0x9C,
0x90, 0x9F, 0x00, 0xEC, 0x2C, 0x04, 0x00, 0x03, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x00, 0x48, 0x00, 0x7C, 0x84, 0x00, 0x2E, 0x7C, 0x03, 0x20, 0x00, 0x41, 0x82, 0xFF, 0xD4, 0x90, 0x9F, 0x00,
0x00, 0x34, 0x2C, 0x04, 0x00, 0x04, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x01, 0x48, 0x00, 0x00, 0x24, 0xEC, 0x2C, 0x04, 0x00, 0x03, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x00, 0x48, 0x00,
0x2C, 0x04, 0x00, 0x09, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x02, 0x48, 0x00, 0x00, 0x14, 0x2C, 0x04, 0x00, 0x34, 0x2C, 0x04, 0x00, 0x04, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x01, 0x48,
0x00, 0x06, 0x40, 0x82, 0x00, 0x00, 0x38, 0x80, 0x00, 0x03, 0x48, 0x00, 0x00, 0x04, 0x3C, 0x60, 0x80, 0x3E, 0x00, 0x00, 0x24, 0x2C, 0x04, 0x00, 0x09, 0x40, 0x82, 0x00, 0x0C, 0x38, 0x80, 0x00, 0x02,
0x60, 0x63, 0x12, 0x48, 0x54, 0x80, 0x10, 0x3A, 0x7C, 0x63, 0x02, 0x14, 0x80, 0x63, 0x03, 0xD8, 0x80, 0x9F, 0x48, 0x00, 0x00, 0x14, 0x2C, 0x04, 0x00, 0x06, 0x40, 0x82, 0x00, 0x00, 0x38, 0x80, 0x00,
0x00, 0xCC, 0x38, 0xBF, 0x00, 0xC8, 0x3C, 0xC0, 0x80, 0x1D, 0x60, 0xC6, 0x42, 0x20, 0x38, 0xE0, 0x00, 0x00, 0x03, 0x48, 0x00, 0x00, 0x04, 0x3C, 0x60, 0x80, 0x3E, 0x60, 0x63, 0x12, 0x48, 0x54, 0x80,
0x3D, 0x80, 0x80, 0x01, 0x61, 0x8C, 0x65, 0x80, 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x38, 0x60, 0x10, 0x3A, 0x7C, 0x63, 0x02, 0x14, 0x80, 0x63, 0x03, 0xD8, 0x80, 0x9F, 0x00, 0xCC, 0x38,
0x00, 0x01, 0x98, 0x7F, 0x00, 0xF0, 0x80, 0x7F, 0x00, 0xD8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x00, 0xC8, 0x3C, 0xC0, 0x80, 0x1D, 0x60, 0xC6, 0x42, 0x20, 0x38, 0xE0, 0x00, 0x00,
0xC2, 0x1D, 0x4F, 0x14, 0x00, 0x00, 0x00, 0x04, // #Common/Preload 0x3D, 0x80, 0x80, 0x01, 0x61, 0x8C, 0x65, 0x80, 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04,
0x21, 0x38, 0x60, 0x00, 0x01, 0x98, 0x7F, 0x00, 0xF0, 0x80, 0x7F, 0x00, 0xD8, 0x60, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x1D, 0x4F, 0x14, 0x00, 0x00, 0x00,
0x04, // #Common/Preload
// Stadium // Stadium
// Transformations/Handlers/Reset // Transformations/Handlers/Reset
// isLoaded.asm // isLoaded.asm
0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x0C, 0x38, 0x60, 0x00, 0x00, 0x98, 0x7F, 0x88, 0x62, 0xF2, 0x38, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x0C, 0x38, 0x60, 0x00,
0x00, 0xF0, 0x80, 0x6D, 0xB2, 0xD8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x06, 0x8F, 0x30, 0x00, 0x98, 0x7F, 0x00, 0xF0, 0x80, 0x6D, 0xB2, 0xD8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x9D, // #Common/PAL/Handlers/Character DAT 0x00, 0x00, 0xC2, 0x06, 0x8F, 0x30, 0x00, 0x00, 0x00, 0x9D, // #Common/PAL/Handlers/Character
// Patcher.asm // DAT Patcher.asm
0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x04, 0xD4, 0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x04, 0xD4, 0x7C, 0x08, 0x02,
0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x00, 0x08, 0x83, 0xFE, 0x01, 0x0C, 0x83, 0xFF, 0x00, 0x08, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x94, 0x21, 0xFF, 0x50, 0xBE, 0x81, 0x00, 0x08, 0x83, 0xFE,
0x3B, 0xFF, 0xFF, 0xE0, 0x80, 0x7D, 0x00, 0x00, 0x2C, 0x03, 0x00, 0x1B, 0x40, 0x80, 0x04, 0x9C, 0x48, 0x00, 0x01, 0x0C, 0x83, 0xFF, 0x00, 0x08, 0x3B, 0xFF, 0xFF, 0xE0, 0x80, 0x7D, 0x00, 0x00, 0x2C,
0x00, 0x71, 0x48, 0x00, 0x00, 0xA9, 0x48, 0x00, 0x00, 0xB9, 0x48, 0x00, 0x01, 0x51, 0x48, 0x00, 0x01, 0x79, 0x03, 0x00, 0x1B, 0x40, 0x80, 0x04, 0x9C, 0x48, 0x00, 0x00, 0x71, 0x48, 0x00, 0x00, 0xA9,
0x48, 0x00, 0x01, 0x79, 0x48, 0x00, 0x02, 0x29, 0x48, 0x00, 0x02, 0x39, 0x48, 0x00, 0x02, 0x81, 0x48, 0x00, 0x48, 0x00, 0x00, 0xB9, 0x48, 0x00, 0x01, 0x51, 0x48, 0x00, 0x01, 0x79, 0x48, 0x00, 0x01,
0x02, 0xF9, 0x48, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x11, 0x79, 0x48, 0x00, 0x02, 0x29, 0x48, 0x00, 0x02, 0x39, 0x48, 0x00, 0x02, 0x81, 0x48, 0x00,
0x48, 0x00, 0x03, 0x21, 0x48, 0x00, 0x03, 0x21, 0x48, 0x00, 0x03, 0x89, 0x48, 0x00, 0x03, 0x89, 0x48, 0x00, 0x02, 0xF9, 0x48, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x11, 0x48,
0x03, 0x91, 0x48, 0x00, 0x03, 0x91, 0x48, 0x00, 0x03, 0xA9, 0x48, 0x00, 0x03, 0xA9, 0x48, 0x00, 0x03, 0xB9, 0x00, 0x03, 0x11, 0x48, 0x00, 0x03, 0x21, 0x48, 0x00, 0x03, 0x21, 0x48, 0x00, 0x03, 0x89,
0x48, 0x00, 0x03, 0xB9, 0x48, 0x00, 0x03, 0xC9, 0x48, 0x00, 0x03, 0xC9, 0x48, 0x00, 0x03, 0xC9, 0x48, 0x00, 0x48, 0x00, 0x03, 0x89, 0x48, 0x00, 0x03, 0x91, 0x48, 0x00, 0x03, 0x91, 0x48, 0x00, 0x03,
0x04, 0x29, 0x7C, 0x88, 0x02, 0xA6, 0x1C, 0x63, 0x00, 0x04, 0x7C, 0x84, 0x1A, 0x14, 0x80, 0xA4, 0x00, 0x00, 0xA9, 0x48, 0x00, 0x03, 0xA9, 0x48, 0x00, 0x03, 0xB9, 0x48, 0x00, 0x03, 0xB9, 0x48, 0x00,
0x54, 0xA5, 0x01, 0xBA, 0x7C, 0xA4, 0x2A, 0x14, 0x80, 0x65, 0x00, 0x00, 0x80, 0x85, 0x00, 0x04, 0x2C, 0x03, 0x03, 0xC9, 0x48, 0x00, 0x03, 0xC9, 0x48, 0x00, 0x03, 0xC9, 0x48, 0x00, 0x04, 0x29, 0x7C,
0x00, 0xFF, 0x41, 0x82, 0x00, 0x14, 0x7C, 0x63, 0xFA, 0x14, 0x90, 0x83, 0x00, 0x00, 0x38, 0xA5, 0x00, 0x08, 0x88, 0x02, 0xA6, 0x1C, 0x63, 0x00, 0x04, 0x7C, 0x84, 0x1A, 0x14, 0x80, 0xA4, 0x00, 0x00,
0x4B, 0xFF, 0xFF, 0xE4, 0x48, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x33, 0x44, 0x3F, 0x54, 0x7A, 0xE1, 0x00, 0x00, 0x54, 0xA5, 0x01, 0xBA, 0x7C, 0xA4, 0x2A, 0x14, 0x80, 0x65, 0x00, 0x00, 0x80, 0x85, 0x00,
0x33, 0x60, 0x42, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x37, 0x9C, 0x42, 0x92, 0x00, 0x00, 0x04, 0x2C, 0x03, 0x00, 0xFF, 0x41, 0x82, 0x00, 0x14, 0x7C, 0x63, 0xFA, 0x14, 0x90, 0x83,
0x00, 0x00, 0x39, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x0C, 0x40, 0x86, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x38, 0xA5, 0x00, 0x08, 0x4B, 0xFF, 0xFF, 0xE4, 0x48, 0x00, 0x03, 0xF0, 0x00,
0x39, 0x10, 0x3D, 0xEA, 0x0E, 0xA1, 0x00, 0x00, 0x39, 0x28, 0x41, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x04, 0x00, 0x33, 0x44, 0x3F, 0x54, 0x7A, 0xE1, 0x00, 0x00, 0x33, 0x60, 0x42, 0xC4, 0x00, 0x00,
0x2C, 0x01, 0x48, 0x0C, 0x00, 0x00, 0x47, 0x20, 0x1B, 0x96, 0x80, 0x13, 0x00, 0x00, 0x47, 0x34, 0x1B, 0x96, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x37, 0x9C, 0x42, 0x92, 0x00, 0x00, 0x00, 0x00, 0x39,
0x80, 0x13, 0x00, 0x00, 0x47, 0x3C, 0x04, 0x00, 0x00, 0x09, 0x00, 0x00, 0x4A, 0x40, 0x2C, 0x00, 0x68, 0x11, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x0C, 0x40, 0x86, 0x66, 0x66, 0x00, 0x00,
0x00, 0x00, 0x4A, 0x4C, 0x28, 0x1B, 0x00, 0x13, 0x00, 0x00, 0x4A, 0x50, 0x0D, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x39, 0x10, 0x3D, 0xEA, 0x0E, 0xA1, 0x00, 0x00, 0x39, 0x28, 0x41, 0xA0, 0x00, 0x00, 0x00,
0x4A, 0x54, 0x2C, 0x80, 0x68, 0x11, 0x00, 0x00, 0x4A, 0x60, 0x28, 0x1B, 0x00, 0x13, 0x00, 0x00, 0x4A, 0x64, 0x00, 0x3C, 0x04, 0x2C, 0x01, 0x48, 0x0C, 0x00, 0x00, 0x47, 0x20, 0x1B, 0x96, 0x80, 0x13,
0x0D, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x4B, 0x24, 0x2C, 0x00, 0x68, 0x0D, 0x00, 0x00, 0x4B, 0x30, 0x0F, 0x10, 0x00, 0x00, 0x47, 0x34, 0x1B, 0x96, 0x80, 0x13, 0x00, 0x00, 0x47, 0x3C, 0x04, 0x00, 0x00,
0x40, 0x13, 0x00, 0x00, 0x4B, 0x38, 0x2C, 0x80, 0x38, 0x0D, 0x00, 0x00, 0x4B, 0x44, 0x0F, 0x10, 0x40, 0x13, 0x09, 0x00, 0x00, 0x4A, 0x40, 0x2C, 0x00, 0x68, 0x11, 0x00, 0x00, 0x4A, 0x4C, 0x28, 0x1B,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x4E, 0xF8, 0x2C, 0x00, 0x00, 0x13, 0x00, 0x00, 0x4A, 0x50, 0x0D, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x4A, 0x54, 0x2C,
0x38, 0x03, 0x00, 0x00, 0x4F, 0x08, 0x0F, 0x80, 0x00, 0x0B, 0x00, 0x00, 0x4F, 0x0C, 0x2C, 0x80, 0x20, 0x03, 0x80, 0x68, 0x11, 0x00, 0x00, 0x4A, 0x60, 0x28, 0x1B, 0x00, 0x13, 0x00, 0x00, 0x4A, 0x64,
0x00, 0x00, 0x4F, 0x1C, 0x0F, 0x80, 0x00, 0x0B, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x0D, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x4B, 0x24, 0x2C, 0x00, 0x68, 0x0D, 0x00, 0x00, 0x4B,
0x4D, 0x10, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x70, 0x42, 0x94, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xD4, 0x30, 0x0F, 0x10, 0x40, 0x13, 0x00, 0x00, 0x4B, 0x38, 0x2C, 0x80, 0x38, 0x0D, 0x00, 0x00,
0x41, 0x90, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xE0, 0x41, 0x90, 0x00, 0x00, 0x00, 0x00, 0x83, 0xAC, 0x2C, 0x00, 0x4B, 0x44, 0x0F, 0x10, 0x40, 0x13, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x38, 0x0C, 0x00,
0x00, 0x09, 0x00, 0x00, 0x83, 0xB8, 0x34, 0x8C, 0x80, 0x11, 0x00, 0x00, 0x84, 0x00, 0x34, 0x8C, 0x80, 0x11, 0x00, 0x00, 0x07, 0x00, 0x00, 0x4E, 0xF8, 0x2C, 0x00, 0x38, 0x03, 0x00, 0x00, 0x4F, 0x08,
0x00, 0x00, 0x84, 0x30, 0x05, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x84, 0x38, 0x04, 0x1A, 0x05, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x0B, 0x00, 0x00, 0x4F, 0x0C, 0x2C, 0x80, 0x20, 0x03, 0x00, 0x00, 0x4F,
0x84, 0x44, 0x05, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x84, 0xDC, 0x05, 0x78, 0x05, 0x78, 0x00, 0x00, 0x85, 0xB8, 0x1C, 0x0F, 0x80, 0x00, 0x0B, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
0x10, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x85, 0xC0, 0x03, 0xE8, 0x01, 0xF4, 0x00, 0x00, 0x85, 0xCC, 0x10, 0x00, 0x4D, 0x10, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x70, 0x42, 0x94, 0x00, 0x00, 0x00,
0x01, 0x0B, 0x00, 0x00, 0x85, 0xD4, 0x03, 0x84, 0x03, 0xE8, 0x00, 0x00, 0x85, 0xE0, 0x10, 0x00, 0x01, 0x0B, 0x00, 0x4D, 0xD4, 0x41, 0x90, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xE0, 0x41, 0x90, 0x00, 0x00,
0x00, 0x00, 0x88, 0x18, 0x0B, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x88, 0x2C, 0x0B, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x83, 0xAC, 0x2C, 0x00, 0x00, 0x09, 0x00, 0x00, 0x83, 0xB8, 0x34, 0x8C, 0x80,
0x88, 0xF8, 0x04, 0x1A, 0x0B, 0xB8, 0x00, 0x00, 0x89, 0x3C, 0x04, 0x1A, 0x0B, 0xB8, 0x00, 0x00, 0x89, 0x80, 0x11, 0x00, 0x00, 0x84, 0x00, 0x34, 0x8C, 0x80, 0x11, 0x00, 0x00, 0x84, 0x30, 0x05, 0x00,
0x04, 0x1A, 0x0B, 0xB8, 0x00, 0x00, 0x89, 0xE0, 0x04, 0xFE, 0xF7, 0x04, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x84, 0x38, 0x04, 0x1A, 0x05, 0x00, 0x00, 0x00, 0x84, 0x44, 0x05,
0x36, 0xCC, 0x42, 0xEC, 0x00, 0x00, 0x00, 0x00, 0x37, 0xC4, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x84, 0xDC, 0x05, 0x78, 0x05, 0x78, 0x00, 0x00, 0x85, 0xB8,
0x00, 0x00, 0x34, 0x68, 0x3F, 0x66, 0x66, 0x66, 0x00, 0x00, 0x39, 0xD8, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x85, 0xC0, 0x03, 0xE8, 0x01, 0xF4, 0x00, 0x00, 0x85,
0x3A, 0x44, 0xB4, 0x99, 0x00, 0x11, 0x00, 0x00, 0x3A, 0x48, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3A, 0x58, 0xCC, 0x10, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x85, 0xD4, 0x03, 0x84, 0x03, 0xE8, 0x00, 0x00,
0xB4, 0x99, 0x00, 0x11, 0x00, 0x00, 0x3A, 0x5C, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3A, 0x6C, 0xB4, 0x99, 0x85, 0xE0, 0x10, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x88, 0x18, 0x0B, 0x00, 0x01, 0x0B, 0x00,
0x00, 0x11, 0x00, 0x00, 0x3A, 0x70, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3B, 0x30, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x88, 0x2C, 0x0B, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x88, 0xF8, 0x04, 0x1A, 0x0B, 0xB8,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x45, 0xC8, 0x2C, 0x01, 0x50, 0x10, 0x00, 0x00, 0x45, 0xD4, 0x2D, 0x19, 0x00, 0x00, 0x89, 0x3C, 0x04, 0x1A, 0x0B, 0xB8, 0x00, 0x00, 0x89, 0x80, 0x04, 0x1A, 0x0B,
0x80, 0x13, 0x00, 0x00, 0x45, 0xDC, 0x2C, 0x80, 0xB0, 0x10, 0x00, 0x00, 0x45, 0xE8, 0x2D, 0x19, 0x80, 0x13, 0xB8, 0x00, 0x00, 0x89, 0xE0, 0x04, 0xFE, 0xF7, 0x04, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x49, 0xC4, 0x2C, 0x00, 0x68, 0x0A, 0x00, 0x00, 0x49, 0xD0, 0x28, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x36, 0xCC, 0x42, 0xEC, 0x00, 0x00, 0x00, 0x00, 0x37, 0xC4, 0x0C, 0x00, 0x00, 0x00, 0x00,
0x49, 0xD8, 0x2C, 0x80, 0x78, 0x0A, 0x00, 0x00, 0x49, 0xE4, 0x28, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x49, 0xF0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x34, 0x68, 0x3F, 0x66, 0x66, 0x66, 0x00, 0x00, 0x39, 0xD8,
0x2C, 0x00, 0x68, 0x08, 0x00, 0x00, 0x49, 0xFC, 0x23, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x4A, 0x04, 0x2C, 0x80, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x44, 0xB4, 0x99, 0x00, 0x11, 0x00, 0x00, 0x3A,
0x78, 0x08, 0x00, 0x00, 0x4A, 0x10, 0x23, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x5C, 0x98, 0x1E, 0x0C, 0x80, 0x80, 0x48, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3A, 0x58, 0xB4, 0x99, 0x00, 0x11, 0x00, 0x00,
0x00, 0x00, 0x5C, 0xF4, 0xB4, 0x80, 0x0C, 0x90, 0x00, 0x00, 0x5D, 0x08, 0xB4, 0x80, 0x0C, 0x90, 0x00, 0x00, 0x3A, 0x5C, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3A, 0x6C, 0xB4, 0x99, 0x00, 0x11, 0x00,
0x00, 0xFF, 0x00, 0x00, 0x3A, 0x1C, 0xB4, 0x94, 0x00, 0x13, 0x00, 0x00, 0x3A, 0x64, 0x2C, 0x00, 0x00, 0x15, 0x00, 0x3A, 0x70, 0x1B, 0x8C, 0x00, 0x8F, 0x00, 0x00, 0x3B, 0x30, 0x44, 0x0C, 0x00, 0x00,
0x00, 0x00, 0x3A, 0x70, 0xB4, 0x92, 0x80, 0x13, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x45, 0xC8, 0x2C, 0x01, 0x50, 0x10, 0x00, 0x00, 0x45,
0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x64, 0x7C, 0xB4, 0x9A, 0x40, 0x17, 0x00, 0x00, 0x64, 0x80, 0xD4, 0x2D, 0x19, 0x80, 0x13, 0x00, 0x00, 0x45, 0xDC, 0x2C, 0x80, 0xB0, 0x10, 0x00, 0x00,
0x64, 0x00, 0x10, 0x97, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x33, 0xE4, 0x42, 0xDE, 0x45, 0xE8, 0x2D, 0x19, 0x80, 0x13, 0x00, 0x00, 0x49, 0xC4, 0x2C, 0x00, 0x68, 0x0A, 0x00,
0x00, 0x00, 0x00, 0x00, 0x45, 0x28, 0x2C, 0x01, 0x30, 0x11, 0x00, 0x00, 0x45, 0x34, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x49, 0xD0, 0x28, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x49, 0xD8, 0x2C, 0x80, 0x78, 0x0A,
0x00, 0x00, 0x45, 0x3C, 0x2C, 0x81, 0x30, 0x11, 0x00, 0x00, 0x45, 0x48, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x00, 0x00, 0x00, 0x49, 0xE4, 0x28, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x49, 0xF0, 0x2C, 0x00, 0x68,
0x45, 0x50, 0x2D, 0x00, 0x20, 0x11, 0x00, 0x00, 0x45, 0x5C, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x00, 0x45, 0xF8, 0x08, 0x00, 0x00, 0x49, 0xFC, 0x23, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x4A, 0x04, 0x2C, 0x80,
0x2C, 0x01, 0x30, 0x0F, 0x00, 0x00, 0x46, 0x08, 0x0F, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x46, 0x0C, 0x2C, 0x81, 0x78, 0x08, 0x00, 0x00, 0x4A, 0x10, 0x23, 0x1B, 0x80, 0x13, 0x00, 0x00, 0x5C, 0x98, 0x1E,
0x28, 0x0F, 0x00, 0x00, 0x46, 0x1C, 0x0F, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x4A, 0xEC, 0x2C, 0x00, 0x70, 0x03, 0x0C, 0x80, 0x80, 0x00, 0x00, 0x5C, 0xF4, 0xB4, 0x80, 0x0C, 0x90, 0x00, 0x00, 0x5D, 0x08,
0x00, 0x00, 0x4B, 0x00, 0x2C, 0x80, 0x38, 0x03, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xB4, 0x80, 0x0C, 0x90, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3A, 0x1C, 0xB4, 0x94, 0x00,
0x48, 0x5C, 0x2C, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x37, 0xB0, 0x13, 0x00, 0x00, 0x3A, 0x64, 0x2C, 0x00, 0x00, 0x15, 0x00, 0x00, 0x3A, 0x70, 0xB4, 0x92,
0x3F, 0x59, 0x99, 0x9A, 0x00, 0x00, 0x37, 0xCC, 0x42, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x55, 0x20, 0x87, 0x11, 0x80, 0x13, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00,
0x80, 0x13, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3B, 0x8C, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x64, 0x7C, 0xB4, 0x9A, 0x40, 0x17, 0x00, 0x00, 0x64, 0x80,
0x00, 0x00, 0x3D, 0x0C, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x64, 0x00, 0x10, 0x97, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x33,
0x50, 0xE4, 0xB4, 0x99, 0x00, 0x13, 0x00, 0x00, 0x50, 0xF8, 0xB4, 0x99, 0x00, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xE4, 0x42, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x45, 0x28, 0x2C, 0x01, 0x30, 0x11, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x4E, 0xB0, 0x02, 0xBC, 0xFF, 0x38, 0x00, 0x00, 0x45, 0x34, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x00, 0x45, 0x3C, 0x2C, 0x81, 0x30, 0x11, 0x00,
0x4E, 0xBC, 0x14, 0x00, 0x01, 0x23, 0x00, 0x00, 0x4E, 0xC4, 0x03, 0x84, 0x01, 0xF4, 0x00, 0x00, 0x4E, 0xD0, 0x00, 0x45, 0x48, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x00, 0x45, 0x50, 0x2D, 0x00, 0x20, 0x11,
0x14, 0x00, 0x01, 0x23, 0x00, 0x00, 0x4E, 0xD8, 0x04, 0x4C, 0x04, 0xB0, 0x00, 0x00, 0x4E, 0xE4, 0x14, 0x00, 0x00, 0x00, 0x45, 0x5C, 0xB4, 0x98, 0x80, 0x13, 0x00, 0x00, 0x45, 0xF8, 0x2C, 0x01, 0x30,
0x01, 0x23, 0x00, 0x00, 0x50, 0x5C, 0x2C, 0x00, 0x68, 0x15, 0x00, 0x00, 0x50, 0x6C, 0x14, 0x08, 0x01, 0x23, 0x0F, 0x00, 0x00, 0x46, 0x08, 0x0F, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x46, 0x0C, 0x2C, 0x81,
0x00, 0x00, 0x50, 0x70, 0x2C, 0x80, 0x60, 0x15, 0x00, 0x00, 0x50, 0x80, 0x14, 0x08, 0x01, 0x23, 0x00, 0x00, 0x28, 0x0F, 0x00, 0x00, 0x46, 0x1C, 0x0F, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x4A, 0xEC, 0x2C,
0x50, 0x84, 0x2D, 0x00, 0x20, 0x15, 0x00, 0x00, 0x50, 0x94, 0x14, 0x08, 0x01, 0x23, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x70, 0x03, 0x00, 0x00, 0x4B, 0x00, 0x2C, 0x80, 0x38, 0x03, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x00, 0x00, 0xFF, 0xBA, 0x81, 0x00, 0x08, 0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x48, 0x5C, 0x2C, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x03, 0xA6, 0x3C, 0x60, 0x80, 0x3C, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x2F, 0x9A, 0x3C, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x37, 0xB0, 0x3F, 0x59, 0x99, 0x9A, 0x00, 0x00,
0x37, 0xCC, 0x42, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x55, 0x20, 0x87, 0x11, 0x80, 0x13, 0x00,
0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3B, 0x8C, 0x44, 0x0C, 0x00, 0x00,
0x00, 0x00, 0x3D, 0x0C, 0x44, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
0xFF, 0x00, 0x00, 0x50, 0xE4, 0xB4, 0x99, 0x00, 0x13, 0x00, 0x00, 0x50, 0xF8, 0xB4, 0x99,
0x00, 0x13, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00,
0x00, 0x4E, 0xB0, 0x02, 0xBC, 0xFF, 0x38, 0x00, 0x00, 0x4E, 0xBC, 0x14, 0x00, 0x01, 0x23,
0x00, 0x00, 0x4E, 0xC4, 0x03, 0x84, 0x01, 0xF4, 0x00, 0x00, 0x4E, 0xD0, 0x14, 0x00, 0x01,
0x23, 0x00, 0x00, 0x4E, 0xD8, 0x04, 0x4C, 0x04, 0xB0, 0x00, 0x00, 0x4E, 0xE4, 0x14, 0x00,
0x01, 0x23, 0x00, 0x00, 0x50, 0x5C, 0x2C, 0x00, 0x68, 0x15, 0x00, 0x00, 0x50, 0x6C, 0x14,
0x08, 0x01, 0x23, 0x00, 0x00, 0x50, 0x70, 0x2C, 0x80, 0x60, 0x15, 0x00, 0x00, 0x50, 0x80,
0x14, 0x08, 0x01, 0x23, 0x00, 0x00, 0x50, 0x84, 0x2D, 0x00, 0x20, 0x15, 0x00, 0x00, 0x50,
0x94, 0x14, 0x08, 0x01, 0x23, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xBA, 0x81,
0x00, 0x08, 0x80, 0x01, 0x00, 0xB4, 0x38, 0x21, 0x00, 0xB0, 0x7C, 0x08, 0x03, 0xA6, 0x3C,
0x60, 0x80, 0x3C, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x2F, 0x9A, 0x3C,
0x00, 0x00, 0x00, 0x08, // #Common/PAL/Handlers/PAL Stock Icons.asm 0x00, 0x00, 0x00, 0x08, // #Common/PAL/Handlers/PAL Stock Icons.asm
0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x30, 0x48, 0x00, 0x00, 0x21, 0x7C, 0x88, 0x88, 0x62, 0xF2, 0x34, 0x2C, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00, 0x30, 0x48, 0x00, 0x00,
0x02, 0xA6, 0x80, 0x64, 0x00, 0x00, 0x90, 0x7D, 0x00, 0x2C, 0x90, 0x7D, 0x00, 0x30, 0x80, 0x64, 0x00, 0x04, 0x21, 0x7C, 0x88, 0x02, 0xA6, 0x80, 0x64, 0x00, 0x00, 0x90, 0x7D, 0x00, 0x2C, 0x90, 0x7D,
0x90, 0x7D, 0x00, 0x3C, 0x48, 0x00, 0x00, 0x10, 0x4E, 0x80, 0x00, 0x21, 0x3F, 0x59, 0x99, 0x9A, 0xC1, 0xA8, 0x00, 0x30, 0x80, 0x64, 0x00, 0x04, 0x90, 0x7D, 0x00, 0x3C, 0x48, 0x00, 0x00, 0x10, 0x4E,
0x00, 0x00, 0x80, 0x1D, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x10, 0xFC, 0x44, 0x00, 0x00, 0x00, 0x80, 0x00, 0x21, 0x3F, 0x59, 0x99, 0x9A, 0xC1, 0xA8, 0x00, 0x00, 0x80, 0x1D, 0x00, 0x14,
0x00, 0x00, 0x00, 0x00, 0xC2, 0x10, 0xFC, 0x44, 0x00, 0x00, 0x00,
0x04, // #Common/PAL/Handlers/DK 0x04, // #Common/PAL/Handlers/DK
// Up B/Aerial Up B.asm // Up B/Aerial Up B.asm
0x88, 0x82, 0xF2, 0x34, 0x2C, 0x04, 0x00, 0x00, 0x41, 0x82, 0x00, 0x10, 0x3C, 0x00, 0x80, 0x11, 0x60, 0x00, 0x88, 0x82, 0xF2, 0x34, 0x2C, 0x04, 0x00, 0x00, 0x41, 0x82, 0x00, 0x10, 0x3C, 0x00, 0x80,
0x00, 0x74, 0x48, 0x00, 0x00, 0x08, 0x38, 0x03, 0xD7, 0x74, 0x00, 0x00, 0x00, 0x00, 0xC2, 0x10, 0xFB, 0x64, 0x11, 0x60, 0x00, 0x00, 0x74, 0x48, 0x00, 0x00, 0x08, 0x38, 0x03, 0xD7, 0x74, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, // #Common/PAL/Handlers/DK Up B/Grounded 0x00, 0x00, 0xC2, 0x10, 0xFB, 0x64, 0x00, 0x00, 0x00, 0x04, // #Common/PAL/Handlers/DK Up
// Up B.asm // B/Grounded Up B.asm
0x88, 0x82, 0xF2, 0x34, 0x2C, 0x04, 0x00, 0x00, 0x41, 0x82, 0x00, 0x10, 0x3C, 0x00, 0x80, 0x11, 0x60, 0x00, 0x88, 0x82, 0xF2, 0x34, 0x2C, 0x04, 0x00, 0x00, 0x41, 0x82, 0x00, 0x10, 0x3C, 0x00, 0x80,
0x00, 0x74, 0x48, 0x00, 0x00, 0x08, 0x38, 0x03, 0xD7, 0x74, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x11, 0x60, 0x00, 0x00, 0x74, 0x48, 0x00, 0x00, 0x08, 0x38, 0x03, 0xD7, 0x74, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 // Termination sequence 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Termination sequence
}; };
static std::unordered_map<u32, bool> staticBlacklist = { static std::unordered_map<u32, bool> staticBlacklist = {
@ -925,7 +976,8 @@ void CEXISlippi::prepareGeckoList()
{0x8016e74c, true}, // Recording/SendGameInfo.asm {0x8016e74c, true}, // Recording/SendGameInfo.asm
{0x8006c5d8, true}, // Recording/SendGamePostFrame.asm {0x8006c5d8, true}, // Recording/SendGamePostFrame.asm
{0x8006b0dc, true}, // Recording/SendGamePreFrame.asm {0x8006b0dc, true}, // Recording/SendGamePreFrame.asm
{0x803219ec, true}, // 3.4.0: Recording/FlushFrameBuffer.asm (Have to keep old ones for backward compatibility) {0x803219ec, true}, // 3.4.0: Recording/FlushFrameBuffer.asm (Have to keep old ones for
// backward compatibility)
{0x8006da34, true}, // 3.4.0: Recording/SendGamePostFrame.asm {0x8006da34, true}, // 3.4.0: Recording/SendGamePostFrame.asm
{0x8016d884, true}, // 3.7.0: Recording/SendGameEnd.asm {0x8016d884, true}, // 3.7.0: Recording/SendGameEnd.asm
@ -963,20 +1015,22 @@ void CEXISlippi::prepareGeckoList()
{0x801a4570, true}, // External/LagReduction/ForceHD/480pDeflickerOff.asm {0x801a4570, true}, // External/LagReduction/ForceHD/480pDeflickerOff.asm
{0x802fccd8, true}, // External/Hide Nametag When Invisible/Hide Nametag When Invisible.asm {0x802fccd8, true}, // External/Hide Nametag When Invisible/Hide Nametag When Invisible.asm
{0x804ddb30, {0x804ddb30, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble
true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble Positions/Adjust Corner Value 1.asm // Positions/Adjust Corner Value 1.asm
{0x804ddb34, {0x804ddb34, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble
true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble Positions/Adjust Corner Value 2.asm // Positions/Adjust Corner Value 2.asm
{0x804ddb2c, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble Positions/Extend Negative {0x804ddb2c, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble
// Vertical Bound.asm // Positions/Extend Negative Vertical Bound.asm
{0x804ddb28, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble Positions/Extend Positive {0x804ddb28, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble
// Vertical Bound.asm // Positions/Extend Positive Vertical Bound.asm
{0x804ddb4c, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble Positions/Widen Bubble Region.asm {0x804ddb4c, true}, // External/Widescreen/Adjust Offscreen Scissor/Fix Bubble
// Positions/Widen Bubble Region.asm
{0x804ddb58, true}, // External/Widescreen/Adjust Offscreen Scissor/Adjust Bubble Zoom.asm {0x804ddb58, true}, // External/Widescreen/Adjust Offscreen Scissor/Adjust Bubble Zoom.asm
{0x80086b24, true}, // External/Widescreen/Adjust Offscreen Scissor/Draw High Poly Models.asm {0x80086b24, true}, // External/Widescreen/Adjust Offscreen Scissor/Draw High Poly Models.asm
{0x80030C7C, true}, // External/Widescreen/Adjust Offscreen Scissor/Left Camera Bound.asm {0x80030C7C, true}, // External/Widescreen/Adjust Offscreen Scissor/Left Camera Bound.asm
{0x80030C88, true}, // External/Widescreen/Adjust Offscreen Scissor/Right Camera Bound.asm {0x80030C88, true}, // External/Widescreen/Adjust Offscreen Scissor/Right Camera Bound.asm
{0x802fcfc4, true}, // External/Widescreen/Nametag Fixes/Adjust Nametag Background X Scale.asm {0x802fcfc4,
true}, // External/Widescreen/Nametag Fixes/Adjust Nametag Background X Scale.asm
{0x804ddb84, true}, // External/Widescreen/Nametag Fixes/Adjust Nametag Text X Scale.asm {0x804ddb84, true}, // External/Widescreen/Nametag Fixes/Adjust Nametag Text X Scale.asm
{0x803BB05C, true}, // External/Widescreen/Fix Screen Flash.asm {0x803BB05C, true}, // External/Widescreen/Fix Screen Flash.asm
{0x8036A4A8, true}, // External/Widescreen/Overwrite CObj Values.asm {0x8036A4A8, true}, // External/Widescreen/Overwrite CObj Values.asm
@ -1040,7 +1094,8 @@ void CEXISlippi::prepareGeckoList()
while (idx < source.size()) while (idx < source.size())
{ {
u8 codeType = source[idx] & 0xFE; u8 codeType = source[idx] & 0xFE;
u32 address = source[idx] << 24 | source[idx + 1] << 16 | source[idx + 2] << 8 | source[idx + 3]; u32 address =
source[idx] << 24 | source[idx + 1] << 16 | source[idx + 2] << 8 | source[idx + 3];
address = (address & 0x01FFFFFF) | 0x80000000; address = (address & 0x01FFFFFF) | 0x80000000;
u32 codeOffset = 8; // Default code offset. Most codes are this length u32 codeOffset = 8; // Default code offset. Most codes are this length
@ -1049,7 +1104,8 @@ void CEXISlippi::prepareGeckoList()
case 0xC0: case 0xC0:
case 0xC2: case 0xC2:
{ {
u32 lineCount = source[idx + 4] << 24 | source[idx + 5] << 16 | source[idx + 6] << 8 | source[idx + 7]; u32 lineCount =
source[idx + 4] << 24 | source[idx + 5] << 16 | source[idx + 6] << 8 | source[idx + 7];
codeOffset = 8 + (lineCount * 8); codeOffset = 8 + (lineCount * 8);
break; break;
} }
@ -1058,8 +1114,10 @@ void CEXISlippi::prepareGeckoList()
break; break;
case 0x06: case 0x06:
{ {
u32 byteLen = source[idx + 4] << 24 | source[idx + 5] << 16 | source[idx + 6] << 8 | source[idx + 7]; u32 byteLen =
codeOffset = 8 + ((byteLen + 7) & 0xFFFFFFF8); // Round up to next 8 bytes and add the first 8 bytes source[idx + 4] << 24 | source[idx + 5] << 16 | source[idx + 6] << 8 | source[idx + 7];
codeOffset =
8 + ((byteLen + 7) & 0xFFFFFFF8); // Round up to next 8 bytes and add the first 8 bytes
break; break;
} }
} }
@ -1070,8 +1128,8 @@ void CEXISlippi::prepareGeckoList()
if (blacklist.count(address)) if (blacklist.count(address))
continue; continue;
INFO_LOG(SLIPPI, "Codetype [%x] Inserting section: %d - %d (%x, %d)", codeType, idx - codeOffset, idx, address, INFO_LOG(SLIPPI, "Codetype [%x] Inserting section: %d - %d (%x, %d)", codeType,
codeOffset); idx - codeOffset, idx, address, codeOffset);
// If not blacklisted, add code to return vector // If not blacklisted, add code to return vector
geckoList.insert(geckoList.end(), source.begin() + (idx - codeOffset), source.begin() + idx); geckoList.insert(geckoList.end(), source.begin() + (idx - codeOffset), source.begin() + idx);
@ -1100,12 +1158,12 @@ void CEXISlippi::prepareCharacterFrameData(Slippi::FrameData* frame, u8 port, u8
// Get data for this player // Get data for this player
Slippi::PlayerFrameData data = source[port]; Slippi::PlayerFrameData data = source[port];
// log << frameIndex << "\t" << port << "\t" << data.locationX << "\t" << data.locationY << "\t" << // log << frameIndex << "\t" << port << "\t" << data.locationX << "\t" << data.locationY << "\t"
// data.animation // << data.animation
// << "\n"; // << "\n";
// WARN_LOG(EXPANSIONINTERFACE, "[Frame %d] [Player %d] Positions: %f | %f", frameIndex, port, data.locationX, // WARN_LOG(EXPANSIONINTERFACE, "[Frame %d] [Player %d] Positions: %f | %f", frameIndex, port,
// data.locationY); // data.locationX, data.locationY);
// Add all of the inputs in order // Add all of the inputs in order
appendWordToBuffer(&m_read_queue, data.randomSeed); appendWordToBuffer(&m_read_queue, data.randomSeed);
@ -1249,7 +1307,8 @@ void CEXISlippi::prepareFrameData(u8* payload)
if (requestResultCode == FRAME_RESP_TERMINATE) if (requestResultCode == FRAME_RESP_TERMINATE)
{ {
ERROR_LOG(EXPANSIONINTERFACE, "Game should terminate on frame %d [%X]", frameIndex, frameIndex); ERROR_LOG(EXPANSIONINTERFACE, "Game should terminate on frame %d [%X]", frameIndex,
frameIndex);
} }
return; return;
@ -1429,8 +1488,10 @@ void CEXISlippi::handleOnlineInputs(u8* payload)
bool CEXISlippi::shouldSkipOnlineFrame(s32 frame) bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
{ {
auto status = slippi_netplay->GetSlippiConnectStatus(); auto status = slippi_netplay->GetSlippiConnectStatus();
bool connectionFailed = status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_FAILED; bool connectionFailed =
bool connectionDisconnected = status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED; status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_FAILED;
bool connectionDisconnected =
status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED;
if (connectionFailed || connectionDisconnected) if (connectionFailed || connectionDisconnected)
{ {
// If connection failed just continue the game // If connection failed just continue the game
@ -1454,7 +1515,8 @@ bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
isConnectionStalled = true; isConnectionStalled = true;
} }
WARN_LOG(SLIPPI_ONLINE, "Halting for one frame due to rollback limit (frame: %d | latest: %d)...", frame, WARN_LOG(SLIPPI_ONLINE,
"Halting for one frame due to rollback limit (frame: %d | latest: %d)...", frame,
latestRemoteFrame); latestRemoteFrame);
return true; return true;
} }
@ -1463,9 +1525,9 @@ bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
// Return true if we are over 60% of a frame ahead of our opponent. Currently limiting how // Return true if we are over 60% of a frame ahead of our opponent. Currently limiting how
// often this happens because I'm worried about jittery data causing a lot of unneccesary delays. // often this happens because I'm worried about jittery data causing a lot of unneccesary delays.
// Only skip once for a given frame because our time detection method doesn't take into consideration // Only skip once for a given frame because our time detection method doesn't take into
// waiting for a frame. Also it's less jarring and it happens often enough that it will smoothly // consideration waiting for a frame. Also it's less jarring and it happens often enough that it
// get to the right place // will smoothly get to the right place
auto isTimeSyncFrame = frame % SLIPPI_ONLINE_LOCKSTEP_INTERVAL; // Only time sync every 30 frames auto isTimeSyncFrame = frame % SLIPPI_ONLINE_LOCKSTEP_INTERVAL; // Only time sync every 30 frames
if (isTimeSyncFrame == 0 && !isCurrentlySkipping) if (isTimeSyncFrame == 0 && !isCurrentlySkipping)
{ {
@ -1478,10 +1540,11 @@ bool CEXISlippi::shouldSkipOnlineFrame(s32 frame)
int maxSkipFrames = frame <= 120 ? 5 : 1; // On early frames, support skipping more frames int maxSkipFrames = frame <= 120 ? 5 : 1; // On early frames, support skipping more frames
framesToSkip = ((offsetUs - 10000) / 16683) + 1; framesToSkip = ((offsetUs - 10000) / 16683) + 1;
framesToSkip = framesToSkip > maxSkipFrames ? maxSkipFrames : framesToSkip; // Only skip 5 frames max framesToSkip =
framesToSkip > maxSkipFrames ? maxSkipFrames : framesToSkip; // Only skip 5 frames max
WARN_LOG(SLIPPI_ONLINE, "Halting on frame %d due to time sync. Offset: %d us. Frames: %d...", frame, WARN_LOG(SLIPPI_ONLINE, "Halting on frame %d due to time sync. Offset: %d us. Frames: %d...",
offsetUs, framesToSkip); frame, offsetUs, framesToSkip);
} }
} }
@ -1519,7 +1582,8 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
u8 frameResult = 1; // Indicates to continue frame u8 frameResult = 1; // Indicates to continue frame
auto state = slippi_netplay->GetSlippiConnectStatus(); auto state = slippi_netplay->GetSlippiConnectStatus();
if (state != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED || isConnectionStalled) if (state != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED ||
isConnectionStalled)
{ {
frameResult = 3; // Indicates we have disconnected frameResult = 3; // Indicates we have disconnected
} }
@ -1548,8 +1612,9 @@ void CEXISlippi::prepareOpponentInputs(u8* payload)
m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end()); m_read_queue.insert(m_read_queue.end(), tx.begin(), tx.end());
// ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5], m_read_queue[6], // ERROR_LOG(SLIPPI_ONLINE, "EXI: [%d] %X %X %X %X %X %X %X %X", latestFrame, m_read_queue[5],
// m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10], m_read_queue[11], m_read_queue[12]); // m_read_queue[6], m_read_queue[7], m_read_queue[8], m_read_queue[9], m_read_queue[10],
// m_read_queue[11], m_read_queue[12]);
} }
void CEXISlippi::handleCaptureSavestate(u8* payload) void CEXISlippi::handleCaptureSavestate(u8* payload)
@ -1609,7 +1674,8 @@ void CEXISlippi::handleLoadSavestate(u8* payload)
int idx = 0; int idx = 0;
while (Common::swap32(preserveArr[idx]) != 0) while (Common::swap32(preserveArr[idx]) != 0)
{ {
SlippiSavestate::PreserveBlock p = { Common::swap32(preserveArr[idx]), Common::swap32(preserveArr[idx + 1]) }; SlippiSavestate::PreserveBlock p = {Common::swap32(preserveArr[idx]),
Common::swap32(preserveArr[idx + 1])};
blocks.push_back(p); blocks.push_back(p);
idx += 2; idx += 2;
} }
@ -1626,7 +1692,8 @@ void CEXISlippi::handleLoadSavestate(u8* payload)
activeSavestates.clear(); activeSavestates.clear();
u32 timeDiff = (u32)(Common::Timer::GetTimeUs() - startTime); u32 timeDiff = (u32)(Common::Timer::GetTimeUs() - startTime);
INFO_LOG(SLIPPI_ONLINE, "SLIPPI ONLINE: Loaded savestate for frame %d in: %f ms", frame, ((double)timeDiff) / 1000); INFO_LOG(SLIPPI_ONLINE, "SLIPPI ONLINE: Loaded savestate for frame %d in: %f ms", frame,
((double)timeDiff) / 1000);
} }
void CEXISlippi::startFindMatch(u8* payload) void CEXISlippi::startFindMatch(u8* payload)
@ -1645,9 +1712,9 @@ void CEXISlippi::startFindMatch(u8* payload)
// Store this search so we know what was queued for // Store this search so we know what was queued for
lastSearch = search; lastSearch = search;
// While we do have another condition that checks characters after being connected, it's nice to give // While we do have another condition that checks characters after being connected, it's nice to
// someone an early error before they even queue so that they wont enter the queue and make someone // give someone an early error before they even queue so that they wont enter the queue and make
// else get force removed from queue and have to requeue // someone else get force removed from queue and have to requeue
auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT; auto directMode = SlippiMatchmaking::OnlinePlayMode::DIRECT;
if (search.mode != directMode && localSelections.characterId >= 26) if (search.mode != directMode && localSelections.characterId >= 26)
{ {
@ -1672,34 +1739,38 @@ void CEXISlippi::startFindMatch(u8* payload)
void CEXISlippi::prepareOnlineMatchState() void CEXISlippi::prepareOnlineMatchState()
{ {
// This match block is a VS match with P1 Red Falco vs P2 Red Bowser on Battlefield. The proper values will // This match block is a VS match with P1 Red Falco vs P2 Red Bowser on Battlefield. The proper
// be overwritten // 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, 0x1F, 0x00, 0x00, 0x32, 0x01, 0x86, 0x4C, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x6E, 0x00,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x05, 0x00, 0x04,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0xC0, 0x00, 0x04, 0x01, 0x00, 0x00,
0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00,
0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x1A, 0x03, 0x04, 0x00, 0x00, 0xFF,
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09, 0x00, 0x78, 0x00, 0x40, 0x00, 0x04,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80,
0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x21, 0x03, 0x04, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x09,
0x00, 0x78, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00,
}; };
m_read_queue.clear(); m_read_queue.clear();
auto errorState = SlippiMatchmaking::ProcessState::ERROR_ENCOUNTERED; auto errorState = SlippiMatchmaking::ProcessState::ERROR_ENCOUNTERED;
SlippiMatchmaking::ProcessState mmState = !forcedError.empty() ? errorState : matchmaking->GetMatchmakeState(); SlippiMatchmaking::ProcessState mmState =
!forcedError.empty() ? errorState : matchmaking->GetMatchmakeState();
#ifdef LOCAL_TESTING #ifdef LOCAL_TESTING
if (localSelections.isCharacterSelected || isLocalConnected) if (localSelections.isCharacterSelected || isLocalConnected)
@ -1735,7 +1806,8 @@ void CEXISlippi::prepareOnlineMatchState()
bool isConnected = true; bool isConnected = true;
#else #else
auto status = slippi_netplay->GetSlippiConnectStatus(); auto status = slippi_netplay->GetSlippiConnectStatus();
bool isConnected = status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED; bool isConnected =
status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED;
#endif #endif
if (isConnected) if (isConnected)
@ -1815,8 +1887,8 @@ void CEXISlippi::prepareOnlineMatchState()
onlineMatchBlock[0x63 + remotePlayerIndex * 0x24] = rps.characterColor; onlineMatchBlock[0x63 + remotePlayerIndex * 0x24] = rps.characterColor;
// Make one character lighter if same character, same color // Make one character lighter if same character, same color
bool isSheikVsZelda = bool isSheikVsZelda = lps.characterId == 0x12 && rps.characterId == 0x13 ||
lps.characterId == 0x12 && rps.characterId == 0x13 || lps.characterId == 0x13 && rps.characterId == 0x12; lps.characterId == 0x13 && rps.characterId == 0x12;
bool charMatch = lps.characterId == rps.characterId || isSheikVsZelda; bool charMatch = lps.characterId == rps.characterId || isSheikVsZelda;
bool colMatch = lps.characterColor == rps.characterColor; bool colMatch = lps.characterColor == rps.characterColor;
@ -1843,7 +1915,8 @@ void CEXISlippi::prepareOnlineMatchState()
// Set rng offset // Set rng offset
rngOffset = isDecider ? lps.rngOffset : rps.rngOffset; rngOffset = isDecider ? lps.rngOffset : rps.rngOffset;
WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset); WARN_LOG(SLIPPI_ONLINE, "Rng Offset: 0x%x", rngOffset);
WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60], onlineMatchBlock[0x84]); WARN_LOG(SLIPPI_ONLINE, "P1 Char: 0x%X, P2 Char: 0x%X", onlineMatchBlock[0x60],
onlineMatchBlock[0x84]);
// Set player names // Set player names
p1Name = isDecider ? lps.playerName : rps.playerName; p1Name = isDecider ? lps.playerName : rps.playerName;
@ -1989,8 +2062,8 @@ void CEXISlippi::logMessageFromGame(u8* payload)
} }
else else
{ {
GENERIC_LOG(Common::Log::SLIPPI, (Common::Log::LOG_LEVELS)payload[1], "%s: %llu", (char*)& payload[2], GENERIC_LOG(Common::Log::SLIPPI, (Common::Log::LOG_LEVELS)payload[1], "%s: %llu",
Common::Timer::GetTimeUs()); (char*)&payload[2], Common::Timer::GetTimeUs());
} }
} }
@ -2015,8 +2088,8 @@ void CEXISlippi::handleLogOutRequest()
void CEXISlippi::handleUpdateAppRequest() void CEXISlippi::handleUpdateAppRequest()
{ {
#ifdef __APPLE__ #ifdef __APPLE__
CriticalAlertT( CriticalAlertT("Automatic updates are not available for macOS, please get the latest update from "
"Automatic updates are not available for macOS, please get the latest update from slippi.gg/netplay."); "slippi.gg/netplay.");
#else #else
Host_LowerWindow(); Host_LowerWindow();
user->UpdateApp(); user->UpdateApp();
@ -2056,7 +2129,8 @@ void CEXISlippi::prepareOnlineStatus()
m_read_queue.insert(m_read_queue.end(), codeBuf, codeBuf + CONNECT_CODE_LENGTH + 2); m_read_queue.insert(m_read_queue.end(), codeBuf, codeBuf + CONNECT_CODE_LENGTH + 2);
} }
void doConnectionCleanup(std::unique_ptr<SlippiMatchmaking> mm, std::unique_ptr<SlippiNetplayClient> nc) void doConnectionCleanup(std::unique_ptr<SlippiMatchmaking> mm,
std::unique_ptr<SlippiNetplayClient> nc)
{ {
if (mm) if (mm)
mm.reset(); mm.reset();
@ -2119,8 +2193,9 @@ void CEXISlippi::DMAWrite(u32 _uAddr, u32 _uSize)
bufLoc += receiveCommandsLen + 1; bufLoc += receiveCommandsLen + 1;
} }
INFO_LOG(EXPANSIONINTERFACE, "EXI SLIPPI DMAWrite: addr: 0x%08x size: %d, bufLoc:[%02x %02x %02x %02x %02x]", INFO_LOG(EXPANSIONINTERFACE,
_uAddr, _uSize, memPtr[bufLoc], memPtr[bufLoc + 1], memPtr[bufLoc + 2], memPtr[bufLoc + 3], "EXI SLIPPI DMAWrite: addr: 0x%08x size: %d, bufLoc:[%02x %02x %02x %02x %02x]", _uAddr,
_uSize, memPtr[bufLoc], memPtr[bufLoc + 1], memPtr[bufLoc + 2], memPtr[bufLoc + 3],
memPtr[bufLoc + 4]); memPtr[bufLoc + 4]);
while (bufLoc < _uSize) while (bufLoc < _uSize)
@ -2218,8 +2293,9 @@ void CEXISlippi::DMARead(u32 addr, u32 size)
m_read_queue.resize(size, 0); // Resize response array to make sure it's all full/allocated m_read_queue.resize(size, 0); // Resize response array to make sure it's all full/allocated
auto queueAddr = &m_read_queue[0]; auto queueAddr = &m_read_queue[0];
INFO_LOG(EXPANSIONINTERFACE, "EXI SLIPPI DMARead: addr: 0x%08x size: %d, startResp: [%02x %02x %02x %02x %02x]", INFO_LOG(EXPANSIONINTERFACE,
addr, size, queueAddr[0], queueAddr[1], queueAddr[2], queueAddr[3], queueAddr[4]); "EXI SLIPPI DMARead: addr: 0x%08x size: %d, startResp: [%02x %02x %02x %02x %02x]", addr,
size, queueAddr[0], queueAddr[1], queueAddr[2], queueAddr[3], queueAddr[4]);
// Copy buffer data to memory // Copy buffer data to memory
Memory::CopyToEmu(addr, queueAddr, size); Memory::CopyToEmu(addr, queueAddr, size);
@ -2230,5 +2306,7 @@ bool CEXISlippi::IsPresent() const
return true; return true;
} }
void CEXISlippi::TransferByte(u8& byte) {} void CEXISlippi::TransferByte(u8& byte)
{
} }
} // namespace ExpansionInterface

View file

@ -7,9 +7,8 @@
#include <SlippiGame.h> #include <SlippiGame.h>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/File.h" #include "Common/File.h"
#include "EXI_Device.h" #include "Common/FileUtil.h"
#include "Core/Slippi/SlippiGameFileLoader.h" #include "Core/Slippi/SlippiGameFileLoader.h"
#include "Core/Slippi/SlippiMatchmaking.h" #include "Core/Slippi/SlippiMatchmaking.h"
#include "Core/Slippi/SlippiNetplay.h" #include "Core/Slippi/SlippiNetplay.h"
@ -17,6 +16,7 @@
#include "Core/Slippi/SlippiReplayComm.h" #include "Core/Slippi/SlippiReplayComm.h"
#include "Core/Slippi/SlippiSavestate.h" #include "Core/Slippi/SlippiSavestate.h"
#include "Core/Slippi/SlippiUser.h" #include "Core/Slippi/SlippiUser.h"
#include "EXI_Device.h"
#define ROLLBACK_MAX_FRAMES 7 #define ROLLBACK_MAX_FRAMES 7
#define MAX_NAME_LENGTH 15 #define MAX_NAME_LENGTH 15
@ -222,4 +222,4 @@ namespace ExpansionInterface
std::map<s32, std::unique_ptr<SlippiSavestate>> activeSavestates; std::map<s32, std::unique_ptr<SlippiSavestate>> activeSavestates;
std::deque<std::unique_ptr<SlippiSavestate>> availableSavestates; std::deque<std::unique_ptr<SlippiSavestate>> availableSavestates;
}; };
} } // namespace ExpansionInterface

View file

@ -1,12 +1,12 @@
#include "SlippiGameFileLoader.h" #include "SlippiGameFileLoader.h"
#include "Common/Logging/Log.h"
#include "Common/FileUtil.h"
#include "Common/File.h" #include "Common/File.h"
#include "Core/HW/DVD/DVDThread.h" #include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Core/Boot/Boot.h" #include "Core/Boot/Boot.h"
#include "Core/Core.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/DVD/DVDThread.h"
std::string getFilePath(std::string fileName) std::string getFilePath(std::string fileName)
{ {
@ -49,8 +49,10 @@ u32 SlippiGameFileLoader::LoadFile(std::string fileName, std::string& data)
std::string fileContents; std::string fileContents;
File::ReadFileToString(gameFilePath, fileContents); File::ReadFileToString(gameFilePath, fileContents);
// If the file was a diff file and the game is running, load the main file from ISO and apply patch // If the file was a diff file and the game is running, load the main file from ISO and apply
if (gameFilePath.substr(gameFilePath.length() - 5) == ".diff" && Core::GetState() == Core::State::Running) // patch
if (gameFilePath.substr(gameFilePath.length() - 5) == ".diff" &&
Core::GetState() == Core::State::Running)
{ {
std::vector<u8> buf; std::vector<u8> buf;
INFO_LOG(SLIPPI, "Will process diff"); INFO_LOG(SLIPPI, "Will process diff");

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#include "Common/CommonTypes.h"
#include <open-vcdiff/src/google/vcdecoder.h> #include <open-vcdiff/src/google/vcdecoder.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "Common/CommonTypes.h"
class SlippiGameFileLoader class SlippiGameFileLoader
{ {

View file

@ -1,11 +1,11 @@
#include "SlippiMatchmaking.h" #include "SlippiMatchmaking.h"
#include <string>
#include <vector>
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/Version.h"
#include "Common/ENetUtil.h" #include "Common/ENetUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include <string> #include "Common/Version.h"
#include <vector>
class MmMessageType class MmMessageType
{ {
@ -28,7 +28,8 @@ SlippiMatchmaking::SlippiMatchmaking(SlippiUser* user)
m_client = nullptr; m_client = nullptr;
m_server = nullptr; m_server = nullptr;
MM_HOST = Common::scm_slippi_semver_str.find("dev") == std::string::npos ? MM_HOST_PROD : MM_HOST_DEV; MM_HOST =
Common::scm_slippi_semver_str.find("dev") == std::string::npos ? MM_HOST_PROD : MM_HOST_DEV;
generator = std::default_random_engine(Common::Timer::GetTimeMs()); generator = std::default_random_engine(Common::Timer::GetTimeMs());
} }
@ -111,7 +112,8 @@ int SlippiMatchmaking::receiveMessage(json& msg, int timeoutMs)
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE:
{ {
std::vector<u8> buf; std::vector<u8> buf;
buf.insert(buf.end(), netEvent.packet->data, netEvent.packet->data + netEvent.packet->dataLength); buf.insert(buf.end(), netEvent.packet->data,
netEvent.packet->data + netEvent.packet->dataLength);
std::string str(buf.begin(), buf.end()); std::string str(buf.begin(), buf.end());
INFO_LOG(SLIPPI_ONLINE, "[Matchmaking] Received: %s", str.c_str()); INFO_LOG(SLIPPI_ONLINE, "[Matchmaking] Received: %s", str.c_str());
@ -296,7 +298,8 @@ void SlippiMatchmaking::startMatchmaking()
int rcvRes = receiveMessage(response, 5000); int rcvRes = receiveMessage(response, 5000);
if (rcvRes != 0) if (rcvRes != 0)
{ {
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Did not receive response from server for create ticket"); ERROR_LOG(SLIPPI_ONLINE,
"[Matchmaking] Did not receive response from server for create ticket");
m_state = ProcessState::ERROR_ENCOUNTERED; m_state = ProcessState::ERROR_ENCOUNTERED;
m_errorMsg = "Failed to join mm queue"; m_errorMsg = "Failed to join mm queue";
return; return;
@ -364,7 +367,8 @@ void SlippiMatchmaking::handleMatchmaking()
if (latestVersion != "") if (latestVersion != "")
{ {
// Update file to get new version number when the mm server tells us our version is outdated // Update file to get new version number when the mm server tells us our version is outdated
m_user->OverwriteLatestVersion(latestVersion); // Force latest version for people whose file updates dont work m_user->OverwriteLatestVersion(
latestVersion); // Force latest version for people whose file updates dont work
} }
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Received error from server for get ticket"); ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Received error from server for get ticket");
@ -382,7 +386,8 @@ void SlippiMatchmaking::handleMatchmaking()
terminateMmConnection(); terminateMmConnection();
m_state = ProcessState::OPPONENT_CONNECTING; m_state = ProcessState::OPPONENT_CONNECTING;
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Opponent found. isDecider: %s", m_isHost ? "true" : "false"); ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Opponent found. isDecider: %s",
m_isHost ? "true" : "false");
} }
void SlippiMatchmaking::handleConnecting() void SlippiMatchmaking::handleConnecting()
@ -390,7 +395,8 @@ void SlippiMatchmaking::handleConnecting()
std::vector<std::string> ipParts = SplitString(m_oppIp, ':'); std::vector<std::string> ipParts = SplitString(m_oppIp, ':');
// Is host is now used to specify who the decider is // Is host is now used to specify who the decider is
auto client = std::make_unique<SlippiNetplayClient>(ipParts[0], std::stoi(ipParts[1]), m_hostPort, m_isHost); auto client = std::make_unique<SlippiNetplayClient>(ipParts[0], std::stoi(ipParts[1]), m_hostPort,
m_isHost);
while (!m_netplayClient) while (!m_netplayClient)
{ {
@ -408,7 +414,8 @@ void SlippiMatchmaking::handleConnecting()
} }
else if (status != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED) else if (status != SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_CONNECTED)
{ {
ERROR_LOG(SLIPPI_ONLINE, "[Matchmaking] Connection attempt failed, looking for someone else."); ERROR_LOG(SLIPPI_ONLINE,
"[Matchmaking] Connection attempt failed, looking for someone else.");
// Return to the start to get a new ticket to find someone else we can hopefully connect with // Return to the start to get a new ticket to find someone else we can hopefully connect with
m_netplayClient = nullptr; m_netplayClient = nullptr;

View file

@ -6,9 +6,9 @@
#include "Core/Slippi/SlippiUser.h" #include "Core/Slippi/SlippiUser.h"
#include <enet/enet.h> #include <enet/enet.h>
#include <random>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <random>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using json = nlohmann::json; using json = nlohmann::json;

View file

@ -11,10 +11,10 @@
#include "Common/MD5.h" #include "Common/MD5.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
#include "Common/Timer.h" #include "Common/Timer.h"
#include "Core/ConfigManager.h"
#include "Core/NetPlayProto.h"
#include "Core/Core.h"
#include "Core/Config/NetplaySettings.h" #include "Core/Config/NetplaySettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/NetPlayProto.h"
//#include "Core/HW/EXI_DeviceIPL.h" //#include "Core/HW/EXI_DeviceIPL.h"
//#include "Core/HW/SI.h" //#include "Core/HW/SI.h"
//#include "Core/HW/SI_DeviceGCController.h" //#include "Core/HW/SI_DeviceGCController.h"
@ -22,16 +22,16 @@
#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/HW/WiimoteReal/WiimoteReal.h"
//#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_emu.h" //#include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_emu.h"
#include "Core/Movie.h"
#include "InputCommon/GCAdapter.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoConfig.h"
#include <SlippiGame.h> #include <SlippiGame.h>
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
#include <mbedtls/md5.h> #include <mbedtls/md5.h>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include "Core/Movie.h"
#include "InputCommon/GCAdapter.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoConfig.h"
static std::mutex pad_mutex; static std::mutex pad_mutex;
static std::mutex ack_mutex; static std::mutex ack_mutex;
@ -62,11 +62,10 @@ SlippiNetplayClient::~SlippiNetplayClient()
} }
// called from ---SLIPPI EXI--- thread // called from ---SLIPPI EXI--- thread
SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 remotePort, const u16 localPort, SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 remotePort,
bool isDecider) const u16 localPort, bool isDecider)
#ifdef _WIN32 #ifdef _WIN32
: m_qos_handle(nullptr) : m_qos_handle(nullptr), m_qos_flow_id(0)
, m_qos_flow_id(0)
#endif #endif
{ {
WARN_LOG(SLIPPI_ONLINE, "Initializing Slippi Netplay for port: %d, with host: %s", localPort, WARN_LOG(SLIPPI_ONLINE, "Initializing Slippi Netplay for port: %d, with host: %s", localPort,
@ -78,9 +77,10 @@ SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 r
ENetAddress* localAddr = nullptr; ENetAddress* localAddr = nullptr;
ENetAddress localAddrDef; ENetAddress localAddrDef;
// It is important to be able to set the local port to listen on even in a client connection because // It is important to be able to set the local port to listen on even in a client connection
// not doing so will break hole punching, the host is expecting traffic to come from a specific ip/port // because not doing so will break hole punching, the host is expecting traffic to come from a
// and if the port does not match what it is expecting, it will not get through the NAT on some routers // specific ip/port and if the port does not match what it is expecting, it will not get through
// the NAT on some routers
if (localPort > 0) if (localPort > 0)
{ {
INFO_LOG(SLIPPI_ONLINE, "Setting up local address"); INFO_LOG(SLIPPI_ONLINE, "Setting up local address");
@ -91,7 +91,8 @@ SlippiNetplayClient::SlippiNetplayClient(const std::string& address, const u16 r
localAddr = &localAddrDef; localAddr = &localAddrDef;
} }
// TODO: Figure out how to use a local port when not hosting without accepting incoming connections // TODO: Figure out how to use a local port when not hosting without accepting incoming
// connections
m_client = enet_host_create(localAddr, 2, 3, 0, 0); m_client = enet_host_create(localAddr, 2, 3, 0, 0);
if (m_client == nullptr) if (m_client == nullptr)
@ -126,7 +127,8 @@ SlippiNetplayClient::SlippiNetplayClient(bool isDecider)
unsigned int SlippiNetplayClient::OnData(sf::Packet& packet) unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
{ {
NetPlay::MessageId mid = 0; NetPlay::MessageId mid = 0;
if (!(packet >> mid)) { if (!(packet >> mid))
{
ERROR_LOG(SLIPPI_ONLINE, "Received empty netplay packet"); ERROR_LOG(SLIPPI_ONLINE, "Received empty netplay packet");
return 0; return 0;
} }
@ -136,7 +138,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
case NetPlay::NP_MSG_SLIPPI_PAD: case NetPlay::NP_MSG_SLIPPI_PAD:
{ {
int32_t frame; int32_t frame;
if (!(packet >> frame)) { if (!(packet >> frame))
{
ERROR_LOG(SLIPPI_ONLINE, "Netplay packet too small to read frame count"); ERROR_LOG(SLIPPI_ONLINE, "Netplay packet too small to read frame count");
break; break;
} }
@ -151,8 +154,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
auto timing = lastFrameTiming; auto timing = lastFrameTiming;
if (!hasGameStarted) if (!hasGameStarted)
{ {
// Handle case where opponent starts sending inputs before our game has reached frame 1. This will // Handle case where opponent starts sending inputs before our game has reached frame 1. This
// continuously say frame 0 is now to prevent opp from getting too far ahead // will continuously say frame 0 is now to prevent opp from getting too far ahead
timing.frame = 0; timing.frame = 0;
timing.timeUs = curTime; timing.timeUs = curTime;
} }
@ -161,8 +164,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
s64 frameDiffOffsetUs = 16683 * (timing.frame - frame); s64 frameDiffOffsetUs = 16683 * (timing.frame - frame);
s64 timeOffsetUs = opponentSendTimeUs - timing.timeUs + frameDiffOffsetUs; s64 timeOffsetUs = opponentSendTimeUs - timing.timeUs + frameDiffOffsetUs;
INFO_LOG(SLIPPI_ONLINE, "[Offset] Opp Frame: %d, My Frame: %d. Time offset: %lld", frame, timing.frame, INFO_LOG(SLIPPI_ONLINE, "[Offset] Opp Frame: %d, My Frame: %d. Time offset: %lld", frame,
timeOffsetUs); timing.frame, timeOffsetUs);
// Add this offset to circular buffer for use later // Add this offset to circular buffer for use later
if (frameOffsetData.buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL) if (frameOffsetData.buf.size() < SLIPPI_ONLINE_LOCKSTEP_INTERVAL)
@ -183,18 +186,20 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
int inputsToCopy = frame - headFrame; int inputsToCopy = frame - headFrame;
// Check that the packet actually contains the data it claims to // Check that the packet actually contains the data it claims to
if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize()) { if ((5 + inputsToCopy * SLIPPI_PAD_DATA_SIZE) > (int)packet.getDataSize())
{
ERROR_LOG(SLIPPI_ONLINE, "Netplay packet too small to read pad buffer"); ERROR_LOG(SLIPPI_ONLINE, "Netplay packet too small to read pad buffer");
break; break;
} }
for (int i = inputsToCopy - 1; i >= 0; i--) for (int i = inputsToCopy - 1; i >= 0; i--)
{ {
auto pad = std::make_unique<SlippiPad>(frame - i, &packetData[5 + i * SLIPPI_PAD_DATA_SIZE]); auto pad =
std::make_unique<SlippiPad>(frame - i, &packetData[5 + i * SLIPPI_PAD_DATA_SIZE]);
INFO_LOG(SLIPPI_ONLINE, "Rcv [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", pad->frame, INFO_LOG(SLIPPI_ONLINE, "Rcv [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", pad->frame,
pad->padBuf[0], pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[5], pad->padBuf[0], pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4],
pad->padBuf[6], pad->padBuf[7]); pad->padBuf[5], pad->padBuf[6], pad->padBuf[7]);
remotePadQueue.push_front(std::move(pad)); remotePadQueue.push_front(std::move(pad));
} }
@ -214,7 +219,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
// Store last frame acked // Store last frame acked
int32_t frame; int32_t frame;
if (!(packet >> frame)) { if (!(packet >> frame))
{
ERROR_LOG(SLIPPI_ONLINE, "Ack packet too small to read frame"); ERROR_LOG(SLIPPI_ONLINE, "Ack packet too small to read frame");
break; break;
} }
@ -239,8 +245,9 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
pingUs = Common::Timer::GetTimeUs() - sendTime; pingUs = Common::Timer::GetTimeUs() - sendTime;
if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0) if (g_ActiveConfig.bShowNetPlayPing && frame % SLIPPI_PING_DISPLAY_INTERVAL == 0)
{ {
OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, StringFromFormat("Ping: %u", pingUs / 1000), OSD::AddTypedMessage(OSD::MessageType::NetPlayPing,
OSD::Duration::NORMAL, OSD::Color::CYAN); StringFromFormat("Ping: %u", pingUs / 1000), OSD::Duration::NORMAL,
OSD::Color::CYAN);
} }
} }
break; break;
@ -259,7 +266,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
// This might be a good place to reset some logic? Game can't start until we receive this msg // This might be a good place to reset some logic? Game can't start until we receive this msg
// so this should ensure that everything is initialized before the game starts // so this should ensure that everything is initialized before the game starts
// TODO: This could cause issues in the case of a desync? If this is ever received mid-game, bad things // TODO: This could cause issues in the case of a desync? If this is ever received mid-game, bad
// things
// TODO: will happen. Consider improving this // TODO: will happen. Consider improving this
hasGameStarted = false; hasGameStarted = false;
} }
@ -267,7 +275,8 @@ unsigned int SlippiNetplayClient::OnData(sf::Packet& packet)
case NetPlay::NP_MSG_SLIPPI_CONN_SELECTED: case NetPlay::NP_MSG_SLIPPI_CONN_SELECTED:
{ {
// Currently this is unused but the intent is to support two-way simultaneous connection attempts // Currently this is unused but the intent is to support two-way simultaneous connection
// attempts
isConnectionSelected = true; isConnectionSelected = true;
} }
break; break;
@ -290,7 +299,8 @@ void SlippiNetplayClient::writeToPacket(sf::Packet& packet, SlippiPlayerSelectio
packet << s.connectCode; packet << s.connectCode;
} }
std::unique_ptr<SlippiPlayerSelections> SlippiNetplayClient::readSelectionsFromPacket(sf::Packet& packet) std::unique_ptr<SlippiPlayerSelections>
SlippiNetplayClient::readSelectionsFromPacket(sf::Packet& packet)
{ {
auto s = std::make_unique<SlippiPlayerSelections>(); auto s = std::make_unique<SlippiPlayerSelections>();
@ -424,7 +434,8 @@ void SlippiNetplayClient::ThreadFunc()
// this will fail if we're not admin // this will fail if we're not admin
// sets DSCP to the same as linux (0x2e) // sets DSCP to the same as linux (0x2e)
QOSSetFlow(m_qos_handle, m_qos_flow_id, QOSSetOutgoingDSCPValue, sizeof(DWORD), &dscp, 0, nullptr); QOSSetFlow(m_qos_handle, m_qos_flow_id, QOSSetOutgoingDSCPValue, sizeof(DWORD), &dscp, 0,
nullptr);
qos_success = true; qos_success = true;
} }
@ -441,7 +452,8 @@ void SlippiNetplayClient::ThreadFunc()
// https://www.tucny.com/Home/dscp-tos // https://www.tucny.com/Home/dscp-tos
// ef is better than cs7 // ef is better than cs7
int tos_val = 0xb8; int tos_val = 0xb8;
qos_success = setsockopt(m_server->host->socket, IPPROTO_IP, IP_TOS, &tos_val, sizeof(tos_val)) == 0; qos_success =
setsockopt(m_server->host->socket, IPPROTO_IP, IP_TOS, &tos_val, sizeof(tos_val)) == 0;
} }
#endif #endif
@ -550,8 +562,10 @@ void SlippiNetplayClient::SendConnectionSelected()
void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad) void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
{ {
auto status = slippiConnectStatus; auto status = slippiConnectStatus;
bool connectionFailed = status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_FAILED; bool connectionFailed =
bool connectionDisconnected = status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED; status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_FAILED;
bool connectionDisconnected =
status == SlippiNetplayClient::SlippiConnectStatus::NET_CONNECT_STATUS_DISCONNECTED;
if (connectionFailed || connectionDisconnected) if (connectionFailed || connectionDisconnected)
{ {
return; return;
@ -559,8 +573,9 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
// if (pad && isDecider) // if (pad && isDecider)
//{ //{
// ERROR_LOG(SLIPPI_ONLINE, "[%d] %X %X %X %X %X %X %X %X", pad->frame, pad->padBuf[0], pad->padBuf[1], // ERROR_LOG(SLIPPI_ONLINE, "[%d] %X %X %X %X %X %X %X %X", pad->frame, pad->padBuf[0],
// pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[5], pad->padBuf[6], pad->padBuf[7]); // pad->padBuf[1], pad->padBuf[2], pad->padBuf[3], pad->padBuf[4], pad->padBuf[5],
// pad->padBuf[6], pad->padBuf[7]);
//} //}
if (pad) if (pad)
@ -590,9 +605,9 @@ void SlippiNetplayClient::SendSlippiPad(std::unique_ptr<SlippiPad> pad)
INFO_LOG(SLIPPI_ONLINE, "Sending a packet of inputs [%d]...", frame); INFO_LOG(SLIPPI_ONLINE, "Sending a packet of inputs [%d]...", frame);
for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it) for (auto it = localPadQueue.begin(); it != localPadQueue.end(); ++it)
{ {
INFO_LOG(SLIPPI_ONLINE, "Send [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", (*it)->frame, (*it)->padBuf[0], INFO_LOG(SLIPPI_ONLINE, "Send [%d] -> %02X %02X %02X %02X %02X %02X %02X %02X", (*it)->frame,
(*it)->padBuf[1], (*it)->padBuf[2], (*it)->padBuf[3], (*it)->padBuf[4], (*it)->padBuf[5], (*it)->padBuf[0], (*it)->padBuf[1], (*it)->padBuf[2], (*it)->padBuf[3],
(*it)->padBuf[6], (*it)->padBuf[7]); (*it)->padBuf[4], (*it)->padBuf[5], (*it)->padBuf[6], (*it)->padBuf[7]);
spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad spac->append((*it)->padBuf, SLIPPI_PAD_DATA_SIZE); // only transfer 8 bytes per pad
} }

View file

@ -4,6 +4,16 @@
#pragma once #pragma once
#include <SFML/Network/Packet.hpp>
#include <array>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/Timer.h" #include "Common/Timer.h"
@ -11,22 +21,13 @@
#include "Core/NetPlayProto.h" #include "Core/NetPlayProto.h"
#include "Core/Slippi/SlippiPad.h" #include "Core/Slippi/SlippiPad.h"
#include "InputCommon/GCPadStatus.h" #include "InputCommon/GCPadStatus.h"
#include <SFML/Network/Packet.hpp>
#include <array>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <queue>
#ifdef _WIN32 #ifdef _WIN32
#include <Qos2.h> #include <Qos2.h>
#endif #endif
#define SLIPPI_ONLINE_LOCKSTEP_INTERVAL 30 // Number of frames to wait before attempting to time-sync #define SLIPPI_ONLINE_LOCKSTEP_INTERVAL \
30 // Number of frames to wait before attempting to time-sync
#define SLIPPI_PING_DISPLAY_INTERVAL 60 #define SLIPPI_PING_DISPLAY_INTERVAL 60
struct SlippiRemotePadOutput struct SlippiRemotePadOutput
@ -104,7 +105,8 @@ public:
void SendAsync(std::unique_ptr<sf::Packet> packet); void SendAsync(std::unique_ptr<sf::Packet> packet);
SlippiNetplayClient(bool isDecider); // Make a dummy client SlippiNetplayClient(bool isDecider); // Make a dummy client
SlippiNetplayClient(const std::string& address, const u16 remotePort, const u16 localPort, bool isDecider); SlippiNetplayClient(const std::string& address, const u16 remotePort, const u16 localPort,
bool isDecider);
~SlippiNetplayClient(); ~SlippiNetplayClient();
// Slippi Online // Slippi Online

View file

@ -15,4 +15,3 @@ public:
int32_t frame; int32_t frame;
u8 padBuf[SLIPPI_PAD_FULL_SIZE]; u8 padBuf[SLIPPI_PAD_FULL_SIZE];
}; };

View file

@ -86,8 +86,7 @@ void SlippiPlaybackStatus::prepareSlippiPlayback(s32& frameIndex)
// TODO: figure out why sometimes playback frame increments past targetFrameNum // TODO: figure out why sometimes playback frame increments past targetFrameNum
if (inSlippiPlayback && frameIndex >= targetFrameNum) if (inSlippiPlayback && frameIndex >= targetFrameNum)
{ {
INFO_LOG(SLIPPI, "Reached frame %d. Target was %d. Unblocking", frameIndex, INFO_LOG(SLIPPI, "Reached frame %d. Target was %d. Unblocking", frameIndex, targetFrameNum);
targetFrameNum);
cv_waitingForTargetFrame.notify_one(); cv_waitingForTargetFrame.notify_one();
} }
} }
@ -136,7 +135,8 @@ void SlippiPlaybackStatus::SavestateThread()
{ {
// Wait to hit one of the intervals // Wait to hit one of the intervals
// Possible while rewinding that we hit this wait again. // Possible while rewinding that we hit this wait again.
while (shouldRunThreads && (currentPlaybackFrame - Slippi::PLAYBACK_FIRST_SAVE) % FRAME_INTERVAL != 0) while (shouldRunThreads &&
(currentPlaybackFrame - Slippi::PLAYBACK_FIRST_SAVE) % FRAME_INTERVAL != 0)
condVar.wait(intervalLock); condVar.wait(intervalLock);
if (!shouldRunThreads) if (!shouldRunThreads)
@ -169,11 +169,13 @@ void SlippiPlaybackStatus::SavestateThread()
void SlippiPlaybackStatus::seekToFrame() void SlippiPlaybackStatus::seekToFrame()
{ {
if (seekMtx.try_lock()) { if (seekMtx.try_lock())
{
if (targetFrameNum < Slippi::PLAYBACK_FIRST_SAVE) if (targetFrameNum < Slippi::PLAYBACK_FIRST_SAVE)
targetFrameNum = Slippi::PLAYBACK_FIRST_SAVE; targetFrameNum = Slippi::PLAYBACK_FIRST_SAVE;
if (targetFrameNum > lastFrame) { if (targetFrameNum > lastFrame)
{
targetFrameNum = lastFrame; targetFrameNum = lastFrame;
} }
@ -186,8 +188,10 @@ void SlippiPlaybackStatus::seekToFrame()
if (prevState != Core::State::Paused) if (prevState != Core::State::Paused)
Core::SetState(Core::State::Paused); Core::SetState(Core::State::Paused);
s32 closestStateFrame = targetFrameNum - emod(targetFrameNum - Slippi::PLAYBACK_FIRST_SAVE, FRAME_INTERVAL); s32 closestStateFrame =
bool isLoadingStateOptimal = targetFrameNum < currentPlaybackFrame || closestStateFrame > currentPlaybackFrame; targetFrameNum - emod(targetFrameNum - Slippi::PLAYBACK_FIRST_SAVE, FRAME_INTERVAL);
bool isLoadingStateOptimal =
targetFrameNum < currentPlaybackFrame || closestStateFrame > currentPlaybackFrame;
if (isLoadingStateOptimal) if (isLoadingStateOptimal)
{ {
@ -217,7 +221,8 @@ void SlippiPlaybackStatus::seekToFrame()
futureDiffs.count(closestActualStateFrame) == 0) futureDiffs.count(closestActualStateFrame) == 0)
closestActualStateFrame -= FRAME_INTERVAL; closestActualStateFrame -= FRAME_INTERVAL;
// only load a savestate if we find one past our current frame since we are seeking forwards // only load a savestate if we find one past our current frame since we are seeking
// forwards
if (closestActualStateFrame > currentPlaybackFrame) if (closestActualStateFrame > currentPlaybackFrame)
loadState(closestActualStateFrame); loadState(closestActualStateFrame);
} }
@ -234,18 +239,22 @@ void SlippiPlaybackStatus::seekToFrame()
setHardFFW(false); setHardFFW(false);
} }
// We've reached the frame we want. Reset targetFrameNum and release mutex so another seek can be performed // We've reached the frame we want. Reset targetFrameNum and release mutex so another seek can
// be performed
g_playbackStatus->currentPlaybackFrame = targetFrameNum; g_playbackStatus->currentPlaybackFrame = targetFrameNum;
targetFrameNum = INT_MAX; targetFrameNum = INT_MAX;
Core::SetState(prevState); Core::SetState(prevState);
seekMtx.unlock(); seekMtx.unlock();
} else { }
else
{
INFO_LOG(SLIPPI, "Already seeking. Ignoring this call"); INFO_LOG(SLIPPI, "Already seeking. Ignoring this call");
} }
} }
// Set isHardFFW and update OC settings to speed up the FFW // Set isHardFFW and update OC settings to speed up the FFW
void SlippiPlaybackStatus::setHardFFW(bool enable) { void SlippiPlaybackStatus::setHardFFW(bool enable)
{
if (enable) if (enable)
{ {
SConfig::GetInstance().m_OCEnable = true; SConfig::GetInstance().m_OCEnable = true;
@ -260,7 +269,6 @@ void SlippiPlaybackStatus::setHardFFW(bool enable) {
isHardFFW = enable; isHardFFW = enable;
} }
void SlippiPlaybackStatus::loadState(s32 closestStateFrame) void SlippiPlaybackStatus::loadState(s32 closestStateFrame)
{ {
if (closestStateFrame == Slippi::PLAYBACK_FIRST_SAVE) if (closestStateFrame == Slippi::PLAYBACK_FIRST_SAVE)
@ -268,7 +276,8 @@ void SlippiPlaybackStatus::loadState(s32 closestStateFrame)
else else
{ {
std::string stateString; std::string stateString;
decoder.Decode((char*)iState.data(), iState.size(), futureDiffs[closestStateFrame].get(), &stateString); decoder.Decode((char*)iState.data(), iState.size(), futureDiffs[closestStateFrame].get(),
&stateString);
std::vector<u8> stateToLoad(stateString.begin(), stateString.end()); std::vector<u8> stateToLoad(stateString.begin(), stateString.end());
State::LoadFromBuffer(stateToLoad); State::LoadFromBuffer(stateToLoad);
} }

View file

@ -5,12 +5,12 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <SlippiLib/SlippiGame.h>
#include <open-vcdiff/src/google/vcdecoder.h> #include <open-vcdiff/src/google/vcdecoder.h>
#include <open-vcdiff/src/google/vcencoder.h> #include <open-vcdiff/src/google/vcencoder.h>
#include <SlippiLib/SlippiGame.h>
#include "Core/ConfigManager.h"
#include "../../Common/CommonTypes.h" #include "../../Common/CommonTypes.h"
#include "Core/ConfigManager.h"
class SlippiPlaybackStatus class SlippiPlaybackStatus
{ {

View file

@ -1,6 +1,6 @@
#include "SlippiReplayComm.h"
#include <cctype> #include <cctype>
#include <memory> #include <memory>
#include "SlippiReplayComm.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Logging/LogManager.h" #include "Common/Logging/LogManager.h"
@ -18,7 +18,8 @@ static inline void ltrim(std::string& s)
// trim from end (in place) // trim from end (in place)
static inline void rtrim(std::string& s) static inline void rtrim(std::string& s)
{ {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(),
s.end());
} }
// trim from both ends (in place) // trim from both ends (in place)
@ -35,7 +36,9 @@ SlippiReplayComm::SlippiReplayComm()
configFilePath = SConfig::GetInstance().m_strSlippiInput.c_str(); configFilePath = SConfig::GetInstance().m_strSlippiInput.c_str();
} }
SlippiReplayComm::~SlippiReplayComm() {} SlippiReplayComm::~SlippiReplayComm()
{
}
SlippiReplayComm::CommSettings SlippiReplayComm::getSettings() SlippiReplayComm::CommSettings SlippiReplayComm::getSettings()
{ {
@ -177,8 +180,8 @@ void SlippiReplayComm::loadFile()
{ {
WARN_LOG(EXPANSIONINTERFACE, "Comm file load error detected. Check file format"); WARN_LOG(EXPANSIONINTERFACE, "Comm file load error detected. Check file format");
// Reset in the case of read error. this fixes a race condition where file mod time changes but // Reset in the case of read error. this fixes a race condition where file mod time changes
// the file is not readable yet? // but the file is not readable yet?
configLastLoadModTime = 0; configLastLoadModTime = 0;
} }

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#include <SlippiGame.h>
#include <limits.h> #include <limits.h>
#include <nlohmann/json.hpp>
#include <queue> #include <queue>
#include <string> #include <string>
#include <nlohmann/json.hpp>
#include <SlippiGame.h>
#include <Common/CommonTypes.h> #include <Common/CommonTypes.h>

View file

@ -1,4 +1,5 @@
#include "SlippiSavestate.h" #include "SlippiSavestate.h"
#include <vector>
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/MemoryUtil.h" #include "Common/MemoryUtil.h"
#include "Core/HW/AudioInterface.h" #include "Core/HW/AudioInterface.h"
@ -11,7 +12,6 @@
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI.h"
#include "Core/HW/VideoInterface.h" #include "Core/HW/VideoInterface.h"
#include <vector>
SlippiSavestate::SlippiSavestate() SlippiSavestate::SlippiSavestate()
{ {

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <unordered_map>
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include <unordered_map>
class PointerWrap; class PointerWrap;
@ -14,7 +14,10 @@ public:
u32 address; u32 address;
u32 length; u32 length;
bool operator==(const PreserveBlock& p) const { return address == p.address && length == p.length; } bool operator==(const PreserveBlock& p) const
{
return address == p.address && length == p.length;
}
}; };
SlippiSavestate(); SlippiSavestate();

View file

@ -110,19 +110,21 @@ bool SlippiUser::AttemptLogin()
{ {
std::string userFilePathTxt = std::string userFilePathTxt =
userFilePath + ".txt"; // Put the filename here in its own scope because we don't really need it elsewhere userFilePath +
".txt"; // Put the filename here in its own scope because we don't really need it elsewhere
if (File::Exists(userFilePathTxt)) if (File::Exists(userFilePathTxt))
{ {
// If both files exist we just log they exist and take no further action // If both files exist we just log they exist and take no further action
if (File::Exists(userFilePath)) if (File::Exists(userFilePath))
{ {
INFO_LOG(SLIPPI_ONLINE, INFO_LOG(SLIPPI_ONLINE, "Found both .json.txt and .json file for user data. Using .json "
"Found both .json.txt and .json file for user data. Using .json and ignoring the .json.txt"); "and ignoring the .json.txt");
} }
// If only the .txt file exists move the contents to a json file and log if it fails // If only the .txt file exists move the contents to a json file and log if it fails
else if (!File::Rename(userFilePathTxt, userFilePath)) else if (!File::Rename(userFilePathTxt, userFilePath))
{ {
WARN_LOG(SLIPPI_ONLINE, "Could not move file %s to %s", userFilePathTxt.c_str(), userFilePath.c_str()); WARN_LOG(SLIPPI_ONLINE, "Could not move file %s to %s", userFilePathTxt.c_str(),
userFilePath.c_str());
} }
} }
} }
@ -137,7 +139,8 @@ bool SlippiUser::AttemptLogin()
if (isLoggedIn) if (isLoggedIn)
{ {
overwriteFromServer(); overwriteFromServer();
WARN_LOG(SLIPPI_ONLINE, "Found user %s (%s)", userInfo.displayName.c_str(), userInfo.uid.c_str()); WARN_LOG(SLIPPI_ONLINE, "Found user %s (%s)", userInfo.displayName.c_str(),
userInfo.uid.c_str());
} }
return isLoggedIn; return isLoggedIn;
@ -149,7 +152,8 @@ void SlippiUser::OpenLogInPage()
std::string path = getUserFilePath(); std::string path = getUserFilePath();
#ifdef _WIN32 #ifdef _WIN32
// On windows, sometimes the path can have backslashes and slashes mixed, convert all to backslashes // On windows, sometimes the path can have backslashes and slashes mixed, convert all to
// backslashes
path = ReplaceAll(path, "\\", "\\"); path = ReplaceAll(path, "\\", "\\");
path = ReplaceAll(path, "/", "\\"); path = ReplaceAll(path, "/", "\\");
#endif #endif
@ -181,12 +185,15 @@ void SlippiUser::UpdateApp()
auto isoPath = SConfig::GetInstance().m_strIsoPath; auto isoPath = SConfig::GetInstance().m_strIsoPath;
std::string path = File::GetExeDirectory() + "/dolphin-slippi-tools.exe"; std::string path = File::GetExeDirectory() + "/dolphin-slippi-tools.exe";
std::string echoMsg = "echo Starting update process. If nothing happen after a few " std::string echoMsg =
"echo Starting update process. If nothing happen after a few "
"minutes, you may need to update manually from https://slippi.gg/netplay ..."; "minutes, you may need to update manually from https://slippi.gg/netplay ...";
// std::string command = // std::string command =
// "start /b cmd /c " + echoMsg + " && \"" + path + "\" app-update -launch -iso \"" + isoPath + "\""; // "start /b cmd /c " + echoMsg + " && \"" + path + "\" app-update -launch -iso \"" + isoPath +
std::string command = "start /b cmd /c " + echoMsg + " && \"" + path + "\" app-update -launch -iso \"" + isoPath + // "\"";
"\" -version \"" + Common::scm_slippi_semver_str + "\""; std::string command = "start /b cmd /c " + echoMsg + " && \"" + path +
"\" app-update -launch -iso \"" + isoPath + "\" -version \"" +
Common::scm_slippi_semver_str + "\"";
WARN_LOG(SLIPPI, "Executing app update command: %s", command.c_str()); WARN_LOG(SLIPPI, "Executing app update command: %s", command.c_str());
RunSystemCommand(command); RunSystemCommand(command);
#elif defined(__APPLE__) #elif defined(__APPLE__)
@ -263,7 +270,8 @@ void SlippiUser::FileListenThread()
std::string SlippiUser::getUserFilePath() std::string SlippiUser::getUserFilePath()
{ {
#if defined(__APPLE__) #if defined(__APPLE__)
std::string userFilePath = File::GetBundleDirectory() + "/Contents/Resources" + DIR_SEP + "user.json"; std::string userFilePath =
File::GetBundleDirectory() + "/Contents/Resources" + DIR_SEP + "user.json";
#elif defined(_WIN32) #elif defined(_WIN32)
std::string userFilePath = File::GetExeDirectory() + DIR_SEP + "user.json"; std::string userFilePath = File::GetExeDirectory() + DIR_SEP + "user.json";
#else #else

View file

@ -1,12 +1,12 @@
#pragma once #pragma once
#include "Common/CommonTypes.h"
#include <atomic> #include <atomic>
#include <curl/curl.h> #include <curl/curl.h>
#include <string> #include <string>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "Common/CommonTypes.h"
class SlippiUser class SlippiUser
{ {

View file

@ -101,10 +101,11 @@ void SlippiPane::CreateLayout()
layout->addWidget(playback_settings); layout->addWidget(playback_settings);
auto* enable_playback_seek_checkbox = new QCheckBox(tr("Enable Seekbar")); auto* enable_playback_seek_checkbox = new QCheckBox(tr("Enable Seekbar"));
char seekbarTooltip[] = "<html><head/><body><p>Enables video player style controls while watching Slippi replays. Uses more cpu resources and can be stuttery. " \ char seekbarTooltip[] = "<html><head/><body><p>Enables video player style controls while "
"Space: Pause/Play " \ "watching Slippi replays. Uses more cpu resources and can be stuttery. "
"Left/Right: Jump 5 seconds back/forward" \ "Space: Pause/Play "
"Shift + Left/Right: Jump 20 seconds back/forward" \ "Left/Right: Jump 5 seconds back/forward"
"Shift + Left/Right: Jump 20 seconds back/forward"
"Period (while paused): Advance one frame"; "Period (while paused): Advance one frame";
enable_playback_seek_checkbox->setToolTip(tr(seekbarTooltip)); enable_playback_seek_checkbox->setToolTip(tr(seekbarTooltip));
playback_settings_layout->addWidget(enable_playback_seek_checkbox); playback_settings_layout->addWidget(enable_playback_seek_checkbox);