input-rec: remove integration from SIO/Counters, fix imgui updating

This commit is contained in:
Tyler Wilding 2022-11-26 14:39:14 -05:00 committed by refractionpcsx2
parent 96d9eadb4b
commit 7383bc3f44
11 changed files with 358 additions and 335 deletions

View File

@ -614,11 +614,6 @@ static __fi void VSyncStart(u32 sCycle)
// Not doing so would sacrifice a frame of a savestate-based recording when loading any savestate
#ifndef PCSX2_CORE
g_InputRecordingControls.HandlePausingAndLocking();
#else
if (g_InputRecording.isActive())
{
g_InputRecording.handleExceededFrameCounter();
}
#endif
}
@ -686,12 +681,6 @@ static __fi void VSyncEnd(u32 sCycle)
{
g_InputRecordingControls.CheckPauseStatus();
}
#else
if (EmuConfig.EnableRecordingTools && g_InputRecording.isActive())
{
g_InputRecording.getControls().processControlQueue();
g_InputRecording.incFrameCounter();
}
#endif
if(EmuConfig.Trace.Enabled && EmuConfig.Trace.EE.m_EnableAll)

View File

@ -16,6 +16,7 @@
#pragma once
#include "PAD/Host/PAD.h"
#include <tuple>
namespace PAD
{
@ -49,6 +50,14 @@ namespace PAD
void Set(u32 pad, u32 index, float value);
__fi void SetRawAnalogs(const u32 pad, const std::tuple<u8, u8> left, const std::tuple<u8, u8> right)
{
m_analog[pad].lx = std::get<0>(left);
m_analog[pad].ly = std::get<1>(left);
m_analog[pad].rx = std::get<0>(right);
m_analog[pad].ry = std::get<1>(right);
}
__fi PAD::ControllerType GetType(u32 pad) { return m_type[pad]; }
__fi void SetType(u32 pad, PAD::ControllerType type) { m_type[pad] = type; }
@ -75,6 +84,9 @@ namespace PAD
__fi u8 GetRawPressure(u32 pad, u32 index) const { return m_button_pressure[pad][index]; }
__fi std::tuple<u8, u8> GetRawLeftAnalog(u32 pad) const { return {m_analog[pad].lx, m_analog[pad].ly}; }
__fi std::tuple<u8, u8> GetRawRightAnalog(u32 pad) const { return {m_analog[pad].rx, m_analog[pad].ry}; }
u32 GetButtons(u32 pad);
u8 GetPressure(u32 pad, u32 index);
};

View File

