From 4d668187462c3bac387505015adf582c2d754ab7 Mon Sep 17 00:00:00 2001 From: sonicfind <52436993+sonicfind@users.noreply.github.com> Date: Fri, 11 Sep 2020 15:30:56 -0500 Subject: [PATCH] GUI/Recording: Resolve issues around playing and creating input recordings under certain scenarios (#3669) * recording: Only stop the current recording once the menuing hasnt been cancelled * recording: Avoid changing menu option's state if the recording couldn't be played * recording: Don't play a recording that uses a savestate, if no game is running * recording: Don't modify `frameAdvance` flag when explicitly [un]pausing These functions are only called when creating/playing a recording, and modifying the frameAdvance flag leads to unexpected behaviour (such as the game starting paused if the recording was made from "power-on") * recording: Refactor and simplify `InputRecordingFile` InputRecordingFile will no longer be concerned with loading the save-state when playing back an existing recording. This makes it much easier to only load the save-state if the file is valid and manipulate the emulation state correctly. * recording: Update play logic with new refactor, resume emulation in the event of a failure * recording/lint: spaces to tabs * recording: Properly only examine controller 1A & 2A The previous controller port checks in place never actually succeeded in their designated task. A new slot check in sio.cpp will perform this task instead. * recording: Save the savestate in OpenNew() instead of open() Ensures that the savesate could be saved before trying to create the actual input recording file. It will overwrite any previous backup savestate. Also, allows for a simplified & easier to read code struture of open(). * Refactor and simplify `InputRecording` Changes the return type of Play/Create from void to bool. Optimizes Stop(), Pause(), and Unpause() call placements Improved handling of emulation pause state, the recording menu on failures, and the conditioning of when a recording file should actually be unloaded. For example, a currently loaded recording should not get unloaded if a user presses Play *but* chooses cancel in the file browser. However, the emulation should be paused during the duration of this action. On the flipside, a loaded recording *should* get unloaded if the tools get disabled in settings AND emulation should resume if not already playing. * recording: Simplify VirtualPad_Open_Click Co-authored-by: Tyler Wilding --- pcsx2/Recording/InputRecording.cpp | 75 ++-- pcsx2/Recording/InputRecording.h | 6 +- pcsx2/Recording/InputRecordingFile.cpp | 473 ++++++++-------------- pcsx2/Recording/InputRecordingFile.h | 101 +++-- pcsx2/Recording/PadData.cpp | 16 - pcsx2/Recording/RecordingControls.cpp | 2 - pcsx2/Recording/RecordingInputManager.cpp | 4 - pcsx2/Sio.cpp | 14 +- pcsx2/gui/AppInit.cpp | 4 +- pcsx2/gui/AppMain.cpp | 2 +- pcsx2/gui/FrameForGS.cpp | 2 +- pcsx2/gui/MainMenuClicks.cpp | 72 ++-- 12 files changed, 330 insertions(+), 441 deletions(-) diff --git a/pcsx2/Recording/InputRecording.cpp b/pcsx2/Recording/InputRecording.cpp index c6ee3bb57b..7221633eae 100644 --- a/pcsx2/Recording/InputRecording.cpp +++ b/pcsx2/Recording/InputRecording.cpp @@ -37,7 +37,7 @@ void SaveStateBase::InputRecordingFreeze() #ifndef DISABLE_RECORDING if (g_FrameCount > 0 && IsLoading()) { - g_InputRecordingData.AddUndoCount(); + g_InputRecordingData.IncrementUndoCount(); } #endif } @@ -50,11 +50,6 @@ InputRecording g_InputRecording; void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 buf[]) { // TODO - Multi-Tap Support - // Only examine controllers 1 / 2 - if (port != 0 && port != 1) - { - return; - } /* This appears to try to ensure that we are only paying attention @@ -99,19 +94,19 @@ void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 b const u8& nowBuf = buf[bufCount]; if (state == INPUT_RECORDING_MODE_RECORD) { - InputRecordingData.UpdateFrameMax(g_FrameCount); - InputRecordingData.WriteKeyBuf(g_FrameCount, port, bufCount - 3, nowBuf); + InputRecordingData.SetTotalFrames(g_FrameCount); + InputRecordingData.WriteKeyBuffer(g_FrameCount, port, bufCount - 3, nowBuf); } else if (state == INPUT_RECORDING_MODE_REPLAY) { - if (InputRecordingData.GetMaxFrame() <= g_FrameCount) + if (InputRecordingData.GetTotalFrames() <= g_FrameCount) { // Pause the emulation but the movie is not closed g_RecordingControls.Pause(); return; } u8 tmp = 0; - if (InputRecordingData.ReadKeyBuf(tmp, g_FrameCount, port, bufCount - 3)) + if (InputRecordingData.ReadKeyBuffer(tmp, g_FrameCount, port, bufCount - 3)) { buf[bufCount] = tmp; } @@ -130,15 +125,11 @@ void InputRecording::Stop() } // GUI Handler - Start recording -void InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName) +bool InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName) { - g_RecordingControls.Pause(); - Stop(); - - // create - if (!InputRecordingData.Open(FileName, true, fromSaveState)) + if (!InputRecordingData.OpenNew(FileName, fromSaveState)) { - return; + return false; } // Set emulator version InputRecordingData.GetHeader().SetEmulatorVersion(); @@ -157,40 +148,62 @@ void InputRecording::Create(wxString FileName, bool fromSaveState, wxString auth // In every case, we reset the g_FrameCount g_FrameCount = 0; + return true; } // GUI Handler - Play a recording -void InputRecording::Play(wxString FileName, bool fromSaveState) +bool InputRecording::Play(wxString fileName) { - g_RecordingControls.Pause(); - Stop(); + if (state != INPUT_RECORDING_MODE_NONE) + Stop(); - if (!InputRecordingData.Open(FileName, false, false)) + // Open the file and verify if it can be played + if (!InputRecordingData.OpenExisting(fileName)) { - return; + return false; } - if (!InputRecordingData.ReadHeaderAndCheck()) + // Either load the savestate, or restart the game + if (InputRecordingData.FromSaveState()) { - recordingConLog(L"[REC]: This file is not a correct InputRecording file.\n"); - InputRecordingData.Close(); - return; + if (!CoreThread.IsOpen()) + { + recordingConLog(L"[REC]: Game is not open, aborting playing input recording which starts on a save-state.\n"); + InputRecordingData.Close(); + return false; + } + FILE* ssFileCheck = wxFopen(InputRecordingData.GetFilename() + "_SaveState.p2s", "r"); + if (ssFileCheck == NULL) + { + recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s_SaveState.p2s\n", InputRecordingData.GetFilename())); + InputRecordingData.Close(); + return false; + } + fclose(ssFileCheck); + StateCopy_LoadFromFile(InputRecordingData.GetFilename() + "_SaveState.p2s"); } - // Check author name + else + { + g_RecordingControls.Unpause(); + sApp.SysExecute(); + } + + // Check if the current game matches with the one used to make the original recording if (!g_Conf->CurrentIso.IsEmpty()) { if (resolveGameName() != InputRecordingData.GetHeader().gameName) { - recordingConLog(L"[REC]: Recording was possibly recorded on a different game.\n"); + recordingConLog(L"[REC]: Recording was possibly constructed for a different game.\n"); } } state = INPUT_RECORDING_MODE_REPLAY; - recordingConLog(wxString::Format(L"[REC]: Replaying movie - [%s]\n", FileName)); + recordingConLog(wxString::Format(L"[REC]: Replaying input recording - [%s]\n", InputRecordingData.GetFilename())); recordingConLog(wxString::Format(L"[REC]: PCSX2 Version Used: %s\n", InputRecordingData.GetHeader().emu)); recordingConLog(wxString::Format(L"[REC]: Recording File Version: %d\n", InputRecordingData.GetHeader().version)); recordingConLog(wxString::Format(L"[REC]: Associated Game Name or ISO Filename: %s\n", InputRecordingData.GetHeader().gameName)); recordingConLog(wxString::Format(L"[REC]: Author: %s\n", InputRecordingData.GetHeader().author)); - recordingConLog(wxString::Format(L"[REC]: MaxFrame: %d\n", InputRecordingData.GetMaxFrame())); - recordingConLog(wxString::Format(L"[REC]: UndoCount: %d\n", InputRecordingData.GetUndoCount())); + recordingConLog(wxString::Format(L"[REC]: Total Frames: %d\n", InputRecordingData.GetTotalFrames())); + recordingConLog(wxString::Format(L"[REC]: Undo Count: %d\n", InputRecordingData.GetUndoCount())); + return true; } wxString InputRecording::resolveGameName() diff --git a/pcsx2/Recording/InputRecording.h b/pcsx2/Recording/InputRecording.h index 06a9e9ce06..d3feb4000d 100644 --- a/pcsx2/Recording/InputRecording.h +++ b/pcsx2/Recording/InputRecording.h @@ -41,8 +41,8 @@ public: bool IsInterruptFrame(); void Stop(); - void Create(wxString filename, bool fromSaveState, wxString authorName); - void Play(wxString filename, bool fromSaveState); + bool Create(wxString filename, bool fromSaveState, wxString authorName); + bool Play(wxString filename); private: InputRecordingFile InputRecordingData; @@ -55,5 +55,5 @@ private: extern InputRecording g_InputRecording; static InputRecordingFile& g_InputRecordingData = g_InputRecording.GetInputRecordingData(); -static InputRecordingHeader& g_InputRecordingHeader = g_InputRecording.GetInputRecordingData().GetHeader(); +static InputRecordingFileHeader& g_InputRecordingHeader = g_InputRecording.GetInputRecordingData().GetHeader(); #endif diff --git a/pcsx2/Recording/InputRecordingFile.cpp b/pcsx2/Recording/InputRecordingFile.cpp index 464356f601..3f03f521d1 100644 --- a/pcsx2/Recording/InputRecordingFile.cpp +++ b/pcsx2/Recording/InputRecordingFile.cpp @@ -24,119 +24,154 @@ #include "InputRecordingFile.h" #ifndef DISABLE_RECORDING -long InputRecordingFile::GetBlockSeekPoint(const long& frame) + +void InputRecordingFileHeader::Init() { - if (savestate.fromSavestate) - { - return RecordingHeaderSize + RecordingSavestateHeaderSize + frame * RecordingBlockSize; - } - else - { - return RecordingHeaderSize + sizeof(bool) + (frame)*RecordingBlockSize; - } + memset(author, 0, ArraySize(author)); + memset(gameName, 0, ArraySize(gameName)); } -// Inits the new (or existing) input recording file -bool InputRecordingFile::Open(const wxString path, bool fNewOpen, bool fromSaveState) +void InputRecordingFileHeader::SetEmulatorVersion() { - Close(); - wxString mode = L"rb+"; - if (fNewOpen) - { - mode = L"wb+"; - MaxFrame = 0; - UndoCount = 0; - header.Init(); - } - recordingFile = wxFopen(path, mode); - if (recordingFile == NULL) - { - recordingConLog(wxString::Format("[REC]: Movie file opening failed. Error - %s\n", strerror(errno))); - return false; - } - filename = path; - - if (fNewOpen) - { - if (fromSaveState) - { - savestate.fromSavestate = true; - FILE* ssFileCheck = wxFopen(path + "_SaveState.p2s", "r"); - if (ssFileCheck != NULL) - { - wxCopyFile(path + "_SaveState.p2s", path + "_SaveState.p2s.bak", false); - fclose(ssFileCheck); - } - StateCopy_SaveToFile(path + "_SaveState.p2s"); - } - else - { - sApp.SysExecute(); - } - } - return true; + wxString emuVersion = wxString::Format("%s-%d.%d.%d", pxGetAppName().c_str(), PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo); + int max = ArraySize(emu) - 1; + strncpy(emu, emuVersion.c_str(), max); + emu[max] = 0; +} + +void InputRecordingFileHeader::SetAuthor(wxString _author) +{ + int max = ArraySize(author) - 1; + strncpy(author, _author.c_str(), max); + author[max] = 0; +} + +void InputRecordingFileHeader::SetGameName(wxString _gameName) +{ + int max = ArraySize(gameName) - 1; + strncpy(gameName, _gameName.c_str(), max); + gameName[max] = 0; } -// Gracefully close the current recording file bool InputRecordingFile::Close() { if (recordingFile == NULL) { return false; } - WriteHeader(); - WriteSaveState(); fclose(recordingFile); recordingFile = NULL; filename = ""; return true; } -// Write savestate flag to file -bool InputRecordingFile::WriteSaveState() +const wxString &InputRecordingFile::GetFilename() { - if (recordingFile == NULL) - { - return false; - } - - fseek(recordingFile, RecordingSeekpointSaveState, SEEK_SET); - if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) - { - return false; - } - - return true; + return filename; } -// Write controller input buffer to file (per frame) -bool InputRecordingFile::WriteKeyBuf(const uint& frame, const uint port, const uint bufIndex, const u8& buf) +InputRecordingFileHeader &InputRecordingFile::GetHeader() { - if (recordingFile == NULL) - { - return false; - } - - long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize + 18 * port + bufIndex; - - if (fseek(recordingFile, seek, SEEK_SET) != 0 || fwrite(&buf, 1, 1, recordingFile) != 1) - { - return false; - } - - fflush(recordingFile); - return true; + return header; } -// Read controller input buffer from file (per frame) -bool InputRecordingFile::ReadKeyBuf(u8& result, const uint& frame, const uint port, const uint bufIndex) +unsigned long &InputRecordingFile::GetTotalFrames() +{ + return totalFrames; +} + +unsigned long &InputRecordingFile::GetUndoCount() +{ + return undoCount; +} + +bool InputRecordingFile::FromSaveState() +{ + return savestate.fromSavestate; +} + +void InputRecordingFile::IncrementUndoCount() +{ + undoCount++; + if (recordingFile == NULL) + { + return; + } + fseek(recordingFile, seekpointUndoCount, SEEK_SET); + fwrite(&undoCount, 4, 1, recordingFile); +} + +bool InputRecordingFile::open(const wxString path, bool newRecording) +{ + if (newRecording) + { + if ((recordingFile = wxFopen(path, L"wb+")) != nullptr) + { + filename = path; + totalFrames = 0; + undoCount = 0; + header.Init(); + return true; + } + } + else if ((recordingFile = wxFopen(path, L"rb+")) != nullptr) + { + if (verifyRecordingFileHeader()) + { + filename = path; + return true; + } + Close(); + recordingConLog(wxString::Format("[REC]: Input recording file header is invalid\n")); + return false; + } + recordingConLog(wxString::Format("[REC]: Input recording file opening failed. Error - %s\n", strerror(errno))); + return false; +} + +bool InputRecordingFile::OpenNew(const wxString path, bool fromSavestate) +{ + if (fromSavestate) + { + if (CoreThread.IsOpen()) + { + if (open(path, true)) + { + savestate.fromSavestate = true; + if (wxFileExists(path + "_SaveState.p2s")) + { + wxCopyFile(path + "_SaveState.p2s", path + "_SaveState.p2s.bak", true); + } + StateCopy_SaveToFile(path + "_SaveState.p2s"); + return true; + } + } + else + recordingConLog(L"[REC]: Game is not open, aborting playing input recording which starts on a save-state.\n"); + return false; + } + else if (open(path, true)) + { + savestate.fromSavestate = false; + sApp.SysExecute(); + return true; + } + return open(path, true); +} + +bool InputRecordingFile::OpenExisting(const wxString path) +{ + return open(path, false); +} + +bool InputRecordingFile::ReadKeyBuffer(u8 &result, const uint &frame, const uint port, const uint bufIndex) { if (recordingFile == NULL) { return false; } - long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize + 18 * port + bufIndex; + long seek = getRecordingBlockSeekPoint(frame) + controllerInputBytes * port + bufIndex; if (fseek(recordingFile, seek, SEEK_SET) != 0) { return false; @@ -149,152 +184,74 @@ bool InputRecordingFile::ReadKeyBuf(u8& result, const uint& frame, const uint po return true; } - -void InputRecordingFile::GetPadData(PadData& result, unsigned long frame) +void InputRecordingFile::SetTotalFrames(unsigned long frame) { - result.fExistKey = false; - if (recordingFile == NULL) + if (recordingFile == NULL || totalFrames >= frame) { return; } - - long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize; - if (fseek(recordingFile, seek, SEEK_SET) != 0 || fread(result.buf, 1, RecordingBlockDataSize, recordingFile) == 0) - { - return; - } - - result.fExistKey = true; + totalFrames = frame; + fseek(recordingFile, seekpointTotalFrames, SEEK_SET); + fwrite(&totalFrames, 4, 1, recordingFile); } -bool InputRecordingFile::DeletePadData(unsigned long frame) -{ - if (recordingFile == NULL) - { - return false; - } - - for (unsigned long i = frame; i < MaxFrame - 1; i++) - { - long seek1 = GetBlockSeekPoint(i + 1) + RecordingBlockHeaderSize; - long seek2 = GetBlockSeekPoint(i) + RecordingBlockHeaderSize; - - u8 buf[2][18]; - fseek(recordingFile, seek1, SEEK_SET); - int rSize = fread(buf, 1, RecordingBlockDataSize, recordingFile); - if (rSize != RecordingBlockDataSize) - { - recordingConLog(wxString::Format("[REC]: Error encountered when reading from file: Expected %d bytes, read %d instead.\n", RecordingBlockDataSize, rSize)); - return false; - } - fseek(recordingFile, seek2, SEEK_SET); - rSize = fwrite(buf, 1, RecordingBlockDataSize, recordingFile); - if (rSize != RecordingBlockDataSize) - { - recordingConLog(wxString::Format("[REC]: Error encountered when writing to file: Expected %d bytes, read %d instead.\n", RecordingBlockDataSize, rSize)); - return false; - } - } - MaxFrame--; - WriteMaxFrame(); - fflush(recordingFile); - - return true; -} - -bool InputRecordingFile::InsertPadData(unsigned long frame, const PadData& key) -{ - if (recordingFile == NULL || !key.fExistKey) - { - return false; - } - - for (unsigned long i = MaxFrame - 1; i >= frame; i--) - { - long seek1 = GetBlockSeekPoint(i) + RecordingBlockHeaderSize; - long seek2 = GetBlockSeekPoint(i + 1) + RecordingBlockHeaderSize; - - u8 buf[2][18]; - fseek(recordingFile, seek1, SEEK_SET); - int rSize = fread(buf, 1, RecordingBlockDataSize, recordingFile); - if (rSize != RecordingBlockDataSize) - { - recordingConLog(wxString::Format("[REC]: Error encountered when reading from file: Expected %d bytes, read %d instead.\n", RecordingBlockDataSize, rSize)); - return false; - } - fseek(recordingFile, seek2, SEEK_SET); - rSize = fwrite(buf, 1, RecordingBlockDataSize, recordingFile); - if (rSize != RecordingBlockDataSize) - { - recordingConLog(wxString::Format("[REC]: Error encountered when writing to file: Expected %d bytes, wrote %d instead.\n", RecordingBlockDataSize, rSize)); - return false; - } - } - long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize; - fseek(recordingFile, seek, SEEK_SET); - int rSize = fwrite(key.buf, 1, RecordingBlockDataSize, recordingFile); - if (rSize != RecordingBlockDataSize) - { - recordingConLog(wxString::Format("[REC]: Error encountered when writing to file: Expected %d bytes, wrote %d instead.\n", RecordingBlockDataSize, rSize)); - return false; - } - MaxFrame++; - WriteMaxFrame(); - fflush(recordingFile); - - return true; -} - -bool InputRecordingFile::UpdatePadData(unsigned long frame, const PadData& key) -{ - if (recordingFile == NULL) - { - return false; - } - if (!key.fExistKey) - { - return false; - } - - long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize; - fseek(recordingFile, seek, SEEK_SET); - if (fwrite(key.buf, 1, RecordingBlockDataSize, recordingFile) == 0) - { - return false; - } - - fflush(recordingFile); - return true; -} - -// Verify header of recording file -bool InputRecordingFile::ReadHeaderAndCheck() +bool InputRecordingFile::WriteHeader() { if (recordingFile == NULL) { return false; } rewind(recordingFile); - if (fread(&header, sizeof(InputRecordingHeader), 1, recordingFile) != 1 || fread(&MaxFrame, 4, 1, recordingFile) != 1 || fread(&UndoCount, 4, 1, recordingFile) != 1 || fread(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) + if (fwrite(&header, sizeof(InputRecordingFileHeader), 1, recordingFile) != 1 + || fwrite(&totalFrames, 4, 1, recordingFile) != 1 + || fwrite(&undoCount, 4, 1, recordingFile) != 1 + || fwrite(&savestate, 1, 1, recordingFile) != 1) { return false; } - if (savestate.fromSavestate) + return true; +} + +bool InputRecordingFile::WriteKeyBuffer(const uint &frame, const uint port, const uint bufIndex, const u8 &buf) +{ + if (recordingFile == NULL) { - FILE* ssFileCheck = wxFopen(filename + "_SaveState.p2s", "r"); - if (ssFileCheck == NULL) - { - recordingConLog(wxString::Format("[REC]: Could not locate savestate file at location - %s\n", filename + "_SaveState.p2s")); - return false; - } - fclose(ssFileCheck); - StateCopy_LoadFromFile(filename + "_SaveState.p2s"); - } - else - { - sApp.SysExecute(); + return false; } + long seek = getRecordingBlockSeekPoint(frame) + 18 * port + bufIndex; + + if (fseek(recordingFile, seek, SEEK_SET) != 0 + || fwrite(&buf, 1, 1, recordingFile) != 1) + { + return false; + } + + fflush(recordingFile); + return true; +} + +long InputRecordingFile::getRecordingBlockSeekPoint(const long &frame) +{ + return headerSize + sizeof(bool) + frame * inputBytesPerFrame; +} + +bool InputRecordingFile::verifyRecordingFileHeader() +{ + if (recordingFile == NULL) + { + return false; + } + // Verify header contents + rewind(recordingFile); + if (fread(&header, sizeof(InputRecordingFileHeader), 1, recordingFile) != 1 + || fread(&totalFrames, 4, 1, recordingFile) != 1 + || fread(&undoCount, 4, 1, recordingFile) != 1 + || fread(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) + { + return false; + } + // Check for current verison if (header.version != 1) { @@ -303,101 +260,19 @@ bool InputRecordingFile::ReadHeaderAndCheck() } return true; } -bool InputRecordingFile::WriteHeader() -{ + +bool InputRecordingFile::writeSaveState() { if (recordingFile == NULL) { return false; } - rewind(recordingFile); - if (fwrite(&header, sizeof(InputRecordingHeader), 1, recordingFile) != 1) + + fseek(recordingFile, seekpointSaveStateHeader, SEEK_SET); + if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1) { return false; } + return true; } - -bool InputRecordingFile::WriteMaxFrame() -{ - if (recordingFile == NULL) - { - return false; - } - fseek(recordingFile, RecordingSeekpointFrameMax, SEEK_SET); - if (fwrite(&MaxFrame, 4, 1, recordingFile) != 1) - { - return false; - } - return true; -} - -void InputRecordingFile::UpdateFrameMax(unsigned long frame) -{ - if (recordingFile == NULL || MaxFrame >= frame) - { - return; - } - MaxFrame = frame; - fseek(recordingFile, RecordingSeekpointFrameMax, SEEK_SET); - fwrite(&MaxFrame, 4, 1, recordingFile); -} - -void InputRecordingFile::AddUndoCount() -{ - UndoCount++; - if (recordingFile == NULL) - { - return; - } - fseek(recordingFile, RecordingSeekpointUndoCount, SEEK_SET); - fwrite(&UndoCount, 4, 1, recordingFile); -} - -void InputRecordingHeader::SetEmulatorVersion() -{ - wxString emuVersion = wxString::Format("%s-%d.%d.%d", pxGetAppName().c_str(), PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo); - int max = ArraySize(emu) - 1; - strncpy(emu, emuVersion.c_str(), max); - emu[max] = 0; -} - -void InputRecordingHeader::SetAuthor(wxString _author) -{ - int max = ArraySize(author) - 1; - strncpy(author, _author.c_str(), max); - author[max] = 0; -} - -void InputRecordingHeader::SetGameName(wxString _gameName) -{ - int max = ArraySize(gameName) - 1; - strncpy(gameName, _gameName.c_str(), max); - gameName[max] = 0; -} - -void InputRecordingHeader::Init() -{ - memset(author, 0, ArraySize(author)); - memset(gameName, 0, ArraySize(gameName)); -} - -InputRecordingHeader& InputRecordingFile::GetHeader() -{ - return header; -} - -unsigned long& InputRecordingFile::GetMaxFrame() -{ - return MaxFrame; -} - -unsigned long& InputRecordingFile::GetUndoCount() -{ - return UndoCount; -} - -const wxString& InputRecordingFile::GetFilename() -{ - return filename; -} #endif diff --git a/pcsx2/Recording/InputRecordingFile.h b/pcsx2/Recording/InputRecordingFile.h index 22d0e14f2d..4c5bcd8006 100644 --- a/pcsx2/Recording/InputRecordingFile.h +++ b/pcsx2/Recording/InputRecordingFile.h @@ -15,12 +15,15 @@ #pragma once -#include "PadData.h" #include "System.h" +#include "PadData.h" + +// NOTE / TODOs for Version 2 +// - Move fromSavestate, undoCount, and total frames into the header #ifndef DISABLE_RECORDING -struct InputRecordingHeader +struct InputRecordingFileHeader { u8 version = 1; char emu[50] = ""; @@ -29,70 +32,80 @@ struct InputRecordingHeader public: void SetEmulatorVersion(); + void Init(); void SetAuthor(wxString author); void SetGameName(wxString cdrom); - void Init(); }; -static const int RecordingHeaderSize = sizeof(InputRecordingHeader) + 4 + 4; -// Contains info about the starting point of the movie +// DEPRECATED / Slated for Removal struct InputRecordingSavestate { // Whether we start from the savestate or from power-on bool fromSavestate = false; }; +// Handles all operations on the input recording file class InputRecordingFile { public: - InputRecordingFile() {} ~InputRecordingFile() { Close(); } - // Movie File Manipulation - bool Open(const wxString fn, bool fNewOpen, bool fromSaveState); + // Closes the underlying input recording file, writing the header and + // prepares for a possible new recording to be started bool Close(); - bool WriteKeyBuf(const uint& frame, const uint port, const uint bufIndex, const u8& buf); - bool ReadKeyBuf(u8& result, const uint& frame, const uint port, const uint bufIndex); - - // Controller Data - void GetPadData(PadData& result_pad, unsigned long frame); - bool DeletePadData(unsigned long frame); - bool InsertPadData(unsigned long frame, const PadData& key); - bool UpdatePadData(unsigned long frame, const PadData& key); - - // Header - InputRecordingHeader& GetHeader(); - unsigned long& GetMaxFrame(); - unsigned long& GetUndoCount(); - const wxString& GetFilename(); - + // Retrieve the input recording's filename (not the path) + const wxString &GetFilename(); + // Retrieve the input recording's header which contains high-level metadata on the recording + InputRecordingFileHeader &GetHeader(); + // The maximum number of frames, or in other words, the length of the recording + unsigned long &GetTotalFrames(); + // The number of times a save-state has been loaded while recording this movie + // this is also often referred to as a "re-record" + unsigned long &GetUndoCount(); + // Whether or not this input recording starts by loading a save-state or by booting the game fresh + bool FromSaveState(); + // Increment the number of undo actions and commit it to the recording file + void IncrementUndoCount(); + // Open an existing recording file + bool OpenExisting(const wxString path); + // Create and open a brand new input recording, either starting from a save-state or from + // booting the game + bool OpenNew(const wxString path, bool fromSaveState); + // Reads the current frame's input data from the file in order to intercept and overwrite + // the current frame's value from the emulator + bool ReadKeyBuffer(u8 &result, const uint &frame, const uint port, const uint bufIndex); + // Updates the total frame counter and commit it to the recording file + void SetTotalFrames(unsigned long frames); + // Persist the input recording file header's current state to the file bool WriteHeader(); - bool WriteMaxFrame(); - bool WriteSaveState(); - - bool ReadHeaderAndCheck(); - void UpdateFrameMax(unsigned long frame); - void AddUndoCount(); + // Writes the current frame's input data to the file so it can be replayed + bool WriteKeyBuffer(const uint &frame, const uint port, const uint bufIndex, const u8 &buf); private: - static const int RecordingSavestateHeaderSize = sizeof(bool); - static const int RecordingBlockHeaderSize = 0; - static const int RecordingBlockDataSize = 18 * 2; - static const int RecordingBlockSize = RecordingBlockHeaderSize + RecordingBlockDataSize; - static const int RecordingSeekpointFrameMax = sizeof(InputRecordingHeader); - static const int RecordingSeekpointUndoCount = sizeof(InputRecordingHeader) + 4; - static const int RecordingSeekpointSaveState = RecordingSeekpointUndoCount + 4; + static const int controllerPortsSupported = 2; + static const int controllerInputBytes = 18; + static const int inputBytesPerFrame = controllerInputBytes * controllerPortsSupported; + // TODO - version 2, this could be greatly simplified if everything was in the header + // + 4 + 4 is the totalFrame and undoCount values + static const int headerSize = sizeof(InputRecordingFileHeader) + 4 + 4; + // DEPRECATED / Slated for Removal + static const int recordingSavestateHeaderSize = sizeof(bool); + static const int seekpointTotalFrames = sizeof(InputRecordingFileHeader); + static const int seekpointUndoCount = sizeof(InputRecordingFileHeader) + 4; + static const int seekpointSaveStateHeader = seekpointUndoCount + 4; - // Movie File - FILE* recordingFile = NULL; + InputRecordingFileHeader header; wxString filename = ""; - long GetBlockSeekPoint(const long& frame); - - // Header - InputRecordingHeader header; + FILE * recordingFile = NULL; InputRecordingSavestate savestate; - unsigned long MaxFrame = 0; - unsigned long UndoCount = 0; + unsigned long totalFrames = 0; + unsigned long undoCount = 0; + + // Calculates the position of the current frame in the input recording + long getRecordingBlockSeekPoint(const long& frame); + bool open(const wxString path, bool newRecording); + bool verifyRecordingFileHeader(); + bool writeSaveState(); }; #endif diff --git a/pcsx2/Recording/PadData.cpp b/pcsx2/Recording/PadData.cpp index 165487596e..9820dfb91c 100644 --- a/pcsx2/Recording/PadData.cpp +++ b/pcsx2/Recording/PadData.cpp @@ -76,10 +76,6 @@ void PadData::SetNormalButtons(int port, std::vector buttons) void PadData::SetNormalButton(int port, PadData_NormalButton button, int fpushed) { - if (port < 0 || 1 < port) - { - return; - } wxByte keybit[2]; GetKeyBit(keybit, button); int pressureByteIndex = GetPressureByte(button); @@ -111,10 +107,6 @@ void PadData::SetNormalButton(int port, PadData_NormalButton button, int fpushed int PadData::GetNormalButton(int port, PadData_NormalButton button) const { - if (port < 0 || 1 < port) - { - return false; - } wxByte keybit[2]; GetKeyBit(keybit, button); int pressureByteIndex = GetPressureByte(button); @@ -282,10 +274,6 @@ void PadData::SetAnalogVectors(int port, std::vector vectors) void PadData::SetAnalogVector(int port, PadData_AnalogVector vector, int val) { - if (port < 0 || 1 < port) - { - return; - } if (val < 0) { val = 0; @@ -300,10 +288,6 @@ void PadData::SetAnalogVector(int port, PadData_AnalogVector vector, int val) int PadData::GetAnalogVector(int port, PadData_AnalogVector vector) const { - if (port < 0 || 1 < port) - { - return 0; - } return buf[port][GetAnalogVectorByte(vector)]; } diff --git a/pcsx2/Recording/RecordingControls.cpp b/pcsx2/Recording/RecordingControls.cpp index c86e346465..516bb192f8 100644 --- a/pcsx2/Recording/RecordingControls.cpp +++ b/pcsx2/Recording/RecordingControls.cpp @@ -110,13 +110,11 @@ void RecordingControls::TogglePause() void RecordingControls::Pause() { fStop = true; - fFrameAdvance = true; } void RecordingControls::Unpause() { fStop = false; fStart = true; - fFrameAdvance = true; } #endif diff --git a/pcsx2/Recording/RecordingInputManager.cpp b/pcsx2/Recording/RecordingInputManager.cpp index d5631a88d8..f896b152a7 100644 --- a/pcsx2/Recording/RecordingInputManager.cpp +++ b/pcsx2/Recording/RecordingInputManager.cpp @@ -32,10 +32,6 @@ RecordingInputManager::RecordingInputManager() void RecordingInputManager::ControllerInterrupt(u8 & data, u8 & port, u16 & BufCount, u8 buf[]) { - if (port >= 2) - { - return; - } if (virtualPad[port]) { diff --git a/pcsx2/Sio.cpp b/pcsx2/Sio.cpp index c207f79945..ce00dd2b18 100644 --- a/pcsx2/Sio.cpp +++ b/pcsx2/Sio.cpp @@ -218,13 +218,17 @@ SIO_WRITE sioWriteController(u8 data) #ifndef DISABLE_RECORDING if (g_Conf->EmuOptions.EnableRecordingTools) { - g_InputRecording.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf); - if (g_InputRecording.IsInterruptFrame()) + // Only examine controllers 1 / 2 + if (sio.slot[sio.port] == 0) { - g_RecordingInput.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf); - } + g_InputRecording.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf); + if (g_InputRecording.IsInterruptFrame()) + { + g_RecordingInput.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf); + } - PadData::LogPadData(sio.port, sio.bufCount, sio.buf); + PadData::LogPadData(sio.port, sio.bufCount, sio.buf); + } } #endif break; diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index a39c9a586d..a063bf0608 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -79,8 +79,8 @@ void Pcsx2App::OpenMainFrame() #ifndef DISABLE_RECORDING VirtualPad* virtualPad0 = new VirtualPad(mainFrame, wxID_ANY, wxEmptyString, 0); m_id_VirtualPad[0] = virtualPad0->GetId(); - - VirtualPad *virtualPad1 = new VirtualPad(mainFrame, wxID_ANY, wxEmptyString, 1); + + VirtualPad* virtualPad1 = new VirtualPad(mainFrame, wxID_ANY, wxEmptyString, 1); m_id_VirtualPad[1] = virtualPad1->GetId(); NewRecordingFrame* newRecordingFrame = new NewRecordingFrame(mainFrame); diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index e492f4ae7b..8e856d3c78 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -1059,7 +1059,7 @@ void Pcsx2App::OnProgramLogClosed( wxWindowID id ) void Pcsx2App::OnMainFrameClosed( wxWindowID id ) { #ifndef DISABLE_RECORDING - if (g_Conf->EmuOptions.EnableRecordingTools) + if (g_InputRecording.GetModeState() == INPUT_RECORDING_MODE_NONE) { g_InputRecording.Stop(); } diff --git a/pcsx2/gui/FrameForGS.cpp b/pcsx2/gui/FrameForGS.cpp index 136ea8f1f5..c2a3ad38aa 100644 --- a/pcsx2/gui/FrameForGS.cpp +++ b/pcsx2/gui/FrameForGS.cpp @@ -749,7 +749,7 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) } title.Replace(L"${frame}", pxsFmt(L"%d", g_FrameCount)); - title.Replace(L"${maxFrame}", pxsFmt(L"%d", g_InputRecording.GetInputRecordingData().GetMaxFrame())); + title.Replace(L"${maxFrame}", pxsFmt(L"%d", g_InputRecording.GetInputRecordingData().GetTotalFrames())); title.Replace(L"${mode}", movieMode); #else wxString title = templates.TitleTemplate; diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index bb6f30d8e8..adf90ed6c9 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -34,6 +34,7 @@ # include "Recording/InputRecording.h" # include "Recording/RecordingControls.h" # include "Recording/VirtualPad.h" +# include "Recording/RecordingControls.h" #endif @@ -499,7 +500,7 @@ void MainEmuFrame::Menu_EnableWideScreenPatches_Click( wxCommandEvent& ) } #ifndef DISABLE_RECORDING -void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&) +void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent& event) { bool checked = GetMenuBar()->IsChecked(MenuId_EnableInputRecording); // Confirm with User @@ -529,6 +530,9 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&) } else { + //Properly close any currently loaded recording file before disabling + if (g_InputRecording.GetModeState() != INPUT_RECORDING_MODE_NONE) + Menu_Recording_Stop_Click(event); GetMenuBar()->Remove(TopLevelMenu_InputRecording); // Always turn controller logs off, but never turn it on by default SysConsole.controlInfo.Enabled = checked; @@ -540,6 +544,8 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&) viewport->InitDefaultAccelerators(); } } + if (g_RecordingControls.IsEmulationAndRecordingPaused()) + g_RecordingControls.Unpause(); } g_Conf->EmuOptions.EnableRecordingTools = checked; @@ -868,49 +874,61 @@ void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click(wxCommandEvent & eve #ifndef DISABLE_RECORDING void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event) { - g_InputRecording.Stop(); - + const bool initiallyPaused = g_RecordingControls.IsEmulationAndRecordingPaused(); + if (!initiallyPaused) + g_RecordingControls.Pause(); NewRecordingFrame* NewRecordingFrame = wxGetApp().GetNewRecordingFramePtr(); if (NewRecordingFrame) { if (NewRecordingFrame->ShowModal() == wxID_CANCEL) { + if (!initiallyPaused) + g_RecordingControls.Unpause(); return; } - // From Current Frame - if (NewRecordingFrame->GetFrom() == 0) + if (!g_InputRecording.Create(NewRecordingFrame->GetFile(), !NewRecordingFrame->GetFrom(), NewRecordingFrame->GetAuthor())) { - if (!CoreThread.IsOpen()) - { - recordingConLog(L"[REC]: Game is not open, aborting new input recording.\n"); - return; - } - g_InputRecording.Create(NewRecordingFrame->GetFile(), true, NewRecordingFrame->GetAuthor()); - } - // From Power-On - else if (NewRecordingFrame->GetFrom() == 1) - { - g_InputRecording.Create(NewRecordingFrame->GetFile(), false, NewRecordingFrame->GetAuthor()); + if (!initiallyPaused) + g_RecordingControls.Unpause(); + return; } } m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); + if (!g_InputRecordingData.FromSaveState()) + g_RecordingControls.Unpause(); } void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event) { - g_InputRecording.Stop(); + const bool initiallyPaused = g_RecordingControls.IsEmulationAndRecordingPaused(); + if (!initiallyPaused) + g_RecordingControls.Pause(); wxFileDialog openFileDialog(this, _("Select P2M2 record file."), L"", L"", L"p2m2 file(*.p2m2)|*.p2m2", wxFD_OPEN); if (openFileDialog.ShowModal() == wxID_CANCEL) { + if (!initiallyPaused) + g_RecordingControls.Unpause(); return; } wxString path = openFileDialog.GetPath(); - g_InputRecording.Play(path, true); - m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); - m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); + const bool recordingLoaded = g_InputRecording.GetModeState() != INPUT_RECORDING_MODE_NONE; + if (!g_InputRecording.Play(path)) + { + if (recordingLoaded) + Menu_Recording_Stop_Click(event); + if (!initiallyPaused) + g_RecordingControls.Unpause(); + return; + } + if (!recordingLoaded) + { + m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); + m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); + } + g_RecordingControls.Unpause(); } void MainEmuFrame::Menu_Recording_Stop_Click(wxCommandEvent &event) @@ -922,18 +940,6 @@ void MainEmuFrame::Menu_Recording_Stop_Click(wxCommandEvent &event) void MainEmuFrame::Menu_Recording_VirtualPad_Open_Click(wxCommandEvent &event) { - VirtualPad *vp = NULL; - if (event.GetId() == MenuId_Recording_VirtualPad_Port0) - { - vp = wxGetApp().GetVirtualPadPtr(0); - } - else if (event.GetId() == MenuId_Recording_VirtualPad_Port1) - { - vp = wxGetApp().GetVirtualPadPtr(1); - } - if (vp != NULL) - { - vp->Show(); - } + wxGetApp().GetVirtualPadPtr(event.GetId() - MenuId_Recording_VirtualPad_Port0)->Show(); } #endif