Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CFLAGS := -Wall -Wextra -Werror -Os -ffunction-sections\

CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__

CXXFLAGS := $(CFLAGS) -std=c++20
CXXFLAGS := $(CFLAGS) -std=c++23

ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUMSSPECS)
Expand Down
3 changes: 2 additions & 1 deletion source/ButtonComboInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ButtonComboInfoIF {
void *context,
bool observer);
virtual ~ButtonComboInfoIF();
virtual void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) = 0;
// Note: return the index of the sample that activated the combo, or -1.
virtual int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) = 0;

[[nodiscard]] bool isObserver() const;

Expand Down
12 changes: 8 additions & 4 deletions source/ButtonComboInfoDown.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "ButtonComboInfoDown.h"

#include "logger.h"
#include <ranges>

ButtonComboInfoDown::ButtonComboInfoDown(
std::string label,
Expand All @@ -16,23 +17,24 @@ ButtonComboInfoDown::~ButtonComboInfoDown() {
DEBUG_FUNCTION_LINE_INFO("Deleted ButtonComboInfoDown: \"%s\", combo: %08X, controllerMask: %08X. Observer %d", mLabel.c_str(), mCombo, mControllerMask, mIsObserver);
}

void ButtonComboInfoDown::UpdateInput(
int ButtonComboInfoDown::UpdateInput(
const ButtonComboModule_ControllerTypes controller,
const std::span<uint32_t> pressedButtons) {
if ((mControllerMask & controller) == 0) {
return;
return -1;
}
const auto chanIndex = ControllerTypeToChanIndex(controller);
if (chanIndex < 0 || static_cast<uint32_t>(chanIndex) >= std::size(mHoldInformation)) {
DEBUG_FUNCTION_LINE_WARN("ChanIndex is out of bounds %d", chanIndex);
return;
return -1;
}

auto &[prevButtonCombo] = mHoldInformation[chanIndex];

DEBUG_FUNCTION_LINE_VERBOSE("[PRESS DOWN] Check button combo %08X on controller %08X (lastItem im pressedButtons (size %d) is %08X) for %s [%p]", mCombo, controller, pressedButtons.size(), pressedButtons.back(), mLabel.c_str(), getHandle().handle);

for (const auto &pressedButton : pressedButtons) {
int activatedIndex = -1;
for (auto [index, pressedButton] : std::views::enumerate(pressedButtons)) {
const bool prevButtonsIncludedCombo = (prevButtonCombo & mCombo) == mCombo; // Make sure the combo can't be triggered on releasing
const bool buttonsPressedChanged = prevButtonCombo != pressedButton; // Avoid "holding" the combo
const bool buttonsPressedMatchCombo = pressedButton == mCombo; // detect the actual combo
Expand All @@ -41,12 +43,14 @@ void ButtonComboInfoDown::UpdateInput(
if (mCallback != nullptr) {
DEBUG_FUNCTION_LINE("Calling callback [%p](controller: %08X, context: %p) for \"%s\" [handle: %p], pressed down %08X", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo);
mCallback(controller, getHandle(), mContext);
activatedIndex = index;
} else {
DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %p", getHandle().handle);
}
}
prevButtonCombo = pressedButton;
}
return activatedIndex;
}

ButtonComboModule_Error ButtonComboInfoDown::setHoldDuration(uint32_t) {
Expand Down
4 changes: 2 additions & 2 deletions source/ButtonComboInfoDown.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF {
uint32_t prevButtonCombo;
} HoldInformation;

void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) override;
int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) override;

ButtonComboModule_Error setHoldDuration(uint32_t uint32) override;

Expand All @@ -33,4 +33,4 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF {

private:
HoldInformation mHoldInformation[9] = {}; // one for each controller
};
};
11 changes: 7 additions & 4 deletions source/ButtonComboInfoHold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ ButtonComboInfoHold::~ButtonComboInfoHold() {
DEBUG_FUNCTION_LINE_INFO("Deleted ButtonComboInfoHold: \"%s\", combo: %08X, targetDurationInMs: %d ms, controllerMask: %08X", mLabel.c_str(), mCombo, mTargetDurationInMs, mControllerMask);
}

void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes controller, const std::span<uint32_t> pressedButtons) {
int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes controller, const std::span<uint32_t> pressedButtons) {
if ((mControllerMask & controller) == 0) {
return;
return -1;
}
const auto chanIndex = ControllerTypeToChanIndex(controller);
if (chanIndex < 0 || static_cast<uint32_t>(chanIndex) >= std::size(mHoldInformation)) {
DEBUG_FUNCTION_LINE_WARN("ChanIndex is out of bounds %d", chanIndex);
return;
return -1;
}

int activatedIndex = -1;

auto &holdInformation = mHoldInformation[chanIndex];
const auto latestButtonPress = pressedButtons.back();

Expand All @@ -53,7 +55,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co
if (mCallback != nullptr) {
DEBUG_FUNCTION_LINE("Calling callback [%p](controller: %08X context: %p) for \"%s\" [handle: %p], hold %08X for %d ms", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo, intervalInMs);
mCallback(controller, getHandle(), mContext);

activatedIndex = pressedButtons.size() - 1;
} else {
DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %p", getHandle().handle);
}
Expand All @@ -63,6 +65,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co
holdInformation.callbackTriggered = false;
holdInformation.holdStartedAt = 0;
}
return activatedIndex;
}

ButtonComboModule_Error ButtonComboInfoHold::setHoldDuration(const uint32_t holdDurationInMs) {
Expand Down
2 changes: 1 addition & 1 deletion source/ButtonComboInfoHold.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ButtonComboInfoHold final : public ButtonComboInfoIF {
~ButtonComboInfoHold() override;

private:
void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) override;
int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons) override;

ButtonComboModule_Error setHoldDuration(uint32_t holdDurationInMs) override;

Expand Down
96 changes: 80 additions & 16 deletions source/ButtonComboManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <padscore/kpad.h>

#include <algorithm>
#include <ranges>
#include <span>
#include <vector>

Expand Down Expand Up @@ -395,12 +396,12 @@ ButtonComboModule_Error ButtonComboManager::GetButtonComboStatus(const ButtonCom
return BUTTON_COMBO_MODULE_ERROR_SUCCESS;
}

void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus *buffer, const uint32_t bufferSize, const VPADReadError *error) {
void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, std::span<VPADStatus> buffer, const VPADReadError *error) {
if (chan < VPAD_CHAN_0 || chan > VPAD_CHAN_1) {
DEBUG_FUNCTION_LINE_ERR("Invalid VPADChan");
return;
}
if (buffer == nullptr || !error || *error != VPAD_READ_SUCCESS) {
if (!buffer.data() || buffer.empty() || !error || *error != VPAD_READ_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Invalid buffer or error state");
return;
}
Expand All @@ -410,25 +411,43 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus *
return;
}

// When button proc mode is loose, only the most recent button state matters.
bool buttonsAreLoose = VPADGetButtonProcMode(chan) == 0;
int comboStatus = -1;

{
std::lock_guard lock(mMutex);
const auto controller = convert(chan);
uint32_t usedBufferSize = 1;
const auto controller = convert(chan);

// Fix games like TP HD
if (VPADGetButtonProcMode(chan) == 1) {
usedBufferSize = bufferSize;
mVPADButtonBuffer.resize(buttonsAreLoose ? 1 : buffer.size());
// The order of buffer samples is new -> old, but we want it to be in old -> new.
for (auto [dst, src] : std::views::zip(mVPADButtonBuffer | std::views::reverse,
buffer)) {
dst = remapVPADButtons(src.hold);
}

if (usedBufferSize > mVPADButtonBuffer.size()) {
mVPADButtonBuffer.resize(usedBufferSize);
}
comboStatus = UpdateInputsLocked(controller, mVPADButtonBuffer);
}

// Begin button suppression logic.
auto &suppressed = mVPADSuppressed[static_cast<unsigned>(chan)];

// the order of the "buffer" data is new -> old, but we want it to be in old -> new
for (uint32_t i = 0; i < usedBufferSize; i++) {
mVPADButtonBuffer[usedBufferSize - i - 1] = remapVPADButtons(buffer[i].hold);
// Check every buffer entry, from old to new.
for (int i = buffer.size() - 1; i >= 0; --i) {
auto& entry = buffer[i];
if ((buttonsAreLoose && comboStatus != -1) || i == comboStatus) {
// This is the entry that activated a combo, the triggers tell which buttons
// to suppress.
suppressed |= entry.trigger;
}
// Re-enable all buttons released.
suppressed &= ~entry.release;

UpdateInputsLocked(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize));
// Don't let the application see the suppressed buttons
entry.hold &= ~suppressed;
entry.trigger &= ~suppressed;
entry.release &= ~suppressed;
}
}

Expand All @@ -438,14 +457,20 @@ void ButtonComboManager::UpdateTVMenuBlocking() {
VPADSetTVMenuInvalid(VPAD_CHAN_1, block);
}

void ButtonComboManager::UpdateInputsLocked(const ButtonComboModule_ControllerTypes controller, const std::span<uint32_t> pressedButtons) {
int ButtonComboManager::UpdateInputsLocked(const ButtonComboModule_ControllerTypes controller, const std::span<uint32_t> pressedButtons) {
int when_triggered = -1;
std::lock_guard lock(mMutex);
mIsIterating++;
for (const auto &combo : mCombos) {
if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) {
continue;
}
combo->UpdateInput(controller, pressedButtons);
int idx = combo->UpdateInput(controller, pressedButtons);
if (idx != -1) {
if (when_triggered == -1 || idx < when_triggered) {
when_triggered = idx;
}
}
}
mIsIterating--;

Expand All @@ -459,6 +484,7 @@ void ButtonComboManager::UpdateInputsLocked(const ButtonComboModule_ControllerTy
// Update TV Menu blocking status once after all removals
UpdateTVMenuBlocking();
}
return when_triggered;
}

void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) {
Expand All @@ -471,6 +497,12 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data)
return;
}

unsigned ctrlIdx = static_cast<unsigned>(chan);
auto &coreBtnTracker = mWPADCoreBtns[ctrlIdx];
auto &extBtnTracker = mWPADExtBtns[ctrlIdx];

coreBtnTracker.update(data->buttons);

// Do not check for combos while the combo detection is active
if (mInButtonComboDetection) {
return;
Expand All @@ -490,18 +522,49 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data)
case WPAD_EXT_MPLUS_CLASSIC: {
const auto classic = reinterpret_cast<WPADStatusClassic *>(data);
pressedButtons = remapClassicButtons(classic->buttons);
extBtnTracker.update(classic->buttons);
break;
}
case WPAD_EXT_PRO_CONTROLLER: {
const auto proController = reinterpret_cast<WPADStatusProController *>(data);
pressedButtons = remapProButtons(proController->buttons);
extBtnTracker.update(proController->buttons);
break;
}
default:
return;
}

UpdateInputsLocked(controller, std::span(&pressedButtons, 1));
// Begin button suppression logic.
int comboStatus = UpdateInputsLocked(controller, std::span(&pressedButtons, 1));
if (comboStatus != -1) {
// A combo was activated, let's suppress all trigger buttons.
coreBtnTracker.blockTriggered();
extBtnTracker.blockTriggered();
}

// For both core and extensions:
// - Re-enable all buttons released.
// - Don't let the application see the suppressed buttons.

coreBtnTracker.unblockReleased();
coreBtnTracker.suppressButtons(data->buttons);
switch (data->extensionType) {
case WPAD_EXT_CLASSIC:
case WPAD_EXT_MPLUS_CLASSIC: {
extBtnTracker.unblockReleased();
const auto classic = reinterpret_cast<WPADStatusClassic *>(data);
extBtnTracker.suppressButtons(classic->buttons);
break;
}
case WPAD_EXT_PRO_CONTROLLER: {
extBtnTracker.unblockReleased();
const auto proController = reinterpret_cast<WPADStatusProController *>(data);
extBtnTracker.suppressButtons(proController->buttons);
break;
}
}
// Finish button suppression logic.
}

ButtonComboInfoIF *ButtonComboManager::GetComboInfoForHandle(const ButtonComboModule_ComboHandle handle) const {
Expand Down Expand Up @@ -675,6 +738,7 @@ ButtonComboModule_Error ButtonComboManager::DetectButtonCombo_Blocking(const But
ButtonComboModule_Error result = BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR;
while (true) {
uint32_t buttonsHold = 0;
[[maybe_unused]]
uint32_t buttonsHoldAbort = 0;
for (int i = 0; i < 2; i++) {
VPADReadError vpad_error = VPAD_READ_UNINITIALIZED;
Expand Down
13 changes: 10 additions & 3 deletions source/ButtonComboManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@
#include <padscore/wpad.h>
#include <vpad/input.h>

#include <array>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <vector>

#include <cstdint>
#include <forward_list>

#include "ButtonTracker.h"

class ButtonComboManager {
public:
ButtonComboManager();

static std::optional<std::shared_ptr<ButtonComboInfoIF>> CreateComboInfo(const ButtonComboModule_ComboOptions &options, ButtonComboModule_Error &err);

void UpdateInputVPAD(VPADChan chan, const VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error);
void UpdateInputVPAD(VPADChan chan, std::span<VPADStatus> buffer, const VPADReadError *error);
void UpdateTVMenuBlocking();

void UpdateInputWPAD(WPADChan chan, WPADStatus *data);
Expand Down Expand Up @@ -55,7 +58,7 @@ class ButtonComboManager {
private:
[[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const;

void UpdateInputsLocked(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons);
int UpdateInputsLocked(ButtonComboModule_ControllerTypes controller, std::span<uint32_t> pressedButtons);

ButtonComboModule_ComboStatus CheckComboStatus(const ButtonComboInfoIF &other);

Expand All @@ -66,4 +69,8 @@ class ButtonComboManager {
mutable std::recursive_mutex mMutex;
std::recursive_mutex mDetectButtonsMutex;
bool mInButtonComboDetection = false;
};

std::array<uint32_t, 2> mVPADSuppressed{};
std::array<ButtonTracker<uint16_t>, 7> mWPADCoreBtns;
std::array<ButtonTracker<uint32_t>, 7> mWPADExtBtns;
};
Loading