mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Squashed commit: [7955b42e3] recording: Throw errors on fread/fwrite errors. [5a2160f9e] recording: Remove function implementation from header files [f2937ab5f] recording: Fixed UndoCount metadata bug and will gracefully fail if savestate is missing [d7f4d43e5] recording: Refactored code-style to be consistent [0f77fbb71] recording: Refactor to use switch statements [28d7945f6] recording: Resolve CMake warnings and use tagged github links for cross-linking to LilyPad [7c01c6cb4] recording: corrected disparity between comment and code [17a8bd8d6] recording: Remove all usages of #define [3830f5a82] recording: Refactor enums and general cleanup [569ef7d67] recording: Completely disable new console log sources when recording is disabled
234 lines
6.2 KiB
C++
234 lines
6.2 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2019 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
|
|
#include "AppSaveStates.h"
|
|
#include "AppGameDatabase.h"
|
|
#include "Common.h"
|
|
#include "Counters.h"
|
|
#include "MemoryTypes.h"
|
|
#include "SaveState.h"
|
|
|
|
#include "InputRecording.h"
|
|
#include "Recording/RecordingControls.h"
|
|
|
|
#include <vector>
|
|
|
|
|
|
InputRecording g_InputRecording;
|
|
|
|
// Tag and save framecount along with savestate
|
|
void SaveStateBase::InputRecordingFreeze()
|
|
{
|
|
FreezeTag("InputRecording");
|
|
Freeze(g_FrameCount);
|
|
|
|
if (g_FrameCount > 0 && IsLoading())
|
|
{
|
|
g_InputRecordingData.AddUndoCount();
|
|
}
|
|
}
|
|
|
|
// Main func for handling controller input data
|
|
// - Called by Sio.cpp::sioWriteController
|
|
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
|
|
to the frames that matter, the ones that are reading from
|
|
the controller.
|
|
|
|
See - Lilypad.cpp::PADpoll - https://github.com/PCSX2/pcsx2/blob/v1.5.0-dev/plugins/LilyPad/LilyPad.cpp#L1193
|
|
0x42 is the magic number for the default read query
|
|
*/
|
|
if (bufCount == 1)
|
|
{
|
|
fInterruptFrame = data == 0x42;
|
|
if (!fInterruptFrame)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if ( bufCount == 2 )
|
|
{
|
|
/*
|
|
See - LilyPad.cpp::PADpoll - https://github.com/PCSX2/pcsx2/blob/v1.5.0-dev/plugins/LilyPad/LilyPad.cpp#L1194
|
|
0x5A is always the second byte in the buffer
|
|
when the normal READ_DATA_AND_VIBRRATE (0x42)
|
|
query is executed, this looks like a sanity check
|
|
*/
|
|
if (buf[bufCount] != 0x5A)
|
|
{
|
|
fInterruptFrame = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!fInterruptFrame
|
|
|| state == INPUT_RECORDING_MODE_NONE
|
|
// We do not want to record or save the first two
|
|
// bytes in the data returned from LilyPad
|
|
|| bufCount < 3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Read or Write
|
|
const u8 &nowBuf = buf[bufCount];
|
|
if (state == INPUT_RECORDING_MODE_RECORD)
|
|
{
|
|
InputRecordingData.UpdateFrameMax(g_FrameCount);
|
|
InputRecordingData.WriteKeyBuf(g_FrameCount, port, bufCount - 3, nowBuf);
|
|
}
|
|
else if (state == INPUT_RECORDING_MODE_REPLAY)
|
|
{
|
|
if (InputRecordingData.GetMaxFrame() <= 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))
|
|
{
|
|
buf[bufCount] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GUI Handler - Stop recording
|
|
void InputRecording::Stop()
|
|
{
|
|
state = INPUT_RECORDING_MODE_NONE;
|
|
if (InputRecordingData.Close())
|
|
{
|
|
recordingConLog(L"[REC]: InputRecording Recording Stopped.\n");
|
|
}
|
|
}
|
|
|
|
// GUI Handler - Start recording
|
|
void InputRecording::Create(wxString FileName, bool fromSaveState, wxString authorName)
|
|
{
|
|
g_RecordingControls.Pause();
|
|
Stop();
|
|
|
|
// create
|
|
if (!InputRecordingData.Open(FileName, true, fromSaveState))
|
|
{
|
|
return;
|
|
}
|
|
// Set author name
|
|
if (!authorName.IsEmpty())
|
|
{
|
|
InputRecordingData.GetHeader().SetAuthor(authorName);
|
|
}
|
|
// Set Game Name
|
|
// Code loosely taken from AppCoreThread.cpp to resolve the Game Name
|
|
// Fallback is ISO name
|
|
wxString gameName;
|
|
const wxString gameKey(SysGetDiscID());
|
|
if (!gameKey.IsEmpty())
|
|
{
|
|
if (IGameDatabase* GameDB = AppHost_GetGameDatabase())
|
|
{
|
|
Game_Data game;
|
|
if (GameDB->findGame(game, gameKey))
|
|
{
|
|
gameName = game.getString("Name");
|
|
gameName += L" (" + game.getString("Region") + L")";
|
|
}
|
|
}
|
|
}
|
|
InputRecordingData.GetHeader().SetGameName(!gameName.IsEmpty() ? gameName : Path::GetFilename(g_Conf->CurrentIso));
|
|
InputRecordingData.WriteHeader();
|
|
state = INPUT_RECORDING_MODE_RECORD;
|
|
recordingConLog(wxString::Format(L"[REC]: Started new recording - [%s]\n", FileName));
|
|
|
|
// In every case, we reset the g_FrameCount
|
|
g_FrameCount = 0;
|
|
}
|
|
|
|
// GUI Handler - Play a recording
|
|
void InputRecording::Play(wxString FileName, bool fromSaveState)
|
|
{
|
|
g_RecordingControls.Pause();
|
|
Stop();
|
|
|
|
if (!InputRecordingData.Open(FileName, false, false))
|
|
{
|
|
return;
|
|
}
|
|
if (!InputRecordingData.ReadHeaderAndCheck())
|
|
{
|
|
recordingConLog(L"[REC]: This file is not a correct InputRecording file.\n");
|
|
InputRecordingData.Close();
|
|
return;
|
|
}
|
|
// Check author name
|
|
if (!g_Conf->CurrentIso.IsEmpty())
|
|
{
|
|
if (Path::GetFilename(g_Conf->CurrentIso) != InputRecordingData.GetHeader().gameName)
|
|
{
|
|
recordingConLog(L"[REC]: Information on CD in Movie file is Different.\n");
|
|
}
|
|
}
|
|
state = INPUT_RECORDING_MODE_REPLAY;
|
|
recordingConLog(wxString::Format(L"[REC]: Replaying movie - [%s]\n", FileName));
|
|
recordingConLog(wxString::Format(L"[REC]: Recording File Version: %d\n", InputRecordingData.GetHeader().version));
|
|
recordingConLog(wxString::Format(L"[REC]: Associated Game Name / 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()));
|
|
}
|
|
|
|
// Keybind Handler - Toggle between recording input and not
|
|
void InputRecording::RecordModeToggle()
|
|
{
|
|
if (state == INPUT_RECORDING_MODE_REPLAY)
|
|
{
|
|
state = INPUT_RECORDING_MODE_RECORD;
|
|
recordingConLog("[REC]: Record mode ON.\n");
|
|
}
|
|
else if (state == INPUT_RECORDING_MODE_RECORD)
|
|
{
|
|
state = INPUT_RECORDING_MODE_REPLAY;
|
|
recordingConLog("[REC]: Replay mode ON.\n");
|
|
}
|
|
}
|
|
|
|
INPUT_RECORDING_MODE InputRecording::GetModeState()
|
|
{
|
|
return state;
|
|
}
|
|
|
|
InputRecordingFile & InputRecording::GetInputRecordingData()
|
|
{
|
|
return InputRecordingData;
|
|
}
|
|
|
|
bool InputRecording::IsInterruptFrame()
|
|
{
|
|
return fInterruptFrame;
|
|
}
|