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 <xtvaser@gmail.com>
This commit is contained in:
sonicfind 2020-09-11 15:30:56 -05:00 committed by GitHub
parent 4d0650baa0
commit 4d66818746
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 330 additions and 441 deletions

View File

@ -37,7 +37,7 @@ void SaveStateBase::InputRecordingFreeze()
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
if (g_FrameCount > 0 && IsLoading()) if (g_FrameCount > 0 && IsLoading())
{ {
g_InputRecordingData.AddUndoCount(); g_InputRecordingData.IncrementUndoCount();
} }
#endif #endif
} }
@ -50,11 +50,6 @@ InputRecording g_InputRecording;
void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 buf[]) void InputRecording::ControllerInterrupt(u8& data, u8& port, u16& bufCount, u8 buf[])
{ {
// TODO - Multi-Tap Support // 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 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]; const u8& nowBuf = buf[bufCount];
if (state == INPUT_RECORDING_MODE_RECORD) if (state == INPUT_RECORDING_MODE_RECORD)
{ {
InputRecordingData.UpdateFrameMax(g_FrameCount); InputRecordingData.SetTotalFrames(g_FrameCount);
InputRecordingData.WriteKeyBuf(g_FrameCount, port, bufCount - 3, nowBuf); InputRecordingData.WriteKeyBuffer(g_FrameCount, port, bufCount - 3, nowBuf);
} }
else if (state == INPUT_RECORDING_MODE_REPLAY) 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 // Pause the emulation but the movie is not closed
g_RecordingControls.Pause(); g_RecordingControls.Pause();
return; return;
} }
u8 tmp = 0; u8 tmp = 0;
if (InputRecordingData.ReadKeyBuf(tmp, g_FrameCount, port, bufCount - 3)) if (InputRecordingData.ReadKeyBuffer(tmp, g_FrameCount, port, bufCount - 3))
{ {
buf[bufCount] = tmp; buf[bufCount] = tmp;
} }
@ -130,15 +125,11 @@ void InputRecording::Stop()
} }
// GUI Handler - Start recording // 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(); if (!InputRecordingData.OpenNew(FileName, fromSaveState))
Stop();
// create
if (!InputRecordingData.Open(FileName, true, fromSaveState))
{ {
return; return false;
} }
// Set emulator version // Set emulator version
InputRecordingData.GetHeader().SetEmulatorVersion(); InputRecordingData.GetHeader().SetEmulatorVersion();
@ -157,40 +148,62 @@ void InputRecording::Create(wxString FileName, bool fromSaveState, wxString auth
// In every case, we reset the g_FrameCount // In every case, we reset the g_FrameCount
g_FrameCount = 0; g_FrameCount = 0;
return true;
} }
// GUI Handler - Play a recording // GUI Handler - Play a recording
void InputRecording::Play(wxString FileName, bool fromSaveState) bool InputRecording::Play(wxString fileName)
{ {
g_RecordingControls.Pause(); if (state != INPUT_RECORDING_MODE_NONE)
Stop(); 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"); if (!CoreThread.IsOpen())
{
recordingConLog(L"[REC]: Game is not open, aborting playing input recording which starts on a save-state.\n");
InputRecordingData.Close(); InputRecordingData.Close();
return; return false;
} }
// Check author name 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");
}
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 (!g_Conf->CurrentIso.IsEmpty())
{ {
if (resolveGameName() != InputRecordingData.GetHeader().gameName) 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; 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]: 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]: 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]: 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]: Author: %s\n", InputRecordingData.GetHeader().author));
recordingConLog(wxString::Format(L"[REC]: MaxFrame: %d\n", InputRecordingData.GetMaxFrame())); recordingConLog(wxString::Format(L"[REC]: Total Frames: %d\n", InputRecordingData.GetTotalFrames()));
recordingConLog(wxString::Format(L"[REC]: UndoCount: %d\n", InputRecordingData.GetUndoCount())); recordingConLog(wxString::Format(L"[REC]: Undo Count: %d\n", InputRecordingData.GetUndoCount()));
return true;
} }
wxString InputRecording::resolveGameName() wxString InputRecording::resolveGameName()

