mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
291 lines
8.0 KiB
C++
291 lines
8.0 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2022 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 "InputRecordingFile.h"
|
|
|
|
#include "Utilities/InputRecordingLogger.h"
|
|
|
|
#include "common/FileSystem.h"
|
|
#include "DebugTools/Debug.h"
|
|
#include "MemoryTypes.h"
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <vector>
|
|
#include <array>
|
|
|
|
void InputRecordingFile::InputRecordingFileHeader::init() noexcept
|
|
{
|
|
m_fileVersion = 1;
|
|
}
|
|
|
|
void InputRecordingFile::setEmulatorVersion()
|
|
{
|
|
static const std::string emuVersion = fmt::format("PCSX2-{}.{}.{}", PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo);
|
|
strncpy(m_header.m_emulatorVersion, emuVersion.c_str(), sizeof(m_header.m_emulatorVersion) - 1);
|
|
}
|
|
|
|
void InputRecordingFile::setAuthor(const std::string& _author)
|
|
{
|
|
strncpy(m_header.m_author, _author.data(), sizeof(m_header.m_author) - 1);
|
|
}
|
|
|
|
void InputRecordingFile::setGameName(const std::string& _gameName)
|
|
{
|
|
strncpy(m_header.m_gameName, _gameName.data(), sizeof(m_header.m_gameName) - 1);
|
|
}
|
|
|
|
const char* InputRecordingFile::getEmulatorVersion() const noexcept
|
|
{
|
|
return m_header.m_emulatorVersion;
|
|
}
|
|
|
|
const char* InputRecordingFile::getAuthor() const noexcept
|
|
{
|
|
return m_header.m_author;
|
|
}
|
|
|
|
const char* InputRecordingFile::getGameName() const noexcept
|
|
{
|
|
return m_header.m_gameName;
|
|
}
|
|
|
|
bool InputRecordingFile::close() noexcept
|
|
{
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
fclose(m_recordingFile);
|
|
m_recordingFile = nullptr;
|
|
m_filename.clear();
|
|
return true;
|
|
}
|
|
|
|
const std::string& InputRecordingFile::getFilename() const noexcept
|
|
{
|
|
return m_filename;
|
|
}
|
|
|
|
unsigned long InputRecordingFile::getTotalFrames() const noexcept
|
|
{
|
|
return m_totalFrames;
|
|
}
|
|
|
|
unsigned long InputRecordingFile::getUndoCount() const noexcept
|
|
{
|
|
return m_undoCount;
|
|
}
|
|
|
|
bool InputRecordingFile::fromSaveState() const noexcept
|
|
{
|
|
return m_savestate;
|
|
}
|
|
|
|
void InputRecordingFile::incrementUndoCount()
|
|
{
|
|
m_undoCount++;
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
fseek(m_recordingFile, s_seekpointUndoCount, SEEK_SET);
|
|
fwrite(&m_undoCount, 4, 1, m_recordingFile);
|
|
}
|
|
|
|
bool InputRecordingFile::openNew(const std::string& path, bool fromSavestate)
|
|
{
|
|
if ((m_recordingFile = FileSystem::OpenCFile(path.data(), "wb+")) == nullptr)
|
|
{
|
|
InputRec::consoleLog(fmt::format("Input recording file opening failed. Error - {}", strerror(errno)));
|
|
return false;
|
|
}
|
|
|
|
m_filename = path;
|
|
m_totalFrames = 0;
|
|
m_undoCount = 0;
|
|
m_header.init();
|
|
m_savestate = fromSavestate;
|
|
return true;
|
|
}
|
|
|
|
bool InputRecordingFile::openExisting(const std::string& path)
|
|
{
|
|
if ((m_recordingFile = FileSystem::OpenCFile(path.data(), "rb+")) == nullptr)
|
|
{
|
|
InputRec::consoleLog(fmt::format("Input recording file opening failed. Error - {}", strerror(errno)));
|
|
return false;
|
|
}
|
|
|
|
if (!verifyRecordingFileHeader())
|
|
{
|
|
close();
|
|
InputRec::consoleLog("Input recording file header is invalid");
|
|
return false;
|
|
}
|
|
|
|
m_filename = path;
|
|
return true;
|
|
}
|
|
|
|
std::optional<PadData> InputRecordingFile::readPadData(const uint frame, const uint port, const uint slot)
|
|
{
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::array<u8, s_controllerInputBytes> data{};
|
|
|
|
// TODO - slot unused, use it in the new format
|
|
const size_t seek = getRecordingBlockSeekPoint(frame) + s_controllerInputBytes * port;
|
|
if (fseek(m_recordingFile, seek, SEEK_SET) != 0 || fread(&data, 1, 18, m_recordingFile) != 1)
|
|
{
|
|
return PadData(port, slot, data);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
void InputRecordingFile::setTotalFrames(u32 frame)
|
|
{
|
|
if (m_recordingFile == nullptr || m_totalFrames >= frame)
|
|
{
|
|
return;
|
|
}
|
|
m_totalFrames = frame;
|
|
fseek(m_recordingFile, s_seekpointTotalFrames, SEEK_SET);
|
|
fwrite(&m_totalFrames, 4, 1, m_recordingFile);
|
|
}
|
|
|
|
bool InputRecordingFile::writeHeader() const
|
|
{
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
rewind(m_recordingFile);
|
|
if (fwrite(&m_header, sizeof(InputRecordingFileHeader), 1, m_recordingFile) != 1 ||
|
|
fwrite(&m_totalFrames, 4, 1, m_recordingFile) != 1 ||
|
|
fwrite(&m_undoCount, 4, 1, m_recordingFile) != 1 ||
|
|
fwrite(&m_savestate, 1, 1, m_recordingFile) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InputRecordingFile::writePadData(const uint frame, const PadData data) const
|
|
{
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// TODO - use the slot in the future
|
|
const size_t seek = getRecordingBlockSeekPoint(frame) + s_controllerInputBytes * data.m_port;
|
|
|
|
// seek to the correct position and write data to the file
|
|
if (fseek(m_recordingFile, seek, SEEK_SET) != 0 ||
|
|
fwrite(&data.m_compactPressFlagsGroupOne, 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&data.m_compactPressFlagsGroupTwo, 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<0>(data.m_rightAnalog), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_rightAnalog), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<0>(data.m_leftAnalog), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_leftAnalog), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_right), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_left), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_up), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_down), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_triangle), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_circle), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_cross), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_square), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_l1), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_r1), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_l2), 1, 1, m_recordingFile) != 1 ||
|
|
fwrite(&std::get<1>(data.m_r2), 1, 1, m_recordingFile) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
fflush(m_recordingFile);
|
|
return true;
|
|
}
|
|
|
|
void InputRecordingFile::logRecordingMetadata()
|
|
{
|
|
InputRec::consoleMultiLog({fmt::format("File: {}", getFilename()),
|
|
fmt::format("PCSX2 Version Used: {}", m_header.m_emulatorVersion),
|
|
fmt::format("Recording File Version: {}", m_header.m_fileVersion),
|
|
fmt::format("Associated Game Name or ISO Filename: {}", m_header.m_gameName),
|
|
fmt::format("Author: {}", m_header.m_author),
|
|
fmt::format("Total Frames: {}", getTotalFrames()),
|
|
fmt::format("Undo Count: {}", getUndoCount())});
|
|
}
|
|
|
|
std::vector<PadData> InputRecordingFile::bulkReadPadData(u32 frameStart, u32 frameEnd, const uint port)
|
|
{
|
|
std::vector<PadData> data;
|
|
|
|
if (m_recordingFile == nullptr || frameEnd < frameStart)
|
|
{
|
|
return data;
|
|
}
|
|
|
|
// TODO - no multi-tap support
|
|
for (uint64_t currFrame = frameStart; currFrame < frameEnd; currFrame++)
|
|
{
|
|
const auto padData = readPadData(currFrame, port, 0);
|
|
if (padData)
|
|
{
|
|
data.push_back(padData.value());
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
size_t InputRecordingFile::getRecordingBlockSeekPoint(const u32 frame) const noexcept
|
|
{
|
|
return s_headerSize + sizeof(bool) + frame * s_inputBytesPerFrame;
|
|
}
|
|
|
|
bool InputRecordingFile::verifyRecordingFileHeader()
|
|
{
|
|
if (m_recordingFile == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
// Verify header contents
|
|
rewind(m_recordingFile);
|
|
if (fread(&m_header, sizeof(InputRecordingFileHeader), 1, m_recordingFile) != 1 ||
|
|
fread(&m_totalFrames, 4, 1, m_recordingFile) != 1 ||
|
|
fread(&m_undoCount, 4, 1, m_recordingFile) != 1 ||
|
|
fread(&m_savestate, sizeof(bool), 1, m_recordingFile) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check for current verison
|
|
if (m_header.m_fileVersion != 1)
|
|
{
|
|
InputRec::consoleLog(fmt::format("Input recording file is not a supported version - {}", m_header.m_fileVersion));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|