@ -479,18 +479,7 @@ wxString InputRecording::resolveGameName()
#include "DebugTools/Debug.h"
#include "GameDatabase.h"
#include "fmt/format.h"
// Future TODOs
// - restart
// - tooltips on GUI options
// - Controller Logging (virtual pad related)
// - persist last browsed IR path
// - logs are weirdly formatted
// - force OSD updates since a lot of input recording occurs during a paused state
// - differentiating OSD logs somehow would be nice (color / a preceding icon?)
#include <queue>
#include <fmt/format.h>
#include "GS.h"
void SaveStateBase::InputRecordingFreeze()
{
@ -517,9 +506,11 @@ bool InputRecording::create(const std::string& fileName, const bool fromSaveStat
{
FileSystem::CopyFilePath(savestatePath.c_str(), fmt::format("{}.bak", savestatePath).c_str(), true);
}
m_initial_load_complete = false;
m_type = Type::FROM_SAVESTATE;
m_is_active = true;
m_initial_load_complete = true;
m_watching_for_rerecords = true;
setStartingFrame(g_FrameCount);
// TODO - error handling
VMManager::SaveState(savestatePath.c_str());
}
@ -590,73 +581,98 @@ bool InputRecording::play(const std::string& filename)
{
InputRec::consoleLog(fmt::format("Input recording was possibly constructed for a different game. Expected: {}, Actual: {}", m_file.getGameName(), resolveGameName()));
}
return true;
}
void InputRecording::closeActiveFile()
{
if (!m_is_active)
{
return;
}
if (m_file.close())
{
m_is_active = false;
InputRec::log("Input recording stopped");
GetMTGS().PresentCurrentFrame();
}
else
{
InputRec::log("Unable to stop input recording");
}
}
void InputRecording::stop()
{
m_is_active = false;
if (m_file.close())
if (VMManager::GetState() == VMState::Paused)
{
InputRec::log("Input recording stopped");
closeActiveFile();
}
else
{
// Don't stop immediately, close the file after the current frame completes
m_recordingQueue.push([&]() {
closeActiveFile();
});
}
}
// TODO: Refactor this
void InputRecording::ControllerInterrupt(u8 port, size_t fifoSize, u8 dataIn, u8 dataOut)
void InputRecording::handleControllerDataUpdate()
{
m_pad_data_available = data == s_READ_DATA_AND_VIBRATE_QUERY_FIRST_BYTE;
}
void InputRecording::querySecondByte(const u8 data)
{
m_pad_data_available &= data == s_READ_DATA_AND_VIBRATE_QUERY_SECOND_BYTE;
}
void InputRecording::controllerInterrupt(u8 port, size_t fifoSize, u8& dataIn, u8& dataOut)
{
// TODO - Multi-Tap Support (Qt doesn't support it yet anyway!)
// Keep these safe-guard checks in here, they are input recording only concerns
if (fifoSize == 1)
// TODO - multi-tap support with new file format, for now just controller 0 and 1
for (int i = 0; i < 2; i++)
{
queryFirstByte(dataOut);
return;
}
else if (fifoSize == 2)
{
querySecondByte(dataOut);
return;
}
if (!m_pad_data_available)
{
// bad data / first and second byte checks failed
return;
}
if (m_controls.isReplaying())
{
if (!m_file.readKeyBuffer(dataOut, m_frame_counter, port, fifoSize))
// Fetch the current frame's data
PadData frameData(i, 0);
if (m_is_active)
{
InputRec::consoleLog(fmt::format("Failed to read input data at frame {}", m_frame_counter));
}
// Update controller data state for future VirtualPad / logging usage.
//pads[port].padData->UpdateControllerData(bufIndex, bufVal);
}
// If there is data to read (previous two bytes looked correct)
if (bufCount >= 3 && m_pad_data_available)
{
u8& bufVal = dataOut;
const u16 bufIndex = fifoSize - 3;
if (state == InputRecordingMode::Replaying)
{
if (!m_file.writeKeyBuffer(m_frame_counter, port, fifoSize, dataOut))
if (m_controls.isRecording())
{
InputRec::consoleLog(fmt::format("Failed to write input data at frame {}", m_frame_counter));
saveControllerData(frameData, i, 0);
}
else if (m_controls.isReplaying())
{
const auto& modifiedFrameData = updateControllerData(i, 0);
if (modifiedFrameData) {
frameData = modifiedFrameData.value();
}
}
}
// Log the data we have gathered, useful for debugging our use-case
frameData.LogPadData();
}
}
void InputRecording::saveControllerData(const PadData& data, const int port, const int slot)
{
// Save the frame's data to the file
if (!m_file.writePadData(m_frame_counter, data))
{
InputRec::consoleLog(fmt::format("Failed to write input data at [{}:{}:{}]", m_frame_counter, port, slot));
}
}
std::optional<PadData> InputRecording::updateControllerData(const int port, const int slot)
{
// Get the PadData from the file
const auto frameData = m_file.readPadData(m_frame_counter, port, slot);
if (frameData)
{
// Update the g_key_status appropriately
frameData->OverrideActualController();
}
else
{
InputRec::consoleLog(fmt::format("Failed to read input data at [{}:{}:{}]", m_frame_counter, port, slot));
}
return frameData;
}
void InputRecording::processRecordQueue()
{
while (!m_recordingQueue.empty())
{
m_recordingQueue.front()();
m_recordingQueue.pop();
}
}
@ -677,6 +693,11 @@ std::string InputRecording::resolveGameName()
void InputRecording::incFrameCounter()
{
if (!m_is_active)
{
return;
}
if (m_frame_counter >= std::numeric_limits<u64>::max())
{
// TODO - log the incredible achievment of playing for longer than 4 billion years, and end the recording
@ -730,7 +751,9 @@ void InputRecording::handleExceededFrameCounter()
void InputRecording::handleReset()
{
if (m_initial_load_complete)
{
adjustFrameCounterOnReRecord(0);
}
m_initial_load_complete = true;
}
@ -817,4 +840,4 @@ void InputRecording::initializeState()
m_watching_for_rerecords = false;
}
#endif
#endif

View File

@ -161,22 +161,19 @@ public:
POWER_ON,
FROM_SAVESTATE
};
bool create(const std::string& filename, const bool fromSaveState, const std::string& authorName);
bool play(const std::string& path);
void stop();
void queryFirstByte(const u8 data);
void querySecondByte(const u8 bufVal);
void controllerInterrupt(const u8 port, const size_t fifoSize, u8& dataIn, u8& dataOut);
void handleControllerDataUpdate();
void saveControllerData(const PadData& data, const int port, const int slot);
std::optional<PadData> updateControllerData(const int port, const int slot);
void incFrameCounter();
u64 getFrameCounter() const;
bool isActive() const;
void processRecordQueue();
// Main handler for ingesting input data and either saving it to the recording file (recording)
// or mutating it to the contents of the recording file (replaying)
void ControllerInterrupt(u8 port, size_t fifoSize, u8 dataIn, u8 dataOut);
void handleExceededFrameCounter();
void handleReset();
void handleLoadingSavestate();
@ -187,11 +184,6 @@ public:
const InputRecordingFile& getData() const;
private:
// - https://github.com/PCSX2/pcsx2/blob/7db9627ff6986c2d3faeecc58525a0e32da2f29f/pcsx2/PAD/Windows/PAD.cpp#L1141
static constexpr u8 s_READ_DATA_AND_VIBRATE_QUERY_FIRST_BYTE = 0x79;
// - https://github.com/PCSX2/pcsx2/blob/7db9627ff6986c2d3faeecc58525a0e32da2f29f/pcsx2/PAD/Windows/PAD.cpp#L1142
static constexpr u8 s_READ_DATA_AND_VIBRATE_QUERY_SECOND_BYTE = 0x5A;
InputRecordingControls m_controls;
InputRecordingFile m_file;
@ -202,12 +194,16 @@ private:
bool m_pad_data_available = false;
bool m_watching_for_rerecords = false;
// A consistent way to run actions at the end of the each frame (ie. stop the recording)
std::queue<std::function<void()>> m_recordingQueue;
u32 m_frame_counter = 0;
// Either 0 for a power-on movie, or the g_FrameCount that is stored on the starting frame
u32 m_starting_frame = 0;
void initializeState();
void setStartingFrame(u32 startingFrame);
void closeActiveFile();
private:
// Resolve the name and region of the game currently loaded using the GameDB

View File

@ -225,7 +225,7 @@ void InputRecordingControls::StopCapture() const
#include "InputRecordingControls.h"
#include "Utilities/InputRecordingLogger.h"
#include "GS/GS.h"
#include "GS.h"
#include "VMManager.h"
void InputRecordingControls::toggleRecordMode()
@ -246,7 +246,7 @@ void InputRecordingControls::setRecordMode(bool waitForFrameToEnd)
{
m_state = Mode::Recording;
InputRec::log("Record mode ON");
GSPresentCurrentFrame();
GetMTGS().PresentCurrentFrame();
}
else
{
@ -263,7 +263,7 @@ void InputRecordingControls::setReplayMode(bool waitForFrameToEnd)
{
m_state = Mode::Replaying;
InputRec::log("Replay mode ON");
GSPresentCurrentFrame();
GetMTGS().PresentCurrentFrame();
}
else
{
@ -281,10 +281,15 @@ bool InputRecordingControls::isReplaying() const
void InputRecordingControls::processControlQueue()
{
while (!m_controlQueue.empty())
if (!m_controlQueue.empty())
{
m_controlQueue.front()();
m_controlQueue.pop();
while (!m_controlQueue.empty())
{
m_controlQueue.front()();
m_controlQueue.pop();
}
GetMTGS().PresentCurrentFrame();
}
}

View File

@ -359,20 +359,23 @@ bool InputRecordingFile::openExisting(const std::string& path)
return true;
}
bool InputRecordingFile::readKeyBuffer(u8& result, const uint frame, const uint port, const uint bufIndex)
std::optional<PadData> InputRecordingFile::readPadData(const uint frame, const uint port, const uint slot)
{
if (m_recordingFile == nullptr)
{
return false;
return std::nullopt;
}
const size_t seek = getRecordingBlockSeekPoint(frame) + s_controllerInputBytes * port + bufIndex;
if (fseek(m_recordingFile, seek, SEEK_SET) != 0 || fread(&result, 1, 1, m_recordingFile) != 1)
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 false;
return PadData(port, slot, data);
}
return true;
return std::nullopt;
}
void InputRecordingFile::setTotalFrames(u32 frame)
@ -403,17 +406,36 @@ bool InputRecordingFile::writeHeader() const
return true;
}
bool InputRecordingFile::writeKeyBuffer(const uint frame, const uint port, const uint bufIndex, const u8 buf) const
bool InputRecordingFile::writePadData(const uint frame, const PadData data) const
{
if (m_recordingFile == nullptr)
{
return false;
}
const size_t seek = getRecordingBlockSeekPoint(frame) + s_controllerInputBytes * port + bufIndex;
// 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(&buf, 1, 1, m_recordingFile) != 1)
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;
}
@ -425,7 +447,7 @@ bool InputRecordingFile::writeKeyBuffer(const uint frame, const uint port, const
void InputRecordingFile::logRecordingMetadata()
{
InputRec::consoleMultiLog({fmt::format("File: {}", getFilename()),
fmt::format("PCSX2 Version Used: {}",m_header.m_emulatorVersion),
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),
@ -442,32 +464,15 @@ std::vector<PadData> InputRecordingFile::bulkReadPadData(u32 frameStart, u32 fra
return data;
}
const size_t size = static_cast<size_t>(frameEnd - frameStart);
data.reserve(size);
std::array<u8, s_controllerInputBytes> padBytes;
const size_t seek = getRecordingBlockSeekPoint(frameStart) + s_controllerInputBytes * port;
const size_t skip = s_controllerInputBytes * (s_controllerPortsSupported - port - 1);
fseek(m_recordingFile, seek, SEEK_SET);
for (int frame = 0; frame < size; frame++)
// TODO - no multi-tap support
for (uint64_t currFrame = frameStart; currFrame < frameEnd; currFrame++)
{
if (fread(&padBytes, 1, s_controllerInputBytes, m_recordingFile) != s_controllerInputBytes)
const auto padData = readPadData(currFrame, port, 0);
if (padData)
{
data.shrink_to_fit();
break;
data.push_back(padData.value());
}
PadData frameData;
for (int i = 0; i < padBytes.size(); i++)
{
frameData.UpdateControllerData(i, padBytes.at(i));
}
data.push_back(std::move(frameData));
fseek(m_recordingFile, skip, SEEK_CUR);
}
return data;
}

View File

@ -164,13 +164,13 @@ public:
bool openNew(const std::string& 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);
std::optional<PadData> readPadData(const uint frame, const uint port, const uint slot);
// Updates the total frame counter and commit it to the recording file
void setTotalFrames(u32 frames);
// Persist the input recording file header's current state to the file
bool writeHeader() const;
// 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) const;
bool writePadData(const uint frame, const PadData data) const;
// Retrieve the input recording's filename (not the path)

View File

@ -208,104 +208,161 @@ void PadData::LogPadData(u8 const& port)
#include <fmt/core.h>
void PadData::UpdateControllerData(u16 bufIndex, u8 const bufVal) noexcept
#include "PAD/Host/KeyStatus.h"
#include "Sio.h"
PadData::PadData(const int port, const int slot)
{
if (bufIndex == static_cast<u8>(BufferIndex::PressedFlagsGroupOne))
m_port = port;
m_slot = slot;
m_ext_port = sioConvertPortAndSlotToPad(m_port, m_slot);
// Get the state of the buttons
// TODO - for the new recording file format, allow informing max number of buttons per frame per controller as well (ie. the analog button)
const u32 buttons = g_key_status.GetButtons(m_ext_port);
// - pressed group one
// - left
// - down
// - right
// - up
// - start
// - r3
// - l3
// - select
m_compactPressFlagsGroupOne = (buttons & 0b1111111100000000) >> 8;
// - pressed group two
// - square
// - cross
// - circle
// - triangle
// - r1
// - l1
// - r2
// - l2
m_compactPressFlagsGroupTwo = (buttons & 0b11111111);
// Get the analog values
m_rightAnalog = g_key_status.GetRawRightAnalog(m_ext_port);
m_leftAnalog = g_key_status.GetRawLeftAnalog(m_ext_port);
// Get pressure bytes (12 of them)
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_LEFT)};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_DOWN)};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_RIGHT)};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_UP)};
m_start = (0b00001000 & m_compactPressFlagsGroupOne) == 0;
m_r3 = (0b00000100 & m_compactPressFlagsGroupOne) == 0;
m_l3 = (0b00000010 & m_compactPressFlagsGroupOne) == 0;
m_select = (0b00000001 & m_compactPressFlagsGroupOne) == 0;
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_SQUARE)};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_CROSS)};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_CIRCLE)};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_TRIANGLE)};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_R1)};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_L1)};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_R2)};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_L2)};
}
PadData::PadData(const int port, const int slot, const std::array<u8, 18> data)
{
m_port = port;
m_slot = slot;
m_ext_port = sioConvertPortAndSlotToPad(m_port, m_slot);
m_compactPressFlagsGroupOne = data.at(0);
m_compactPressFlagsGroupTwo = data.at(1);
m_rightAnalog = {data.at(2), data.at(3)};
m_leftAnalog = {data.at(4), data.at(5)};
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, data.at(7)};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, data.at(9)};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, data.at(6)};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, data.at(8)};
m_start = (0b00001000 & m_compactPressFlagsGroupOne) == 0;
m_r3 = (0b00000100 & m_compactPressFlagsGroupOne) == 0;
m_l3 = (0b00000010 & m_compactPressFlagsGroupOne) == 0;
m_select = (0b00000001 & m_compactPressFlagsGroupOne) == 0;
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, data.at(13)};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, data.at(12)};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, data.at(11)};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, data.at(10)};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, data.at(15)};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, data.at(14)};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, data.at(17)};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, data.at(16)};
}
void PadData::OverrideActualController() const
{
g_key_status.SetRawAnalogs(m_ext_port, m_leftAnalog, m_rightAnalog);
g_key_status.Set(m_ext_port, PAD_RIGHT, std::get<1>(m_right));
g_key_status.Set(m_ext_port, PAD_LEFT, std::get<1>(m_left));
g_key_status.Set(m_ext_port, PAD_UP, std::get<1>(m_up));
g_key_status.Set(m_ext_port, PAD_DOWN, std::get<1>(m_down));
g_key_status.Set(m_ext_port, PAD_START, m_start);
g_key_status.Set(m_ext_port, PAD_SELECT, m_select);
g_key_status.Set(m_ext_port, PAD_R3, m_r3);
g_key_status.Set(m_ext_port, PAD_L3, m_l3);
g_key_status.Set(m_ext_port, PAD_SQUARE, std::get<1>(m_square));
g_key_status.Set(m_ext_port, PAD_CROSS, std::get<1>(m_cross));
g_key_status.Set(m_ext_port, PAD_CIRCLE, std::get<1>(m_circle));
g_key_status.Set(m_ext_port, PAD_TRIANGLE, std::get<1>(m_triangle));
g_key_status.Set(m_ext_port, PAD_R1, std::get<1>(m_r1));
g_key_status.Set(m_ext_port, PAD_L1, std::get<1>(m_l1));
g_key_status.Set(m_ext_port, PAD_R2, std::get<1>(m_r2));
g_key_status.Set(m_ext_port, PAD_L2, std::get<1>(m_l2));
}
void addButtonInfoToString(std::string label, std::string& str, std::tuple<bool, u8> buttonInfo)
{
const auto& [pressed, pressure] = buttonInfo;
if (pressed)
{
m_leftPressed.setPressedState(bufVal);
m_downPressed.setPressedState(bufVal);
m_rightPressed.setPressedState(bufVal);
m_upPressed.setPressedState(bufVal);
m_start.setPressedState(bufVal);
m_r3.setPressedState(bufVal);
m_l3.setPressedState(bufVal);
m_select.setPressedState(bufVal);
}
else if (bufIndex == static_cast<u8>(BufferIndex::PressedFlagsGroupTwo))
{
m_squarePressed.setPressedState(bufVal);
m_crossPressed.setPressedState(bufVal);
m_circlePressed.setPressedState(bufVal);
m_trianglePressed.setPressedState(bufVal);
m_r1Pressed.setPressedState(bufVal);
m_l1Pressed.setPressedState(bufVal);
m_r2Pressed.setPressedState(bufVal);
m_l2Pressed.setPressedState(bufVal);
}
else
{
bufIndex -= 2;
if (bufIndex < sizeof(m_allIntensities) / sizeof(u8*))
*m_allIntensities[bufIndex] = bufVal;
str += fmt::format(" {}:{}", label, pressure);
}
}
u8 PadData::PollControllerData(u16 bufIndex) const noexcept
void addButtonInfoToString(std::string label, std::string& str, bool pressed)
{
u8 byte = 0;
if (bufIndex == static_cast<u8>(BufferIndex::PressedFlagsGroupOne))
if (pressed)
{
// Construct byte by combining flags if the buttons are pressed
byte |= m_leftPressed.getMaskIfPressed();
byte |= m_downPressed.getMaskIfPressed();
byte |= m_rightPressed.getMaskIfPressed();
byte |= m_upPressed.getMaskIfPressed();
byte |= m_start.getMaskIfPressed();
byte |= m_r3.getMaskIfPressed();
byte |= m_l3.getMaskIfPressed();
byte |= m_select.getMaskIfPressed();
// We flip the bits because as mentioned below, 0 = pressed
byte = ~byte;
str += fmt::format(" {}", label);
}
else if (bufIndex == static_cast<u8>(BufferIndex::PressedFlagsGroupTwo))
{
// Construct byte by combining flags if the buttons are pressed
byte |= m_squarePressed.getMaskIfPressed();
byte |= m_crossPressed.getMaskIfPressed();
byte |= m_circlePressed.getMaskIfPressed();
byte |= m_trianglePressed.getMaskIfPressed();
byte |= m_r1Pressed.getMaskIfPressed();
byte |= m_l1Pressed.getMaskIfPressed();
byte |= m_r2Pressed.getMaskIfPressed();
byte |= m_l2Pressed.getMaskIfPressed();
// We flip the bits because as mentioned below, 0 = pressed
byte = ~byte;
}
else
{
bufIndex -= 2;
if (bufIndex < sizeof(m_allIntensities) / sizeof(u8*))
byte = *m_allIntensities[bufIndex - 2];
}
return byte;
}
std::string PadData::RawPadBytesToString(int start, int end)
void PadData::LogPadData() const
{
std::string str;
for (int i = start; i < end; i++)
{
str += fmt::format("{}", PollControllerData(i));
std::string pressedButtons = "";
addButtonInfoToString("Square", pressedButtons, m_square);
addButtonInfoToString("Cross", pressedButtons, m_cross);
addButtonInfoToString("Circle", pressedButtons, m_circle);
addButtonInfoToString("Triangle", pressedButtons, m_triangle);
if (i != end - 1)
str += ", ";
}
return str;
}
addButtonInfoToString("D-Right", pressedButtons, m_right);
addButtonInfoToString("D-Left", pressedButtons, m_left);
addButtonInfoToString("D-Up", pressedButtons, m_up);
addButtonInfoToString("D-Down", pressedButtons, m_down);
void PadData::LogPadData(u8 const& port)
{
const std::string pressedBytes = RawPadBytesToString(0, 2);
const std::string rightAnalogBytes = RawPadBytesToString(2, 4);
const std::string leftAnalogBytes = RawPadBytesToString(4, 6);
const std::string pressureBytes = RawPadBytesToString(6, 17);
const std::string fullLog =
fmt::format("[PAD {}] Raw Bytes: Pressed = [{}]\n", port + 1, pressedBytes) +
fmt::format("[PAD {}] Raw Bytes: Right Analog = [{}]\n", port + 1, rightAnalogBytes) +
fmt::format("[PAD {}] Raw Bytes: Left Analog = [{}]\n", port + 1, leftAnalogBytes) +
fmt::format("[PAD {}] Raw Bytes: Pressure = [{}]\n", port + 1, pressureBytes);
controlLog(fullLog);
addButtonInfoToString("R1", pressedButtons, m_r1);
addButtonInfoToString("L1", pressedButtons, m_l1);
addButtonInfoToString("R2", pressedButtons, m_r2);
addButtonInfoToString("L2", pressedButtons, m_l2);
addButtonInfoToString("Start", pressedButtons, m_start);
addButtonInfoToString("Select", pressedButtons, m_select);
addButtonInfoToString("R3", pressedButtons, m_r3);
addButtonInfoToString("L3", pressedButtons, m_l3);
const auto& [left_x, left_y] = m_leftAnalog;
const auto& [right_x, right_y] = m_rightAnalog;
const std::string analogs = fmt::format("Left: [{}, {}] | Right: [{}, {}]", left_x, left_y, right_x, right_y);
const std::string finalLog = fmt::format("[PAD {}:{}:{}]\n\t[Buttons]: {}\n\t[Analogs]: {}\n", m_ext_port, m_port, m_slot, pressedButtons, analogs);
controlLog(finalLog);
}
#endif

View File

@ -134,124 +134,57 @@ private:
class PadData
{
public:
/// Create a struct containing the PAD data from the global PAD state
/// see - `g_key_status`
PadData(const int port, const int slot);
PadData(const int port, const int slot, const std::array<u8, 18> data);
/// Constants
static constexpr u8 ANALOG_VECTOR_NEUTRAL = 127;
enum class BufferIndex
{
PressedFlagsGroupOne,
PressedFlagsGroupTwo,
RightAnalogXVector,
RightAnalogYVector,
LeftAnalogXVector,
LeftAnalogYVector,
RightPressure,
LeftPressure,
UpPressure,
DownPressure,
TrianglePressure,
CirclePressure,
CrossPressure,
SquarePressure,
L1Pressure,
R1Pressure,
L2Pressure,
R2Pressure
};
int m_ext_port;
int m_port;
int m_slot;
/// Analog Sticks - 0-255 (127 center)
u8 m_leftAnalogX = ANALOG_VECTOR_NEUTRAL;
u8 m_leftAnalogY = ANALOG_VECTOR_NEUTRAL;
u8 m_rightAnalogX = ANALOG_VECTOR_NEUTRAL;
u8 m_rightAnalogY = ANALOG_VECTOR_NEUTRAL;
// Analog Sticks - <x, y> - 0-255 (127 center)
std::tuple<u8, u8> m_rightAnalog = {ANALOG_VECTOR_NEUTRAL, ANALOG_VECTOR_NEUTRAL};
std::tuple<u8, u8> m_leftAnalog = {ANALOG_VECTOR_NEUTRAL, ANALOG_VECTOR_NEUTRAL};
/// Pressure Buttons - 0-255
u8 m_circlePressure = 0;
u8 m_crossPressure = 0;
u8 m_squarePressure = 0;
u8 m_trianglePressure = 0;
u8 m_downPressure = 0;
u8 m_leftPressure = 0;
u8 m_rightPressure = 0;
u8 m_upPressure = 0;
u8 m_l1Pressure = 0;
u8 m_l2Pressure = 0;
u8 m_r1Pressure = 0;
u8 m_r2Pressure = 0;
u8 m_compactPressFlagsGroupOne = 255;
u8 m_compactPressFlagsGroupTwo = 255;
u8* const m_allIntensities[16]{
&m_rightAnalogX,
&m_rightAnalogY,
&m_leftAnalogX,
&m_leftAnalogY,
&m_rightPressure,
&m_leftPressure,
&m_upPressure,
&m_downPressure,
&m_trianglePressure,
&m_circlePressure,
&m_crossPressure,
&m_squarePressure,
&m_l1Pressure,
&m_r1Pressure,
&m_l2Pressure,
&m_r2Pressure,
};
// Buttons <pressed, pressure (0-255)>
std::tuple<bool, u8> m_circle = {false, 0};
std::tuple<bool, u8> m_cross = {false, 0};
std::tuple<bool, u8> m_square = {false, 0};
std::tuple<bool, u8> m_triangle = {false, 0};
/// Pressure Button Flags
struct ButtonFlag
{
bool m_pressed = false;
const u8 m_BITMASK;
constexpr ButtonFlag(u8 maskValue)
: m_BITMASK(maskValue)
{
}
std::tuple<bool, u8> m_down = {false, 0};
std::tuple<bool, u8> m_left = {false, 0};
std::tuple<bool, u8> m_right = {false, 0};
std::tuple<bool, u8> m_up = {false, 0};
void setPressedState(u8 bufVal) noexcept
{
m_pressed = (~bufVal & m_BITMASK) > 0;
}
std::tuple<bool, u8> m_l1 = {false, 0};
std::tuple<bool, u8> m_l2 = {false, 0};
std::tuple<bool, u8> m_r1 = {false, 0};
std::tuple<bool, u8> m_r2 = {false, 0};
u8 getMaskIfPressed() const noexcept
{
return m_pressed ? m_BITMASK : 0;
}
};
/// NOTE - It shouldn't be possible to depress a button while also having no pressure
/// But for the sake of completeness, it should be tracked.
ButtonFlag m_circlePressed{0b00100000};
ButtonFlag m_crossPressed{0b01000000};
ButtonFlag m_squarePressed{0b10000000};
ButtonFlag m_trianglePressed{0b00010000};
ButtonFlag m_downPressed{0b01000000};
ButtonFlag m_leftPressed{0b10000000};
ButtonFlag m_rightPressed{0b00100000};
ButtonFlag m_upPressed{0b00010000};
ButtonFlag m_l1Pressed{0b00000100};
ButtonFlag m_l2Pressed{0b00000001};
ButtonFlag m_r1Pressed{0b00001000};
ButtonFlag m_r2Pressed{0b00000010};
// Buttons <pressed>
bool m_start = false;
bool m_select = false;
bool m_l3 = false;
bool m_r3 = false;
/// Normal (un)pressed buttons
ButtonFlag m_select{0b00000001};
ButtonFlag m_start{0b00001000};
ButtonFlag m_l3{0b00000010};
ButtonFlag m_r3{0b00000100};
// Overrides the actual controller's state with the the values in this struct
void OverrideActualController() const;
// Given the input buffer and the current index, updates the correct field(s)
void UpdateControllerData(u16 bufIndex, u8 const bufVal) noexcept;
u8 PollControllerData(u16 bufIndex) const noexcept;
// Prints current PadData to the Controller Log filter which disabled by default
void LogPadData(u8 const& port);
// Prints current PadData to the Controller Log filter which is disabled by default
void LogPadData() const;
private:
#ifndef PCSX2_CORE
wxString RawPadBytesToString(int start, int end);
#else
std::string RawPadBytesToString(int start, int end);
#endif
};

View File

@ -421,18 +421,6 @@ u8 Sio0::Memcard(u8 value)
// SIO2
// ============================================================================
void Sio2::UpdateInputRecording(u8& dataIn, u8& dataOut)
{
if (EmuConfig.EnableRecordingTools && g_InputRecording.isActive())
{
// Ignore multitapped slots, only allow physical ports
if (slot == 0)
{
g_InputRecording.controllerInterrupt(port, fifoOut.size(), dataIn, dataOut);
}
}
}
Sio2::Sio2()
{
@ -542,7 +530,6 @@ void Sio2::Pad()
// Send PAD our current port, and get back whatever it says the first response byte should be.
u8 commandByte = 0x01;
u8 firstResponseByte = PADstartPoll(port, slot);
UpdateInputRecording(commandByte, firstResponseByte);
fifoOut.push_back(firstResponseByte);
// Some games will refuse to read ALL pads, if RECV1 is not set to the CONNECTED value when ANY pad is polled,
// REGARDLESS of whether that pad is truly connected or not.
@ -554,7 +541,6 @@ void Sio2::Pad()
u8 commandByte = fifoIn.front();
fifoIn.pop_front();
u8 responseByte = PADpoll(commandByte);
UpdateInputRecording(commandByte, responseByte);
fifoOut.push_back(responseByte);
}
}