View File

@ -41,8 +41,8 @@ public:
bool IsInterruptFrame(); bool IsInterruptFrame();
void Stop(); void Stop();
void Create(wxString filename, bool fromSaveState, wxString authorName); bool Create(wxString filename, bool fromSaveState, wxString authorName);
void Play(wxString filename, bool fromSaveState); bool Play(wxString filename);
private: private:
InputRecordingFile InputRecordingData; InputRecordingFile InputRecordingData;
@ -55,5 +55,5 @@ private:
extern InputRecording g_InputRecording; extern InputRecording g_InputRecording;
static InputRecordingFile& g_InputRecordingData = g_InputRecording.GetInputRecordingData(); static InputRecordingFile& g_InputRecordingData = g_InputRecording.GetInputRecordingData();
static InputRecordingHeader& g_InputRecordingHeader = g_InputRecording.GetInputRecordingData().GetHeader(); static InputRecordingFileHeader& g_InputRecordingHeader = g_InputRecording.GetInputRecordingData().GetHeader();
#endif #endif

View File

@ -24,119 +24,154 @@
#include "InputRecordingFile.h" #include "InputRecordingFile.h"
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
long InputRecordingFile::GetBlockSeekPoint(const long& frame)
void InputRecordingFileHeader::Init()
{ {
if (savestate.fromSavestate) memset(author, 0, ArraySize(author));
{ memset(gameName, 0, ArraySize(gameName));
return RecordingHeaderSize + RecordingSavestateHeaderSize + frame * RecordingBlockSize;
}
else
{
return RecordingHeaderSize + sizeof(bool) + (frame)*RecordingBlockSize;
}
} }
// Inits the new (or existing) input recording file void InputRecordingFileHeader::SetEmulatorVersion()
bool InputRecordingFile::Open(const wxString path, bool fNewOpen, bool fromSaveState)
{ {
Close(); wxString emuVersion = wxString::Format("%s-%d.%d.%d", pxGetAppName().c_str(), PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo);
wxString mode = L"rb+"; int max = ArraySize(emu) - 1;
if (fNewOpen) strncpy(emu, emuVersion.c_str(), max);
{ emu[max] = 0;
mode = L"wb+"; }
MaxFrame = 0;
UndoCount = 0; void InputRecordingFileHeader::SetAuthor(wxString _author)
header.Init(); {
} int max = ArraySize(author) - 1;
recordingFile = wxFopen(path, mode); strncpy(author, _author.c_str(), max);
if (recordingFile == NULL) author[max] = 0;
{ }
recordingConLog(wxString::Format("[REC]: Movie file opening failed. Error - %s\n", strerror(errno)));
return false; void InputRecordingFileHeader::SetGameName(wxString _gameName)
} {
filename = path; int max = ArraySize(gameName) - 1;
strncpy(gameName, _gameName.c_str(), max);
if (fNewOpen) gameName[max] = 0;
{
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;
} }
// Gracefully close the current recording file
bool InputRecordingFile::Close() bool InputRecordingFile::Close()
{ {
if (recordingFile == NULL) if (recordingFile == NULL)
{ {
return false; return false;
} }
WriteHeader();
WriteSaveState();
fclose(recordingFile); fclose(recordingFile);
recordingFile = NULL; recordingFile = NULL;
filename = ""; filename = "";
return true; return true;
} }
// Write savestate flag to file const wxString &InputRecordingFile::GetFilename()
bool InputRecordingFile::WriteSaveState()
{ {
if (recordingFile == NULL) return filename;
{
return false;
}
fseek(recordingFile, RecordingSeekpointSaveState, SEEK_SET);
if (fwrite(&savestate.fromSavestate, sizeof(bool), 1, recordingFile) != 1)
{
return false;
}
return true;
} }
// Write controller input buffer to file (per frame) InputRecordingFileHeader &InputRecordingFile::GetHeader()
bool InputRecordingFile::WriteKeyBuf(const uint& frame, const uint port, const uint bufIndex, const u8& buf)
{ {
if (recordingFile == NULL) return header;
{
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;
} }
// Read controller input buffer from file (per frame) unsigned long &InputRecordingFile::GetTotalFrames()
bool InputRecordingFile::ReadKeyBuf(u8& result, const uint& frame, const uint port, const uint bufIndex) {
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) if (recordingFile == NULL)
{ {
return false; return false;
} }
long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize + 18 * port + bufIndex; long seek = getRecordingBlockSeekPoint(frame) + controllerInputBytes * port + bufIndex;
if (fseek(recordingFile, seek, SEEK_SET) != 0) if (fseek(recordingFile, seek, SEEK_SET) != 0)
{ {
return false; return false;
@ -149,150 +184,72 @@ bool InputRecordingFile::ReadKeyBuf(u8& result, const uint& frame, const uint po
return true; return true;
} }
void InputRecordingFile::SetTotalFrames(unsigned long frame)
void InputRecordingFile::GetPadData(PadData& result, unsigned long frame)
{ {
result.fExistKey = false; if (recordingFile == NULL || totalFrames >= frame)
if (recordingFile == NULL)
{ {
return; return;
} }
totalFrames = frame;
long seek = GetBlockSeekPoint(frame) + RecordingBlockHeaderSize; fseek(recordingFile, seekpointTotalFrames, SEEK_SET);
if (fseek(recordingFile, seek, SEEK_SET) != 0 || fread(result.buf, 1, RecordingBlockDataSize, recordingFile) == 0) fwrite(&totalFrames, 4, 1, recordingFile);
{
return;
}
result.fExistKey = true;
} }
bool InputRecordingFile::DeletePadData(unsigned long frame) bool InputRecordingFile::WriteHeader()
{
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()
{ {
if (recordingFile == NULL) if (recordingFile == NULL)
{ {
return false; return false;
} }
rewind(recordingFile); 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; 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; return false;
} }
fclose(ssFileCheck);
StateCopy_LoadFromFile(filename + "_SaveState.p2s"); long seek = getRecordingBlockSeekPoint(frame) + 18 * port + bufIndex;
}
else if (fseek(recordingFile, seek, SEEK_SET) != 0
|| fwrite(&buf, 1, 1, recordingFile) != 1)
{ {
sApp.SysExecute(); 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 // Check for current verison
@ -303,101 +260,19 @@ bool InputRecordingFile::ReadHeaderAndCheck()
} }
return true; return true;
} }
bool InputRecordingFile::WriteHeader()
{ bool InputRecordingFile::writeSaveState() {
if (recordingFile == NULL) if (recordingFile == NULL)
{ {
return false; 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 false;
} }
return true; 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 #endif

View File

@ -15,12 +15,15 @@
#pragma once #pragma once
#include "PadData.h"
#include "System.h" #include "System.h"
#include "PadData.h"
// NOTE / TODOs for Version 2
// - Move fromSavestate, undoCount, and total frames into the header
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
struct InputRecordingHeader struct InputRecordingFileHeader
{ {
u8 version = 1; u8 version = 1;
char emu[50] = ""; char emu[50] = "";
@ -29,70 +32,80 @@ struct InputRecordingHeader
public: public:
void SetEmulatorVersion(); void SetEmulatorVersion();
void Init();
void SetAuthor(wxString author); void SetAuthor(wxString author);
void SetGameName(wxString cdrom); 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 struct InputRecordingSavestate
{ {
// Whether we start from the savestate or from power-on // Whether we start from the savestate or from power-on
bool fromSavestate = false; bool fromSavestate = false;
}; };
// Handles all operations on the input recording file
class InputRecordingFile class InputRecordingFile
{ {
public: public:
InputRecordingFile() {}
~InputRecordingFile() { Close(); } ~InputRecordingFile() { Close(); }
// Movie File Manipulation // Closes the underlying input recording file, writing the header and
bool Open(const wxString fn, bool fNewOpen, bool fromSaveState); // prepares for a possible new recording to be started
bool Close(); bool Close();
bool WriteKeyBuf(const uint& frame, const uint port, const uint bufIndex, const u8& buf); // Retrieve the input recording's filename (not the path)
bool ReadKeyBuf(u8& result, const uint& frame, const uint port, const uint bufIndex); const wxString &GetFilename();
// Retrieve the input recording's header which contains high-level metadata on the recording
// Controller Data InputRecordingFileHeader &GetHeader();
void GetPadData(PadData& result_pad, unsigned long frame); // The maximum number of frames, or in other words, the length of the recording
bool DeletePadData(unsigned long frame); unsigned long &GetTotalFrames();
bool InsertPadData(unsigned long frame, const PadData& key); // The number of times a save-state has been loaded while recording this movie
bool UpdatePadData(unsigned long frame, const PadData& key); // this is also often referred to as a "re-record"
unsigned long &GetUndoCount();
// Header // Whether or not this input recording starts by loading a save-state or by booting the game fresh
InputRecordingHeader& GetHeader(); bool FromSaveState();
unsigned long& GetMaxFrame(); // Increment the number of undo actions and commit it to the recording file
unsigned long& GetUndoCount(); void IncrementUndoCount();
const wxString& GetFilename(); // 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 WriteHeader();
bool WriteMaxFrame(); // Writes the current frame's input data to the file so it can be replayed
bool WriteSaveState(); bool WriteKeyBuffer(const uint &frame, const uint port, const uint bufIndex, const u8 &buf);
bool ReadHeaderAndCheck();
void UpdateFrameMax(unsigned long frame);
void AddUndoCount();
private: private:
static const int RecordingSavestateHeaderSize = sizeof(bool); static const int controllerPortsSupported = 2;
static const int RecordingBlockHeaderSize = 0; static const int controllerInputBytes = 18;
static const int RecordingBlockDataSize = 18 * 2; static const int inputBytesPerFrame = controllerInputBytes * controllerPortsSupported;
static const int RecordingBlockSize = RecordingBlockHeaderSize + RecordingBlockDataSize; // TODO - version 2, this could be greatly simplified if everything was in the header
static const int RecordingSeekpointFrameMax = sizeof(InputRecordingHeader); // + 4 + 4 is the totalFrame and undoCount values
static const int RecordingSeekpointUndoCount = sizeof(InputRecordingHeader) + 4; static const int headerSize = sizeof(InputRecordingFileHeader) + 4 + 4;
static const int RecordingSeekpointSaveState = RecordingSeekpointUndoCount + 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 InputRecordingFileHeader header;
FILE* recordingFile = NULL;
wxString filename = ""; wxString filename = "";
long GetBlockSeekPoint(const long& frame); FILE * recordingFile = NULL;
// Header
InputRecordingHeader header;
InputRecordingSavestate savestate; InputRecordingSavestate savestate;
unsigned long MaxFrame = 0; unsigned long totalFrames = 0;
unsigned long UndoCount = 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 #endif

View File

@ -76,10 +76,6 @@ void PadData::SetNormalButtons(int port, std::vector<int> buttons)
void PadData::SetNormalButton(int port, PadData_NormalButton button, int fpushed) void PadData::SetNormalButton(int port, PadData_NormalButton button, int fpushed)
{ {
if (port < 0 || 1 < port)
{
return;
}
wxByte keybit[2]; wxByte keybit[2];
GetKeyBit(keybit, button); GetKeyBit(keybit, button);
int pressureByteIndex = GetPressureByte(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 int PadData::GetNormalButton(int port, PadData_NormalButton button) const
{ {
if (port < 0 || 1 < port)
{
return false;
}
wxByte keybit[2]; wxByte keybit[2];
GetKeyBit(keybit, button); GetKeyBit(keybit, button);
int pressureByteIndex = GetPressureByte(button); int pressureByteIndex = GetPressureByte(button);
@ -282,10 +274,6 @@ void PadData::SetAnalogVectors(int port, std::vector<int> vectors)
void PadData::SetAnalogVector(int port, PadData_AnalogVector vector, int val) void PadData::SetAnalogVector(int port, PadData_AnalogVector vector, int val)
{ {
if (port < 0 || 1 < port)
{
return;
}
if (val < 0) if (val < 0)
{ {
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 int PadData::GetAnalogVector(int port, PadData_AnalogVector vector) const
{ {
if (port < 0 || 1 < port)
{
return 0;
}
return buf[port][GetAnalogVectorByte(vector)]; return buf[port][GetAnalogVectorByte(vector)];
} }

View File

@ -110,13 +110,11 @@ void RecordingControls::TogglePause()
void RecordingControls::Pause() void RecordingControls::Pause()
{ {
fStop = true; fStop = true;
fFrameAdvance = true;
} }
void RecordingControls::Unpause() void RecordingControls::Unpause()
{ {
fStop = false; fStop = false;
fStart = true; fStart = true;
fFrameAdvance = true;
} }
#endif #endif

View File

@ -32,10 +32,6 @@ RecordingInputManager::RecordingInputManager()
void RecordingInputManager::ControllerInterrupt(u8 & data, u8 & port, u16 & BufCount, u8 buf[]) void RecordingInputManager::ControllerInterrupt(u8 & data, u8 & port, u16 & BufCount, u8 buf[])
{ {
if (port >= 2)
{
return;
}
if (virtualPad[port]) if (virtualPad[port])
{ {

View File

@ -217,6 +217,9 @@ SIO_WRITE sioWriteController(u8 data)
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
if (g_Conf->EmuOptions.EnableRecordingTools) if (g_Conf->EmuOptions.EnableRecordingTools)
{
// Only examine controllers 1 / 2
if (sio.slot[sio.port] == 0)
{ {
g_InputRecording.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf); g_InputRecording.ControllerInterrupt(data, sio.port, sio.bufCount, sio.buf);
if (g_InputRecording.IsInterruptFrame()) if (g_InputRecording.IsInterruptFrame())
@ -226,6 +229,7 @@ SIO_WRITE sioWriteController(u8 data)
PadData::LogPadData(sio.port, sio.bufCount, sio.buf); PadData::LogPadData(sio.port, sio.bufCount, sio.buf);
} }
}
#endif #endif
break; break;
} }

View File

@ -80,7 +80,7 @@ void Pcsx2App::OpenMainFrame()
VirtualPad* virtualPad0 = new VirtualPad(mainFrame, wxID_ANY, wxEmptyString, 0); VirtualPad* virtualPad0 = new VirtualPad(mainFrame, wxID_ANY, wxEmptyString, 0);
m_id_VirtualPad[0] = virtualPad0->GetId(); 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(); m_id_VirtualPad[1] = virtualPad1->GetId();
NewRecordingFrame* newRecordingFrame = new NewRecordingFrame(mainFrame); NewRecordingFrame* newRecordingFrame = new NewRecordingFrame(mainFrame);

View File

@ -1059,7 +1059,7 @@ void Pcsx2App::OnProgramLogClosed( wxWindowID id )
void Pcsx2App::OnMainFrameClosed( wxWindowID id ) void Pcsx2App::OnMainFrameClosed( wxWindowID id )
{ {
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
if (g_Conf->EmuOptions.EnableRecordingTools) if (g_InputRecording.GetModeState() == INPUT_RECORDING_MODE_NONE)
{ {
g_InputRecording.Stop(); g_InputRecording.Stop();
} }

View File

@ -749,7 +749,7 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt )
} }
title.Replace(L"${frame}", pxsFmt(L"%d", g_FrameCount)); 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); title.Replace(L"${mode}", movieMode);
#else #else
wxString title = templates.TitleTemplate; wxString title = templates.TitleTemplate;

View File

@ -34,6 +34,7 @@
# include "Recording/InputRecording.h" # include "Recording/InputRecording.h"
# include "Recording/RecordingControls.h" # include "Recording/RecordingControls.h"
# include "Recording/VirtualPad.h" # include "Recording/VirtualPad.h"
# include "Recording/RecordingControls.h"
#endif #endif
@ -499,7 +500,7 @@ void MainEmuFrame::Menu_EnableWideScreenPatches_Click( wxCommandEvent& )
} }
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&) void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent& event)
{ {
bool checked = GetMenuBar()->IsChecked(MenuId_EnableInputRecording); bool checked = GetMenuBar()->IsChecked(MenuId_EnableInputRecording);
// Confirm with User // Confirm with User
@ -529,6 +530,9 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&)
} }
else 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); GetMenuBar()->Remove(TopLevelMenu_InputRecording);
// Always turn controller logs off, but never turn it on by default // Always turn controller logs off, but never turn it on by default
SysConsole.controlInfo.Enabled = checked; SysConsole.controlInfo.Enabled = checked;
@ -540,6 +544,8 @@ void MainEmuFrame::Menu_EnableRecordingTools_Click(wxCommandEvent&)
viewport->InitDefaultAccelerators(); viewport->InitDefaultAccelerators();
} }
} }
if (g_RecordingControls.IsEmulationAndRecordingPaused())
g_RecordingControls.Unpause();
} }
g_Conf->EmuOptions.EnableRecordingTools = checked; g_Conf->EmuOptions.EnableRecordingTools = checked;
@ -868,49 +874,61 @@ void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click(wxCommandEvent & eve
#ifndef DISABLE_RECORDING #ifndef DISABLE_RECORDING
void MainEmuFrame::Menu_Recording_New_Click(wxCommandEvent &event) 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(); NewRecordingFrame* NewRecordingFrame = wxGetApp().GetNewRecordingFramePtr();
if (NewRecordingFrame) if (NewRecordingFrame)
{ {
if (NewRecordingFrame->ShowModal() == wxID_CANCEL) if (NewRecordingFrame->ShowModal() == wxID_CANCEL)
{ {
if (!initiallyPaused)
g_RecordingControls.Unpause();
return; return;
} }
// From Current Frame if (!g_InputRecording.Create(NewRecordingFrame->GetFile(), !NewRecordingFrame->GetFrom(), NewRecordingFrame->GetAuthor()))
if (NewRecordingFrame->GetFrom() == 0)
{ {
if (!CoreThread.IsOpen()) if (!initiallyPaused)
{ g_RecordingControls.Unpause();
recordingConLog(L"[REC]: Game is not open, aborting new input recording.\n");
return; 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());
}
} }
m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false); m_menuRecording.FindChildItem(MenuId_Recording_New)->Enable(false);
m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true);
if (!g_InputRecordingData.FromSaveState())
g_RecordingControls.Unpause();
} }
void MainEmuFrame::Menu_Recording_Play_Click(wxCommandEvent &event) 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"", wxFileDialog openFileDialog(this, _("Select P2M2 record file."), L"", L"",
L"p2m2 file(*.p2m2)|*.p2m2", wxFD_OPEN); L"p2m2 file(*.p2m2)|*.p2m2", wxFD_OPEN);
if (openFileDialog.ShowModal() == wxID_CANCEL) if (openFileDialog.ShowModal() == wxID_CANCEL)
{ {
if (!initiallyPaused)
g_RecordingControls.Unpause();
return; return;
} }
wxString path = openFileDialog.GetPath(); wxString path = openFileDialog.GetPath();
g_InputRecording.Play(path, 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_New)->Enable(false);
m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true); m_menuRecording.FindChildItem(MenuId_Recording_Stop)->Enable(true);
}
g_RecordingControls.Unpause();
} }
void MainEmuFrame::Menu_Recording_Stop_Click(wxCommandEvent &event) 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) void MainEmuFrame::Menu_Recording_VirtualPad_Open_Click(wxCommandEvent &event)
{ {
VirtualPad *vp = NULL; wxGetApp().GetVirtualPadPtr(event.GetId() - MenuId_Recording_VirtualPad_Port0)->Show();
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();
}
} }
#endif #endif