mirror of https://github.com/PCSX2/pcsx2.git
input-rec: refactor main input recording class
This commit is contained in:
parent
9e30fa81de
commit
c5298cf12d
|
@ -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 <queue>
|
||||
#include <fmt/format.h>
|
||||
|
@ -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<u64>::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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue