mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(pad): expire a held injected button when its scripted press ends
A scripted button that answered a dialog stayed masked from the title until a title read sampled it released. When the title did not read during the release, a later scripted press of the same button was masked as well. The mask now keeps only the buttons the command timeline held without a break since the last read, so a press that ends, or a new command, clears it whether or not it was sampled. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c2a766ec22
commit
2e2f6b5eef
3 files changed
+76
-3
No files matched your search
@@ -60,6 +60,24 @@ public:
|
||||
|
||||
std::uint32_t Buttons(std::uint64_t now) { return Poll(now).buttons; }
|
||||
|
||||
std::uint32_t HeldThrough(std::uint64_t since, std::uint64_t now) const {
|
||||
if (events.empty() || since < started || now < since) return 0;
|
||||
const auto from = since - started;
|
||||
const auto to = now - started;
|
||||
auto event = std::upper_bound(events.begin(), events.end(), from,
|
||||
[](std::uint64_t time, const Event& candidate) { return time < candidate.start; });
|
||||
if (event == events.begin()) return 0;
|
||||
--event;
|
||||
std::uint32_t held = ~0u;
|
||||
for (std::uint64_t covered = from; event != events.end() && event->start <= covered; ++event) {
|
||||
if (covered >= event->end) return 0;
|
||||
held &= event->input.buttons;
|
||||
covered = event->end;
|
||||
if (to < covered) return held;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Event {
|
||||
std::uint64_t start = 0;
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace {
|
||||
constexpr std::uint32_t kInterceptedButton = 0x80000000;
|
||||
std::uint64_t injectedTimestamp = 0;
|
||||
std::uint32_t injectedHeldThroughCapture = 0;
|
||||
std::uint64_t injectedHeldAt = 0;
|
||||
|
||||
struct QueuedInput {
|
||||
PadInputState input;
|
||||
@@ -244,7 +245,7 @@ PadData BuildData(PadSlot& slot, const PadInputState& state, std::uint64_t chang
|
||||
return data;
|
||||
}
|
||||
|
||||
Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) {
|
||||
Pad::InputCommandFile& InjectedCommands() {
|
||||
static Pad::InputCommandFile commands([] {
|
||||
#ifdef _WIN32
|
||||
const auto* path = _wgetenv(L"APS5_PAD_INPUT_FILE");
|
||||
@@ -253,8 +254,12 @@ Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) {
|
||||
#endif
|
||||
return path ? std::filesystem::path(path) : std::filesystem::path{};
|
||||
}());
|
||||
return commands;
|
||||
}
|
||||
|
||||
Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) {
|
||||
static Pad::InputCommandFile::Input lastInjected;
|
||||
const auto injected = commands.Poll(now);
|
||||
const auto injected = InjectedCommands().Poll(now);
|
||||
if (injected != lastInjected) {
|
||||
lastInjected = injected;
|
||||
slots[0].timestamp = now;
|
||||
@@ -266,7 +271,8 @@ Pad::InputCommandFile::Input InjectedInput(std::uint64_t now) {
|
||||
Pad::InputCommandFile::Input InjectedTitleInput(std::uint64_t now) {
|
||||
auto injected = InjectedInput(now);
|
||||
if (slots[0].state.intercepted) injectedHeldThroughCapture |= injected.buttons;
|
||||
else injectedHeldThroughCapture &= injected.buttons;
|
||||
else injectedHeldThroughCapture &= InjectedCommands().HeldThrough(injectedHeldAt, now);
|
||||
injectedHeldAt = now;
|
||||
injected.buttons &= ~injectedHeldThroughCapture;
|
||||
return injected;
|
||||
}
|
||||
@@ -519,6 +525,7 @@ extern "C" void PadApplyInjectedInput_nid_postfix(PadInputState* state) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
const auto injected = InjectedInput(sceKernelGetProcessTime());
|
||||
injectedHeldThroughCapture |= injected.buttons;
|
||||
injectedHeldAt = sceKernelGetProcessTime();
|
||||
state->buttons |= injected.buttons;
|
||||
if (injected.hasSticks) state->sticks = injected.sticks;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include "prx/libScePad/include/PadState.hpp"
|
||||
#include "prx/libSceIme/include/ImeInput.hpp"
|
||||
#include "prx/libSceIme/include/ModalDialog.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
@@ -157,6 +159,50 @@ void InjectedAnswer(PadInput& padInput, const DisplayWindow& window, const std::
|
||||
ModalDialogSetPresenter_nid_postfix(false);
|
||||
}
|
||||
|
||||
std::uint32_t TitleButtons(bool useRead) {
|
||||
if (!useRead) return Pad::ReadState(0).buttons;
|
||||
std::array<PadData, 8> data{};
|
||||
const int count = Pad::Read(data.data(), static_cast<int>(data.size()), 0);
|
||||
Require(count > 0);
|
||||
return data[static_cast<std::size_t>(count - 1)].buttons;
|
||||
}
|
||||
|
||||
void InjectedPressAfterUnreadRelease(PadInput& padInput, const DisplayWindow& window, const std::filesystem::path& commandPath, std::uint64_t sequence, bool useRead) {
|
||||
constexpr std::uint32_t Cross = 0x4000;
|
||||
constexpr std::uint32_t Intercepted = 0x80000000;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
padInput.Update(window);
|
||||
Require(TitleButtons(useRead) == 0);
|
||||
ModalDialogSetPresenter_nid_postfix(true);
|
||||
ModalDialogRequest request;
|
||||
request.message = u"Create new data?";
|
||||
request.buttons = {u"OK"};
|
||||
Require(ModalDialogOpen_nid_postfix(request));
|
||||
padInput.Update(window);
|
||||
Require(TitleButtons(useRead) == Intercepted);
|
||||
std::ofstream(commandPath) << "APS5_PAD_TIMELINE 1 " << sequence << " 2\n0 400 16384\n1000 400 16384\n";
|
||||
const auto written = std::chrono::steady_clock::now();
|
||||
std::size_t chosen = 9;
|
||||
ModalDialogState state = ModalDialogState::Showing;
|
||||
for (int frame = 0; frame < 30 && state == ModalDialogState::Showing; ++frame) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
padInput.Update(window);
|
||||
state = ModalDialogPoll_nid_postfix(chosen);
|
||||
}
|
||||
Require(state == ModalDialogState::Answered && chosen == 0);
|
||||
padInput.Update(window);
|
||||
Require(TitleButtons(useRead) == 0);
|
||||
Require(std::chrono::steady_clock::now() - written < std::chrono::milliseconds(350));
|
||||
std::this_thread::sleep_until(written + std::chrono::milliseconds(1150));
|
||||
padInput.Update(window);
|
||||
Require(TitleButtons(useRead) == Cross);
|
||||
std::this_thread::sleep_until(written + std::chrono::milliseconds(1700));
|
||||
padInput.Update(window);
|
||||
Require(TitleButtons(useRead) == 0);
|
||||
ModalDialogReset_nid_postfix();
|
||||
ModalDialogSetPresenter_nid_postfix(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -255,6 +301,8 @@ int main() {
|
||||
Require(!padInput.SlotOccupied(0));
|
||||
Require(!padInput.SlotOccupied(1));
|
||||
InjectedAnswer(padInput, window, commandPath);
|
||||
InjectedPressAfterUnreadRelease(padInput, window, commandPath, 3, false);
|
||||
InjectedPressAfterUnreadRelease(padInput, window, commandPath, 4, true);
|
||||
|
||||
Require(SDL_JoystickDetachVirtual(dev1) == 0);
|
||||
Require(SDL_JoystickDetachVirtual(dev0) == 0);
|
||||
|
||||
Reference in new issue
Block a user