View File

@ -369,7 +369,7 @@ std::string VMManager::GetGameSettingsPath(const std::string_view& game_serial,
return game_serial.empty() ?
Path::Combine(EmuFolders::GameSettings, fmt::format("{:08X}.ini", game_crc)) :
Path::Combine(EmuFolders::GameSettings, fmt::format("{}_{:08X}.ini", sanitized_serial, game_crc));
Path::Combine(EmuFolders::GameSettings, fmt::format("{}_{:08X}.ini", sanitized_serial, game_crc));
}
std::string VMManager::GetInputProfilePath(const std::string_view& name)
@ -1129,7 +1129,7 @@ void VMManager::Reset()
if (g_InputRecording.isActive())
{
g_InputRecording.handleReset();
GSPresentCurrentFrame();
GetMTGS().PresentCurrentFrame();
}
}
@ -1188,7 +1188,7 @@ bool VMManager::DoLoadState(const char* filename)
if (g_InputRecording.isActive())
{
g_InputRecording.handleLoadingSavestate();
GSPresentCurrentFrame();
GetMTGS().PresentCurrentFrame();
}
return true;
}
@ -1557,6 +1557,23 @@ void VMManager::Internal::VSyncOnCPUThread()
}
Host::CPUThreadVSync();
if (EmuConfig.EnableRecordingTools)
{
// This code is called _before_ Counter's vsync end, and _after_ vsync start
if (g_InputRecording.isActive())
{
// Process any outstanding recording actions (ie. toggle mode, stop the recording, etc)
g_InputRecording.processRecordQueue();
g_InputRecording.getControls().processControlQueue();
// Increment our internal frame counter, used to keep track of when we hit the end, etc.
g_InputRecording.incFrameCounter();
g_InputRecording.handleExceededFrameCounter();
}
// At this point, the PAD data has been read from the user for the current frame
// so we can either read from it, or overwrite it!
g_InputRecording.handleControllerDataUpdate();
}
}
void VMManager::CheckForCPUConfigChanges(const Pcsx2Config& old_config)