From 7383bc3f44ebe89126028350973a84009d29d3ba Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Sat, 26 Nov 2022 14:39:14 -0500 Subject: [PATCH] input-rec: remove integration from SIO/Counters, fix imgui updating --- pcsx2/Counters.cpp | 11 - pcsx2/PAD/Host/KeyStatus.h | 12 ++ pcsx2/Recording/InputRecording.cpp | 155 ++++++++------ pcsx2/Recording/InputRecording.h | 22 +- pcsx2/Recording/InputRecordingControls.cpp | 17 +- pcsx2/Recording/InputRecordingFile.cpp | 71 ++++--- pcsx2/Recording/InputRecordingFile.h | 4 +- pcsx2/Recording/PadData.cpp | 227 +++++++++++++-------- pcsx2/Recording/PadData.h | 137 ++++--------- pcsx2/Sio.cpp | 14 -- pcsx2/VMManager.cpp | 23 ++- 11 files changed, 358 insertions(+), 335 deletions(-) diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index cd0f10f6a7..93176086c3 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -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) diff --git a/pcsx2/PAD/Host/KeyStatus.h b/pcsx2/PAD/Host/KeyStatus.h index 5bc93e42c2..36547967ab 100644 --- a/pcsx2/PAD/Host/KeyStatus.h +++ b/pcsx2/PAD/Host/KeyStatus.h @@ -16,6 +16,7 @@ #pragma once #include "PAD/Host/PAD.h" +#include 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 left, const std::tuple 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 GetRawLeftAnalog(u32 pad) const { return {m_analog[pad].lx, m_analog[pad].ly}; } + __fi std::tuple GetRawRightAnalog(u32 pad) const { return {m_analog[pad].rx, m_analog[pad].ry}; } + u32 GetButtons(u32 pad); u8 GetPressure(u32 pad, u32 index); }; diff --git a/pcsx2/Recording/InputRecording.cpp b/pcsx2/Recording/InputRecording.cpp index 26b4d4b7d2..8e1b139c8d 100644 --- a/pcsx2/Recording/InputRecording.cpp +++ b/pcsx2/Recording/InputRecording.cpp @@ -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 -#include +#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 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::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 \ No newline at end of file diff --git a/pcsx2/Recording/InputRecording.h b/pcsx2/Recording/InputRecording.h index b988582167..5d011e6980 100644 --- a/pcsx2/Recording/InputRecording.h +++ b/pcsx2/Recording/InputRecording.h @@ -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 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> 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 diff --git a/pcsx2/Recording/InputRecordingControls.cpp b/pcsx2/Recording/InputRecordingControls.cpp index 5234f81f2c..28b619b6b5 100644 --- a/pcsx2/Recording/InputRecordingControls.cpp +++ b/pcsx2/Recording/InputRecordingControls.cpp @@ -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(); } } diff --git a/pcsx2/Recording/InputRecordingFile.cpp b/pcsx2/Recording/InputRecordingFile.cpp index 8fb3ca3948..6ae0630ef4 100644 --- a/pcsx2/Recording/InputRecordingFile.cpp +++ b/pcsx2/Recording/InputRecordingFile.cpp @@ -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 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 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 InputRecordingFile::bulkReadPadData(u32 frameStart, u32 fra return data; } - const size_t size = static_cast(frameEnd - frameStart); - data.reserve(size); - - std::array 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; } diff --git a/pcsx2/Recording/InputRecordingFile.h b/pcsx2/Recording/InputRecordingFile.h index 55e828cf8d..9065d760c3 100644 --- a/pcsx2/Recording/InputRecordingFile.h +++ b/pcsx2/Recording/InputRecordingFile.h @@ -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 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) diff --git a/pcsx2/Recording/PadData.cpp b/pcsx2/Recording/PadData.cpp index 01d2ea38f3..b8d8feadf1 100644 --- a/pcsx2/Recording/PadData.cpp +++ b/pcsx2/Recording/PadData.cpp @@ -208,104 +208,161 @@ void PadData::LogPadData(u8 const& port) #include -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(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 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 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(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(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(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 \ No newline at end of file diff --git a/pcsx2/Recording/PadData.h b/pcsx2/Recording/PadData.h index 3061d5afd4..af2caf5565 100644 --- a/pcsx2/Recording/PadData.h +++ b/pcsx2/Recording/PadData.h @@ -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 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 - - 0-255 (127 center) + std::tuple m_rightAnalog = {ANALOG_VECTOR_NEUTRAL, ANALOG_VECTOR_NEUTRAL}; + std::tuple 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 + std::tuple m_circle = {false, 0}; + std::tuple m_cross = {false, 0}; + std::tuple m_square = {false, 0}; + std::tuple 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 m_down = {false, 0}; + std::tuple m_left = {false, 0}; + std::tuple m_right = {false, 0}; + std::tuple m_up = {false, 0}; - void setPressedState(u8 bufVal) noexcept - { - m_pressed = (~bufVal & m_BITMASK) > 0; - } + std::tuple m_l1 = {false, 0}; + std::tuple m_l2 = {false, 0}; + std::tuple m_r1 = {false, 0}; + std::tuple 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 + 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 }; diff --git a/pcsx2/Sio.cpp b/pcsx2/Sio.cpp index 40a230078f..7e6e96bfff 100644 --- a/pcsx2/Sio.cpp +++ b/pcsx2/Sio.cpp @@ -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); } } diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 2dc0ff5b6b..1200072aa1 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -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)