From c5298cf12d7ad825867ecf1d1ee1f5c3d84e98af Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Wed, 15 Jun 2022 20:46:21 -0400 Subject: [PATCH] input-rec: refactor main input recording class --- pcsx2/Recording/InputRecording.cpp | 555 ++++++++++++++--------------- pcsx2/Recording/InputRecording.h | 124 ++----- 2 files changed, 308 insertions(+), 371 deletions(-) diff --git a/pcsx2/Recording/InputRecording.cpp b/pcsx2/Recording/InputRecording.cpp index e4e678da03..0de5c79d97 100644 --- a/pcsx2/Recording/InputRecording.cpp +++ b/pcsx2/Recording/InputRecording.cpp @@ -465,20 +465,29 @@ wxString InputRecording::resolveGameName() #else +#include "InputRecording.h" + +#include "InputRecordingControls.h" +#include "Utilities/InputRecordingLogger.h" + #include "common/FileSystem.h" #include "common/StringUtil.h" #include "SaveState.h" #include "Counters.h" #include "SaveState.h" - #include "VMManager.h" - #include "DebugTools/Debug.h" #include "GameDatabase.h" +#include "fmt/format.h" -#include "InputRecording.h" -#include "InputRecordingControls.h" -#include "Utilities/InputRecordingLogger.h" +// Future TODOs +// - restart +// - tooltips on GUI options +// - Controller Logging (virtual pad related) +// - persist last browsed IR path +// - logs are weirdly formatted +// - force OSD updates since a lot of input recording occurs during a paused state +// - differentiating OSD logs somehow would be nice (color / a preceding icon?) #include #include @@ -489,42 +498,107 @@ void SaveStateBase::InputRecordingFreeze() // CHANGING THIS WILL BREAK BACKWARDS COMPATIBILITY ON SAVESTATES FreezeTag("InputRecording"); Freeze(g_FrameCount); - - // Loading a save-state is an asynchronous task. If we are playing a recording - // that starts from a savestate (not power-on) and the starting (pcsx2 internal) frame - // marker has not been set (which comes from the save-state), we initialize it. - if (g_InputRecording.IsInitialLoad()) - g_InputRecording.SetupInitialState(g_FrameCount); - else if (g_InputRecording.IsActive() && IsLoading()) - g_InputRecording.SetFrameCounter(g_FrameCount); } InputRecording g_InputRecording; -InputRecording::InputRecordingPad::InputRecordingPad() +bool InputRecording::create(const std::string_view& fileName, const bool fromSaveState, const std::string_view& authorName) { - padData = new PadData; -} - -InputRecording::InputRecordingPad::~InputRecordingPad() -{ - delete padData; -} - -void InputRecording::RecordingReset() -{ - // Booting is an asynchronous task. If we are playing a recording - // that starts from power-on and the starting (pcsx2 internal) frame - // marker has not been set, we initialize it. - if (g_InputRecording.IsInitialLoad()) - g_InputRecording.SetupInitialState(0); - else if (g_InputRecording.IsActive()) + if (!m_file.OpenNew(fileName, fromSaveState)) { - g_InputRecording.SetFrameCounter(0); - g_InputRecordingControls.Lock(0); + return false; + } + + m_controls.setRecordMode(); + if (fromSaveState) + { + std::string savestatePath = fmt::format("{}_SaveState.p2s", fileName); + if (FileSystem::FileExists(savestatePath.c_str())) + { + FileSystem::CopyFilePath(savestatePath.c_str(), fmt::format("{}.bak", savestatePath).c_str(), true); + } + m_initialSavestateLoadComplete = false; + m_type = Type::FROM_SAVESTATE; + m_isActive = true; + // TODO - error handling + VMManager::SaveState(savestatePath.c_str()); } else - g_InputRecordingControls.Resume(); + { + m_startingFrame = 0; + m_type = Type::POWER_ON; + m_isActive = true; + // TODO - should this be an explicit [full] boot instead of a reset? + VMManager::Reset(); + } + + m_file.getHeader().SetEmulatorVersion(); + m_file.getHeader().SetAuthor(authorName); + m_file.getHeader().SetGameName(resolveGameName()); + m_file.WriteHeader(); + initializeState(); + InputRec::log("Started new input recording"); + InputRec::consoleLog(fmt::format("Filename {}", m_file.getFilename())); + return true; +} + +bool InputRecording::play(const std::string_view& filename) +{ + if (!m_file.OpenExisting(filename)) + { + return false; + } + + // Either load the savestate, or restart the game + if (m_file.FromSaveState()) + { + std::string savestatePath = fmt::format("{}_SaveState.p2s", m_file.getFilename()); + if (!FileSystem::FileExists(savestatePath.c_str())) + { + InputRec::consoleLog(fmt::format("Could not locate savestate file at location - {}", savestatePath)); + InputRec::log("Savestate load failed"); + m_file.Close(); + return false; + } + m_type = Type::FROM_SAVESTATE; + m_initialSavestateLoadComplete = false; + m_isActive = true; + const auto loaded = VMManager::LoadState(savestatePath.c_str()); + if (!loaded) + { + InputRec::log("Savestate load failed, unsupported version?"); + m_file.Close(); + m_isActive = false; + return false; + } + } + else + { + m_startingFrame = 0; + m_type = Type::POWER_ON; + m_isActive = true; + // TODO - should this be an explicit [full] boot instead of a reset? + VMManager::Reset(); + } + m_controls.setReplayMode(); + initializeState(); + InputRec::log("Replaying input recording"); + m_file.logRecordingMetadata(); + if (resolveGameName() != m_file.getHeader().gameName) + { + InputRec::consoleLog(fmt::format("Input recording was possibly constructed for a different game. Expected: {}, Actual: {}", m_file.getHeader().gameName, resolveGameName())); + } + + return true; +} + +void InputRecording::stop() +{ + m_isActive = false; + if (m_file.Close()) + { + InputRec::log("Input recording stopped"); + } } // TODO: Refactor this @@ -538,278 +612,42 @@ void InputRecording::ControllerInterrupt(u8 port, size_t fifoSize, u8 dataIn, u8 if (dataOut != READ_DATA_AND_VIBRATE_SECOND_BYTE) fInterruptFrame = false; } - else if (fInterruptFrame) + + // If there is data to read (previous two bytes looked correct) + if (bufCount >= 3 && m_padDataAvailable) { u8& bufVal = dataOut; const u16 bufIndex = fifoSize - 3; if (state == InputRecordingMode::Replaying) { - if (frameCounter >= 0 && frameCounter < INT_MAX) + if (!m_file.ReadKeyBuffer(bufVal, m_frameCounter, port, bufIndex)) { - if (!inputRecordingData.ReadKeyBuffer(bufVal, frameCounter, port, bufIndex)) - inputRec::consoleLog(fmt::format("Failed to read input data at frame {}", frameCounter)); - - // Update controller data state for future VirtualPad / logging usage. - pads[port].padData->UpdateControllerData(bufIndex, bufVal); + InputRec::consoleLog(fmt::format("Failed to read input data at frame {}", m_frameCounter)); } + // Update controller data state for future VirtualPad / logging usage. + //pads[port].padData->UpdateControllerData(bufIndex, bufVal); } else { // Update controller data state for future VirtualPad / logging usage. - pads[port].padData->UpdateControllerData(bufIndex, bufVal); + //pads[port].padData->UpdateControllerData(bufIndex, bufVal); // Commit the byte to the movie file if we are recording - if (state == InputRecordingMode::Recording) + if (m_controls.isRecording()) { - if (frameCounter >= 0) + if (!m_file.WriteKeyBuffer(m_frameCounter, port, bufIndex, bufVal)) { - if (incrementUndo) - { - inputRecordingData.IncrementUndoCount(); - incrementUndo = false; - } - - if (frameCounter < INT_MAX && !inputRecordingData.WriteKeyBuffer(frameCounter, port, bufIndex, bufVal)) - inputRec::consoleLog(fmt::format("Failed to write input data at frame {}", frameCounter)); + InputRec::consoleLog(fmt::format("Failed to write input data at frame {}", m_frameCounter)); } } } } -} - -s32 InputRecording::GetFrameCounter() -{ - return frameCounter; -} - -InputRecordingFile& InputRecording::GetInputRecordingData() -{ - return inputRecordingData; -} - -u32 InputRecording::GetStartingFrame() -{ - return startingFrame; -} - -void InputRecording::IncrementFrameCounter() -{ - if (frameCounter < INT_MAX) + if (bufCount > 20) { - frameCounter++; - switch (state) - { - case InputRecordingMode::Recording: - inputRecordingData.SetTotalFrames(frameCounter); - [[fallthrough]]; - case InputRecordingMode::Replaying: - if (frameCounter == inputRecordingData.GetTotalFrames()) - incrementUndo = false; - default: - break; - } + m_padDataAvailable = false; } } -void InputRecording::LogAndRedraw() -{ - for (u8 port = 0; port < 2; port++) - { - pads[port].padData->LogPadData(port); - } -} - -bool InputRecording::IsInterruptFrame() -{ - return fInterruptFrame; -} - -bool InputRecording::IsActive() -{ - return state != InputRecordingMode::NotActive; -} - -bool InputRecording::IsInitialLoad() -{ - return initialLoad; -} - -bool InputRecording::IsReplaying() -{ - return state == InputRecordingMode::Replaying; -} - -bool InputRecording::IsRecording() -{ - return state == InputRecordingMode::Recording; -} - -void InputRecording::SetToRecordMode() -{ - state = InputRecordingMode::Recording; - inputRec::log("Record mode ON"); -} - -void InputRecording::SetToReplayMode() -{ - state = InputRecordingMode::Replaying; - inputRec::log("Replay mode ON"); -} - -void InputRecording::SetFrameCounter(u32 newGFrameCount) -{ - if (newGFrameCount > startingFrame + (u32)inputRecordingData.GetTotalFrames()) - { - inputRec::consoleLog("Warning, you've loaded PCSX2 emulation to a point after the end of the original recording. This should be avoided."); - inputRec::consoleLog("Savestate's framecount has been ignored."); - frameCounter = inputRecordingData.GetTotalFrames(); - if (state == InputRecordingMode::Replaying) - SetToRecordMode(); - incrementUndo = false; - } - else - { - if (newGFrameCount < startingFrame) - { - inputRec::consoleLog("Warning, you've loaded PCSX2 emulation to a point before the start of the original recording. This should be avoided."); - if (state == InputRecordingMode::Recording) - SetToReplayMode(); - } - else if (newGFrameCount == 0 && state == InputRecordingMode::Recording) - SetToReplayMode(); - frameCounter = newGFrameCount - (s32)startingFrame; - incrementUndo = true; - } -} - -void InputRecording::SetupInitialState(u32 newStartingFrame) -{ - startingFrame = newStartingFrame; - if (state != InputRecordingMode::Replaying) - { - inputRec::log("Started new input recording"); - inputRec::consoleLog(fmt::format("Filename {}", inputRecordingData.GetFilename())); - SetToRecordMode(); - } - else - { - // Check if the current game matches with the one used to make the original recording - // TODO - Vaser - this should be the CRC in hindsight anyway - if (!VMManager::GetDiscPath().empty()) - if (resolveGameName() != inputRecordingData.GetHeader().gameName) - inputRec::consoleLog("Input recording was possibly constructed for a different game."); - - incrementUndo = true; - inputRec::log("Replaying input recording"); - inputRec::consoleMultiLog({fmt::format("File: {}", inputRecordingData.GetFilename()), - fmt::format("PCSX2 Version Used: {}", std::string(inputRecordingData.GetHeader().emu)), - fmt::format("Recording File Version: {}", inputRecordingData.GetHeader().version), - fmt::format("Associated Game Name or ISO Filename: {}", std::string(inputRecordingData.GetHeader().gameName)), - fmt::format("Author: {}", inputRecordingData.GetHeader().author), - fmt::format("Total Frames: {}", inputRecordingData.GetTotalFrames()), - fmt::format("Undo Count: {}", inputRecordingData.GetUndoCount())}); - SetToReplayMode(); - } - - if (inputRecordingData.FromSaveState()) - inputRec::consoleLog(fmt::format("Internal Starting Frame: {}", startingFrame)); - frameCounter = 0; - initialLoad = false; - g_InputRecordingControls.Lock(startingFrame); -} - -void InputRecording::FailedSavestate() -{ - inputRec::consoleLog(fmt::format("{} is not compatible with this version of PCSX2", savestate)); - inputRec::consoleLog(fmt::format("Original PCSX2 version used: {}", inputRecordingData.GetHeader().emu)); - inputRecordingData.Close(); - initialLoad = false; - state = InputRecordingMode::NotActive; - g_InputRecordingControls.Resume(); -} - -void InputRecording::Stop() -{ - state = InputRecordingMode::NotActive; - incrementUndo = false; - if (inputRecordingData.Close()) - inputRec::log("Input recording stopped"); -} - -bool InputRecording::Create(const std::string_view& fileName, const bool fromSaveState, const std::string_view& authorName) -{ - if (!inputRecordingData.OpenNew(fileName, fromSaveState)) - return false; - - initialLoad = true; - state = InputRecordingMode::Recording; - if (fromSaveState) - { - savestate = fmt::format("{}_SaveState.p2s", fileName); - if (FileSystem::FileExists(savestate.c_str())) - { - FileSystem::CopyFilePath(savestate.c_str(), fmt::format("{}.bak", savestate).c_str(), true); - } - VMManager::SaveState(savestate.c_str()); - } - else - { - // Vaser - CHECK - don't need to specify a source anymore? (cdvd/etc?) - VMManager::Execute(); - } - - // Set emulator version - inputRecordingData.GetHeader().SetEmulatorVersion(); - - // Set author name - if (!authorName.empty()) - inputRecordingData.GetHeader().SetAuthor(authorName); - - // Set Game Name - inputRecordingData.GetHeader().SetGameName(resolveGameName()); - // Write header contents - inputRecordingData.WriteHeader(); - return true; -} - -bool InputRecording::Play(const std::string_view& filename) -{ - if (!inputRecordingData.OpenExisting(filename)) - return false; - - // Either load the savestate, or restart the game - if (inputRecordingData.FromSaveState()) - { - // TODO - Vaser - VM State is atomic, be careful. - if (VMManager::GetState() != VMState::Running && VMManager::GetState() != VMState::Paused) - { - inputRec::consoleLog("Game is not open, aborting playing input recording which starts on a save-state."); - inputRecordingData.Close(); - return false; - } - - savestate = fmt::format("{}_SaveState.p2s", inputRecordingData.GetFilename()); - if (!FileSystem::FileExists(savestate.c_str())) - { - inputRec::consoleLog(fmt::format("Could not locate savestate file at location - {}", savestate)); - inputRec::log("Savestate load failed"); - inputRecordingData.Close(); - return false; - } - state = InputRecordingMode::Replaying; - initialLoad = true; - VMManager::LoadState(savestate.c_str()); - } - else - { - state = InputRecordingMode::Replaying; - initialLoad = true; - // Vaser - CHECK - don't need to specify a source anymore? (cdvd/etc?) - VMManager::Execute(); - } - return true; -} - std::string InputRecording::resolveGameName() { std::string gameName; @@ -819,11 +657,160 @@ std::string InputRecording::resolveGameName() auto game = GameDatabase::findGame(gameKey); if (game) { - gameName = game->name; - gameName += " (" + game->region + ")"; + gameName = fmt::format("{} ({})", game->name, game->region); } } return !gameName.empty() ? gameName : VMManager::GetGameName(); } +void InputRecording::incFrameCounter() +{ + if (m_frameCounter >= std::numeric_limits::max()) + { + // TODO - log the incredible achievment of playing for longer than 4 billion years, and end the recording + stop(); + return; + } + m_frameCounter++; + + if (m_controls.isReplaying()) + { + // If we've reached the end of the recording while replaying, pause + if (m_frameCounter == m_file.getTotalFrames() - 1) + { + VMManager::SetPaused(true); + // Can also stop watching for re-records, they've watched to the end of the recording + m_watchingForRerecords = false; + } + } + if (m_controls.isRecording()) + { + m_file.SetTotalFrames(m_frameCounter); + // If we've been in record mode and moved to the next frame, we've overrote something + // if this was following a save-state loading, this is considered a re-record, a.k.a an undo + if (m_watchingForRerecords) + { + m_file.IncrementUndoCount(); + m_watchingForRerecords = false; + } + } +} + +u64 InputRecording::getFrameCounter() const +{ + return m_frameCounter; +} + +bool InputRecording::isInitialSavestateLoadComplete() const +{ + return m_initialSavestateLoadComplete; +} + +bool InputRecording::isActive() const +{ + return m_isActive; +} + +void InputRecording::handleExceededFrameCounter() +{ + // if we go past the end, switch to recording mode so nothing is lost + if (m_frameCounter >= m_file.getTotalFrames() && m_controls.isReplaying()) + { + m_controls.setRecordMode(); + } +} + +void InputRecording::handleLoadingSavestate() +{ + // We need to keep track of the starting internal frame of the recording + // - For a power-on recording this should already be done - it starts at 0 + // - For save state recordings, this is stored inside the initial save-state + // + // Why? + // - When you re-record you load another save-state which has it's own frame counter + // stored within, we use this to adjust the frame we are replaying/recording to + if (isTypeSavestate() && !isInitialSavestateLoadComplete()) + { + setStartingFrame(g_FrameCount); + setInitialSavestateLoaded(); + } + else + { + adjustFrameCounterOnReRecord(g_FrameCount); + watchForRerecords(); + } +} + +bool InputRecording::isTypeSavestate() const +{ + return m_type == Type::FROM_SAVESTATE; +} + +void InputRecording::setStartingFrame(u64 startingFrame) +{ + if (m_type == Type::POWER_ON) + { + return; + } + InputRec::consoleLog(fmt::format("Internal Starting Frame: {}", startingFrame)); + m_startingFrame = startingFrame; +} + +void InputRecording::setInitialSavestateLoaded() +{ + m_initialSavestateLoadComplete = true; +} + +void InputRecording::adjustFrameCounterOnReRecord(u64 newFrameCounter) +{ + if (newFrameCounter > m_startingFrame + (u64)m_file.getTotalFrames()) + { + InputRec::consoleLog("Warning, you've loaded PCSX2 emulation to a point after the end of the original recording. This should be avoided."); + InputRec::consoleLog("Savestate's framecount has been ignored, using the max length of the recording instead."); + m_frameCounter = m_file.getTotalFrames(); + if (getControls().isReplaying()) + { + getControls().setRecordMode(); + } + return; + } + if (newFrameCounter < m_startingFrame) + { + InputRec::consoleLog("Warning, you've loaded PCSX2 emulation to a point before the start of the original recording. This should be avoided."); + InputRec::consoleLog("Savestate's framecount has been ignored, starting from the beginning in replay mode."); + m_frameCounter = 0; + if (getControls().isRecording()) + { + getControls().setReplayMode(); + } + return; + } + else if (newFrameCounter == 0 && getControls().isRecording()) + { + getControls().setReplayMode(); + } + m_frameCounter = newFrameCounter - m_startingFrame; +} + +void InputRecording::watchForRerecords() +{ + m_watchingForRerecords = true; +} + +InputRecordingControls& InputRecording::getControls() +{ + return m_controls; +} + +const InputRecordingFile& InputRecording::getData() const +{ + return m_file; +} + +void InputRecording::initializeState() +{ + m_frameCounter = 0; + m_watchingForRerecords = false; +} + #endif diff --git a/pcsx2/Recording/InputRecording.h b/pcsx2/Recording/InputRecording.h index 2fc0470088..3300c4b0df 100644 --- a/pcsx2/Recording/InputRecording.h +++ b/pcsx2/Recording/InputRecording.h @@ -151,6 +151,7 @@ extern InputRecording g_InputRecording; #else #include "Recording/InputRecordingFile.h" +#include "Recording/InputRecordingControls.h" class InputRecording { @@ -160,109 +161,58 @@ public: POWER_ON, FROM_SAVESTATE }; + + bool create(const std::string_view& filename, const bool fromSaveState, const std::string_view& authorName); + bool play(const std::string_view& path); + void stop(); - // Save or load PCSX2's global frame counter (g_FrameCount) along with each full/fast boot - // - // This is to prevent any inaccuracy issues caused by having a different - // internal emulation frame count than what it was at the beginning of the - // original recording - void RecordingReset(); + void controllerInterrupt(u8& data, u8& port, u16& BufCount, u8 buf[]); + void incFrameCounter(); + u64 getFrameCounter() const; + bool isInitialSavestateLoadComplete() const; + bool isActive() const; // Main handler for ingesting input data and either saving it to the recording file (recording) // or mutating it to the contents of the recording file (replaying) void ControllerInterrupt(u8 port, size_t fifoSize, u8 dataIn, u8 dataOut); + + void handleExceededFrameCounter(); + void handleLoadingSavestate(); - // The running frame counter for the input recording - s32 GetFrameCounter(); + bool isTypeSavestate() const; - InputRecordingFile& GetInputRecordingData(); + void setStartingFrame(u64 startingFrame); + void setInitialSavestateLoaded(); + void adjustFrameCounterOnReRecord(u64 newFrameCounter); - // The internal PCSX2 g_FrameCount value on the first frame of the recording - u32 GetStartingFrame(); + void watchForRerecords(); - void IncrementFrameCounter(); - - // DEPRECATED: Slated for removal - // If the current frame contains controller / input data - bool IsInterruptFrame(); - - // If there is currently an input recording being played back or actively being recorded - bool IsActive(); - - // Whether or not the recording's initial state has yet to be loaded or saved and - // the rest of the recording can be initialized - // This is not applicable to recordings from a "power-on" state - bool IsInitialLoad(); - - // If there is currently an input recording being played back - bool IsReplaying(); - - // If there are inputs currently being recorded to a file - bool IsRecording(); - - // Sets input recording to Record Mode - void SetToRecordMode(); - - // Sets input recording to Replay Mode - void SetToReplayMode(); - - // Set the running frame counter for the input recording to an arbitrary value - void SetFrameCounter(u32 newGFrameCount); - - // Sets up all values and prints console logs pertaining to the start of a recording - void SetupInitialState(u32 newStartingFrame); - - /// Functions called from GUI - - // Create a new input recording file - bool Create(const std::string_view& filename, const bool fromSaveState, const std::string_view& authorName); - // Play an existing input recording from a file - // TODO - Vaser - Calls a file dialog if it fails to locate the default base savestate - bool Play(const std::string_view& path); - // Stop the active input recording - void Stop(); - // Logs the padData and redraws the virtualPad windows of active pads - void LogAndRedraw(); - // Resets a recording if the base savestate could not be loaded at the start - void FailedSavestate(); + InputRecordingControls& getControls(); + const InputRecordingFile& getData() const; private: - enum class InputRecordingMode - { - NotActive, - Recording, - Replaying, - }; + // - https://github.com/PCSX2/pcsx2/blob/7db9627ff6986c2d3faeecc58525a0e32da2f29f/pcsx2/PAD/Windows/PAD.cpp#L1141 + static const u8 READ_DATA_AND_VIBRATE_QUERY_FIRST_BYTE = 0x42; + // - https://github.com/PCSX2/pcsx2/blob/7db9627ff6986c2d3faeecc58525a0e32da2f29f/pcsx2/PAD/Windows/PAD.cpp#L1142 + static const u8 READ_DATA_AND_VIBRATE_QUERY_SECOND_BYTE = 0x5A; - static const int CONTROLLER_PORT_ONE = 0; - static const int CONTROLLER_PORT_TWO = 1; + InputRecordingControls m_controls; + InputRecordingFile m_file; - // 0x42 is the magic number to indicate the default controller read query - // See - PAD.cpp::PADpoll - https://github.com/PCSX2/pcsx2/blob/master/pcsx2/PAD/Windows/PAD.cpp#L1255 - static const u8 READ_DATA_AND_VIBRATE_FIRST_BYTE = 0x42; - // 0x5A is always the second byte in the buffer when the normal READ_DATA_AND_VIBRATE (0x42) query is executed. - // See - PAD.cpp::PADpoll - https://github.com/PCSX2/pcsx2/blob/master/pcsx2/PAD/Windows/PAD.cpp#L1256 - static const u8 READ_DATA_AND_VIBRATE_SECOND_BYTE = 0x5A; + Type m_type; - // DEPRECATED: Slated for removal - bool fInterruptFrame = false; - InputRecordingFile inputRecordingData; - bool initialLoad = false; - u32 startingFrame = 0; - s32 frameCounter = 0; - bool incrementUndo = false; - InputRecordingMode state = InputRecording::InputRecordingMode::NotActive; - std::string savestate; + bool m_initialSavestateLoadComplete = false; + bool m_isActive = false; + bool m_padDataAvailable = false; + bool m_watchingForRerecords = false; - // Array of usable pads (currently, only 2) - struct InputRecordingPad - { - // Controller Data - PadData* padData; - InputRecordingPad(); - ~InputRecordingPad(); - } pads[2]; + u64 m_frameCounter = 0; + // Either 0 for a power-on movie, or the g_FrameCount that is stored on the starting frame + u64 m_startingFrame = 0; + void initializeState(); + +private: // Resolve the name and region of the game currently loaded using the GameDB // If the game cannot be found in the DB, the fallback is the ISO filename std::string resolveGameName();