From 5510a74a9f17b9bbb8646bf5acdee800376130f7 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Mon, 10 Mar 2025 19:02:41 -0300 Subject: [PATCH 1/5] Implemented button suppression when a combo is activated. --- Makefile | 2 +- source/ButtonComboInfo.h | 3 +- source/ButtonComboInfoDown.cpp | 12 ++-- source/ButtonComboInfoDown.h | 4 +- source/ButtonComboInfoHold.cpp | 11 ++-- source/ButtonComboInfoHold.h | 2 +- source/ButtonComboManager.cpp | 115 ++++++++++++++++++++++++++------- source/ButtonComboManager.h | 11 +++- source/ButtonTracker.h | 39 +++++++++++ 9 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 source/ButtonTracker.h diff --git a/Makefile b/Makefile index 2168b7d..ff229bc 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ CFLAGS := -Wall -Wextra -O2 -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) diff --git a/source/ButtonComboInfo.h b/source/ButtonComboInfo.h index 192e9a9..5428861 100644 --- a/source/ButtonComboInfo.h +++ b/source/ButtonComboInfo.h @@ -17,7 +17,8 @@ class ButtonComboInfoIF { void *context, bool observer); virtual ~ButtonComboInfoIF(); - virtual void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) = 0; + // Note: return the index of the sample that activated the combo, or -1. + virtual int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) = 0; [[nodiscard]] bool isObserver() const; diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index 275b6e2..ff8fcd9 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -1,5 +1,6 @@ #include "ButtonComboInfoDown.h" +#include #include "logger.h" ButtonComboInfoDown::ButtonComboInfoDown( @@ -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 pressedButtons) { if ((mControllerMask & controller) == 0) { - return; + return -1; } const auto chanIndex = ControllerTypeToChanIndex(controller); if (chanIndex < 0 || static_cast(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 [%08X]", 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 @@ -41,12 +43,14 @@ void ButtonComboInfoDown::UpdateInput( if (mCallback != nullptr) { DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X, context: %08X) for \"%s\" [handle: %08X], 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 %08X", getHandle()); } } prevButtonCombo = pressedButton; } + return activatedIndex; } ButtonComboModule_Error ButtonComboInfoDown::setHoldDuration(uint32_t) { diff --git a/source/ButtonComboInfoDown.h b/source/ButtonComboInfoDown.h index b7f2b4e..1a4541b 100644 --- a/source/ButtonComboInfoDown.h +++ b/source/ButtonComboInfoDown.h @@ -23,7 +23,7 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF { uint32_t prevButtonCombo; } HoldInformation; - void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; + int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; ButtonComboModule_Error setHoldDuration(uint32_t uint32) override; @@ -33,4 +33,4 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF { private: HoldInformation mHoldInformation[9] = {}; // one for each controller -}; \ No newline at end of file +}; diff --git a/source/ButtonComboInfoHold.cpp b/source/ButtonComboInfoHold.cpp index 959fdb0..b7513e7 100644 --- a/source/ButtonComboInfoHold.cpp +++ b/source/ButtonComboInfoHold.cpp @@ -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 pressedButtons) { +int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes controller, const std::span pressedButtons) { if ((mControllerMask & controller) == 0) { - return; + return -1; } const auto chanIndex = ControllerTypeToChanIndex(controller); if (chanIndex < 0 || static_cast(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(); @@ -53,7 +55,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co if (mCallback != nullptr) { DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X context: %08X) for \"%s\" [handle: %08X], 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 %08X", getHandle()); } @@ -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) { diff --git a/source/ButtonComboInfoHold.h b/source/ButtonComboInfoHold.h index 3c7bdbb..ad843eb 100644 --- a/source/ButtonComboInfoHold.h +++ b/source/ButtonComboInfoHold.h @@ -25,7 +25,7 @@ class ButtonComboInfoHold final : public ButtonComboInfoIF { ~ButtonComboInfoHold() override; private: - void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; + int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; ButtonComboModule_Error setHoldDuration(uint32_t holdDurationInMs) override; diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index e451e71..deb827b 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -398,7 +398,7 @@ 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, VPADStatus *buffer, const uint32_t bufferSize, const VPADReadError *error) { if (chan < VPAD_CHAN_0 || chan > VPAD_CHAN_1) { DEBUG_FUNCTION_LINE_ERR("Invalid VPADChan"); return; @@ -435,9 +435,23 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus * if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { continue; } - combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + int activated = combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + if (activated >= 0) { + // suppress all buttons triggered + uint32_t triggered = buffer[usedBufferSize - activated - 1].trigger; + mVPADSuppressed[chan] |= triggered; + } } } + // hide all suppressed buttons from the game, iterate from oldest sample to newest + for (uint32_t i = bufferSize - 1; i + 1 > 0; --i) { + // released buttons stop being suppressed + mVPADSuppressed[chan] &= ~buffer[i].release; + // hide the suppressed buttons + buffer[i].trigger &= ~mVPADSuppressed[chan]; + buffer[i].hold &= ~mVPADSuppressed[chan]; + buffer[i].release &= ~mVPADSuppressed[chan]; + } } void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) { @@ -450,42 +464,99 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) return; } - // Do not check for combos while the combo detection is active - if (mInButtonComboDetection) { - return; - } + auto& coreBtns = mWPADCoreBtns[chan]; + auto& extBtns = mWPADCoreBtns[chan]; - const auto controller = convert(chan); - uint32_t pressedButtons = {}; + if (mWPADExtension[chan] != data->extensionType) { + mWPADExtension[chan] = data->extensionType; + extBtns.reset(); + } switch (data->extensionType) { case WPAD_EXT_CORE: case WPAD_EXT_NUNCHUK: case WPAD_EXT_MPLUS: - case WPAD_EXT_MPLUS_NUNCHUK: { - pressedButtons = remapWiiMoteButtons(data->buttons); + case WPAD_EXT_MPLUS_NUNCHUK: + coreBtns.update(data->buttons); + extBtns.reset(); break; - } case WPAD_EXT_CLASSIC: case WPAD_EXT_MPLUS_CLASSIC: { - const auto classic = reinterpret_cast(data); - pressedButtons = remapClassicButtons(classic->buttons); + auto cdata = reinterpret_cast(data); + coreBtns.update(cdata->core.buttons); + extBtns.update(cdata->buttons); break; } case WPAD_EXT_PRO_CONTROLLER: { - const auto proController = reinterpret_cast(data); - pressedButtons = remapProButtons(proController->buttons); + auto pdata = reinterpret_cast(data); + coreBtns.reset(); + extBtns.update(pdata->buttons); break; } - default: + default: // early out when we don't know how to handle extension return; } - { - std::lock_guard lock(mMutex); - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; + + // Do not check for combos while the combo detection is active + if (!mInButtonComboDetection) { + const auto controller = convert(chan); + uint32_t pressedButtons = {}; + switch (data->extensionType) { + case WPAD_EXT_CORE: + case WPAD_EXT_NUNCHUK: + case WPAD_EXT_MPLUS: + case WPAD_EXT_MPLUS_NUNCHUK: { + pressedButtons = remapWiiMoteButtons(data->buttons); + break; + } + case WPAD_EXT_CLASSIC: + case WPAD_EXT_MPLUS_CLASSIC: { + const auto classic = reinterpret_cast(data); + pressedButtons = remapClassicButtons(classic->buttons); + break; } - combo->UpdateInput(controller, std::span(&pressedButtons, 1)); + case WPAD_EXT_PRO_CONTROLLER: { + const auto proController = reinterpret_cast(data); + pressedButtons = remapProButtons(proController->buttons); + break; + } + } + { + std::lock_guard lock(mMutex); + for (const auto &combo : mCombos) { + if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { + continue; + } + int activated = combo->UpdateInput(controller, std::span(&pressedButtons, 1)); + if (activated >= 0) { + coreBtns.blockTriggered(); + extBtns.blockTriggered(); + } + } + } + } + + coreBtns.unblockReleased(); + extBtns.unblockReleased(); + + // modify data, to hide all suppressed buttons from the game + switch (data->extensionType) { + case WPAD_EXT_CORE: + case WPAD_EXT_NUNCHUK: + case WPAD_EXT_MPLUS: + case WPAD_EXT_MPLUS_NUNCHUK: + coreBtns.suppressButtons(data->buttons); + break; + case WPAD_EXT_CLASSIC: + case WPAD_EXT_MPLUS_CLASSIC: { + auto cdata = reinterpret_cast(data); + coreBtns.suppressButtons(cdata->core.buttons); + extBtns.suppressButtons(cdata->buttons); + break; + } + case WPAD_EXT_PRO_CONTROLLER: { + auto pdata = reinterpret_cast(data); + extBtns.suppressButtons(pdata->buttons); + break; } } } diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index afead3b..9fae9b7 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include "ButtonTracker.h" class ButtonComboManager { public: @@ -21,7 +23,7 @@ class ButtonComboManager { [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; - void UpdateInputVPAD(VPADChan chan, const VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); + void UpdateInputVPAD(VPADChan chan, VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); void UpdateInputWPAD(WPADChan chan, WPADStatus *data); @@ -61,4 +63,9 @@ class ButtonComboManager { std::mutex mMutex; std::mutex mDetectButtonsMutex; bool mInButtonComboDetection = false; -}; \ No newline at end of file + + std::array mVPADSuppressed{}; + std::array, 7> mWPADCoreBtns; + std::array, 7> mWPADExtBtns; + std::array mWPADExtension{}; +}; diff --git a/source/ButtonTracker.h b/source/ButtonTracker.h new file mode 100644 index 0000000..e0df2ce --- /dev/null +++ b/source/ButtonTracker.h @@ -0,0 +1,39 @@ +#pragma once + +// Simple class to track buttons triggered and released; also perform suppression logic. + +template +struct ButtonTracker { + void reset() noexcept { + hold = 0; + trigger = 0; + release = 0; + suppress = 0; + } + + void update(T buttons) noexcept { + T changed = buttons ^ hold; + hold = buttons; + trigger = changed & buttons; + release = changed & ~buttons; + } + + void blockTriggered() noexcept { + suppress |= trigger; + } + + void unblockReleased() noexcept { + suppress &= ~release; + } + + template + void suppressButtons(U& buttons) { + buttons &= ~suppress; + } + +private: + T hold = 0; + T trigger = 0; + T release = 0; + T suppress = 0; +}; From 276044b3250702312c941704f3fe01bac73136fc Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Mon, 10 Mar 2025 19:17:31 -0300 Subject: [PATCH 2/5] Fixed copy-paste accident. --- source/ButtonComboManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index deb827b..9695fdd 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -465,7 +465,7 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) } auto& coreBtns = mWPADCoreBtns[chan]; - auto& extBtns = mWPADCoreBtns[chan]; + auto& extBtns = mWPADExtBtns[chan]; if (mWPADExtension[chan] != data->extensionType) { mWPADExtension[chan] = data->extensionType; From 47ef57c53f4efdc986c5944ff18c37a23781101b Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Mon, 10 Mar 2025 19:26:22 -0300 Subject: [PATCH 3/5] Changes for clang-format. --- source/ButtonComboInfoDown.cpp | 2 +- source/ButtonComboManager.cpp | 6 +++--- source/ButtonTracker.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index ff8fcd9..53cdf4f 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -1,7 +1,7 @@ #include "ButtonComboInfoDown.h" -#include #include "logger.h" +#include ButtonComboInfoDown::ButtonComboInfoDown( std::string label, diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index 9695fdd..c3f9992 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -449,7 +449,7 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, VPADStatus *buffer mVPADSuppressed[chan] &= ~buffer[i].release; // hide the suppressed buttons buffer[i].trigger &= ~mVPADSuppressed[chan]; - buffer[i].hold &= ~mVPADSuppressed[chan]; + buffer[i].hold &= ~mVPADSuppressed[chan]; buffer[i].release &= ~mVPADSuppressed[chan]; } } @@ -464,8 +464,8 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) return; } - auto& coreBtns = mWPADCoreBtns[chan]; - auto& extBtns = mWPADExtBtns[chan]; + auto &coreBtns = mWPADCoreBtns[chan]; + auto &extBtns = mWPADExtBtns[chan]; if (mWPADExtension[chan] != data->extensionType) { mWPADExtension[chan] = data->extensionType; diff --git a/source/ButtonTracker.h b/source/ButtonTracker.h index e0df2ce..65971ec 100644 --- a/source/ButtonTracker.h +++ b/source/ButtonTracker.h @@ -14,7 +14,7 @@ struct ButtonTracker { void update(T buttons) noexcept { T changed = buttons ^ hold; hold = buttons; - trigger = changed & buttons; + trigger = changed & buttons; release = changed & ~buttons; } @@ -27,7 +27,7 @@ struct ButtonTracker { } template - void suppressButtons(U& buttons) { + void suppressButtons(U &buttons) { buttons &= ~suppress; } From e3b5e87fdebae6482b02984702056323e7a67c24 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Wed, 12 Mar 2025 10:58:04 -0300 Subject: [PATCH 4/5] Avoid suppressing buttons from observer combos. --- source/ButtonComboManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index c3f9992..264e6eb 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -436,7 +436,7 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, VPADStatus *buffer continue; } int activated = combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); - if (activated >= 0) { + if (activated >= 0 && !combo->isObserver()) { // suppress all buttons triggered uint32_t triggered = buffer[usedBufferSize - activated - 1].trigger; mVPADSuppressed[chan] |= triggered; @@ -527,7 +527,7 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) continue; } int activated = combo->UpdateInput(controller, std::span(&pressedButtons, 1)); - if (activated >= 0) { + if (activated >= 0 && !combo->isObserver()) { coreBtns.blockTriggered(); extBtns.blockTriggered(); } From 739df7fd9cbffc23b734af1f0b22638b91073441 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Sat, 9 May 2026 19:11:58 -0300 Subject: [PATCH 5/5] Rebased from main. --- .github/workflows/ci.yml | 4 +- .github/workflows/pr.yml | 6 +- .gitignore | 1 + Dockerfile | 10 +- Makefile | 2 +- README.md | 72 +++++++--- source/ButtonComboInfo.cpp | 18 ++- source/ButtonComboInfoDown.cpp | 6 +- source/ButtonComboInfoHold.cpp | 8 +- source/ButtonComboManager.cpp | 238 +++++++++++++++++---------------- source/ButtonComboManager.h | 21 +-- source/export.cpp | 67 ++++++---- source/function_patches.cpp | 16 ++- source/globals.cpp | 2 +- source/globals.h | 2 +- source/logger.h | 49 ++++--- source/main.cpp | 2 +- 17 files changed, 299 insertions(+), 225 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c6c8eb..d5931d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: clang-format: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: clang-format run: | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 needs: clang-format steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: create version.h run: | git_hash=$(git rev-parse --short "$GITHUB_SHA") diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1bc439b..0f60bc7 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,7 +6,7 @@ jobs: clang-format: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: clang-format run: | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-22.04 needs: clang-format steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: build binary with logging run: | docker build . -t builder @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-22.04 needs: clang-format steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: create version.h run: | git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}") diff --git a/.gitignore b/.gitignore index 98a796f..1217d41 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ cmake-build-debug/ CMakeLists.txt *.wms *.zip +docs/ diff --git a/Dockerfile b/Dockerfile index 523d7cd..e5ffbc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM ghcr.io/wiiu-env/devkitppc:20241128 +FROM ghcr.io/wiiu-env/devkitppc:20260225 -COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20250125-cb22627 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20241012 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20240424 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20260418 /artifacts $DEVKITPRO -WORKDIR project +WORKDIR /project diff --git a/Makefile b/Makefile index ff229bc..263b1a0 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ INCLUDES := source #------------------------------------------------------------------------------- # options for code generation #------------------------------------------------------------------------------- -CFLAGS := -Wall -Wextra -O2 -ffunction-sections\ +CFLAGS := -Wall -Wextra -Werror -Os -ffunction-sections\ $(MACHDEP) CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ diff --git a/README.md b/README.md index 6a3cd9c..2dc00a6 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,61 @@ +# ButtonComboModule + [![CI-Release](https://github.com/wiiu-env/ButtonComboModule/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/ButtonComboModule/actions/workflows/ci.yml) +**ButtonComboModule** is a Wii U Module System (WUMS) module that provides system-wide button combination detection. It +allows other homebrew applications and modules to easily register callbacks for specific button presses or holds across +various controllers (GamePad, Pro Controller, Wii Remote, etc.). + ## Usage -(`[ENVIRONMENT]` is a placeholder for the actual environment name.) -1. Copy the file `ButtonComboModule.wms` into `sd:/wiiu/environments/[ENVIRONMENT]/modules`. -2. Requires the [WUMSLoader](https://github.com/wiiu-env/WUMSLoader) in `sd:/wiiu/environments/[ENVIRONMENT]/modules/setup`. -3. Requires the [FunctionPatcherModule](https://github.com/wiiu-env/FunctionPatcherModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +(`[ENVIRONMENT]` is a placeholder for the actual environment name, e.g., `tiramisu` or `aroma`.) + +1. Copy the file `ButtonComboModule.wms` into `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +2. Requires the [WUMSLoader](https://github.com/wiiu-env/WUMSLoader) in + `sd:/wiiu/environments/[ENVIRONMENT]/modules/setup`. +3. Requires the [FunctionPatcherModule](https://github.com/wiiu-env/FunctionPatcherModule) in + `sd:/wiiu/environments/[ENVIRONMENT]/modules`. + +## Development + +### Homebrew Applications (.rpx / .wuhb) + +If you are developing a standard homebrew application and want to use system-wide button combos, you should use the +**libbuttoncombo** client library. + +* **Repository**: [wiiu-env/libbuttoncombo](https://github.com/wiiu-env/libbuttoncombo) + +### WUPS Plugins (.wps) + +If you are developing a plugin for the Wii U Plugin System (WUPS), you **should not** use `libbuttoncombo`. -## Buildflags +The [WiiUPluginSystem](https://github.com/wiiu-env/WiiUPluginSystem) (WUPS) library already provides built-in wrappers +for the ButtonComboModule. You can use the WUPS API directly to register combos without linking an external library. -### Logging -Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`. +## Building -`make` Logs errors only (via OSReport). -`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). -`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). +To build this module, you need **devkitPro** installed with `wut` and `wums`. You also need the `libfunctionpatcher` +libraries installed. -If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it'll fallback to UDP (Port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging. +### Build Flags (Logging) -## Building using the Dockerfile +Building via `make` only logs critical errors via OSReport by default. To enable verbose logging via +the [LoggingModule](https://github.com/wiiu-env/LoggingModule), set `DEBUG` to `1` or `VERBOSE`. -It's possible to use a docker image for building. This way you don't need anything installed on your host system. +* `make`: Logs errors only (via OSReport). +* `make DEBUG=1`: Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). +* `make DEBUG=VERBOSE`: Enables verbose information and error logging + via [LoggingModule](https://github.com/wiiu-env/LoggingModule). + +If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it will fallback to UDP (Port 4405) +and [USBSerialLoggingModule](https://github.com/wiiu-env/USBSerialLoggingModule) logging. + +### Building using Docker + +It is possible to use a Docker image for building. This way, you don't need anything installed on your host system. ``` -# Build docker image (only needed once) +# Build Docker image (only needed once) docker build . -t buttoncombomodule-builder # make @@ -33,6 +65,14 @@ docker run -it --rm -v ${PWD}:/project buttoncombomodule-builder make docker run -it --rm -v ${PWD}:/project buttoncombomodule-builder make clean ``` -## Format the code via docker +## Formatting + +You can format the code via Docker: + +``` +docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source -i +``` + +## License -`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source -i` \ No newline at end of file +This module is licensed under the **GPL-3.0**. \ No newline at end of file diff --git a/source/ButtonComboInfo.cpp b/source/ButtonComboInfo.cpp index 305582c..04f00f4 100644 --- a/source/ButtonComboInfo.cpp +++ b/source/ButtonComboInfo.cpp @@ -42,7 +42,7 @@ bool ButtonComboInfoIF::getMetaOptions(const ButtonComboModule_MetaOptionsOut &o void ButtonComboInfoIF::setMetaOptions(const ButtonComboModule_MetaOptions options) { mLabel = options.label; - DEBUG_FUNCTION_LINE("Updated label to: \"%s\", for %08X", mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Updated label to: \"%s\", for %p", mLabel.c_str(), getHandle().handle); } ButtonComboModule_CallbackOptions ButtonComboInfoIF::getCallbackOptions() const { @@ -52,7 +52,7 @@ ButtonComboModule_CallbackOptions ButtonComboInfoIF::getCallbackOptions() const void ButtonComboInfoIF::setCallbackOptions(const ButtonComboModule_CallbackOptions options) { mCallback = options.callback; mContext = options.context; - DEBUG_FUNCTION_LINE("Updated callback to: %08X(%08X), for %s %08X", mCallback, mContext, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Updated callback to: %p(%p), for %s handle: %p", mCallback, mContext, mLabel.c_str(), getHandle().handle); } uint32_t ButtonComboInfoIF::getCombo() const { @@ -60,7 +60,7 @@ uint32_t ButtonComboInfoIF::getCombo() const { } void ButtonComboInfoIF::setCombo(const ButtonComboModule_Buttons combo) { mCombo = combo; - DEBUG_FUNCTION_LINE("Updated combo to: %08X, for %s %08X", mCombo, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Updated combo to: %08X, for %s handle: %p", mCombo, mLabel.c_str(), getHandle().handle); resetPrevInput(); } @@ -70,7 +70,7 @@ ButtonComboModule_ComboStatus ButtonComboInfoIF::getStatus() const { void ButtonComboInfoIF::setStatus(const ButtonComboModule_ComboStatus status) { mStatus = status; - DEBUG_FUNCTION_LINE("Updated status to: %08X, for %s %08X", mStatus, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Updated status to: %08X, for %s handle: %p", mStatus, mLabel.c_str(), getHandle().handle); } ButtonComboModule_ControllerTypes ButtonComboInfoIF::getControllerMask() const { @@ -79,7 +79,7 @@ ButtonComboModule_ControllerTypes ButtonComboInfoIF::getControllerMask() const { void ButtonComboInfoIF::setControllerMask(const ButtonComboModule_ControllerTypes mask) { mControllerMask = mask; - DEBUG_FUNCTION_LINE("Updated controllerMask to: %08X, for %s %08X", mControllerMask, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Updated controllerMask to: %08X, for %s handle: %p", mControllerMask, mLabel.c_str(), getHandle().handle); resetPrevInput(); } @@ -97,6 +97,12 @@ bool ButtonComboInfoIF::conflictsWith(const ButtonComboModule_ButtonComboOptions if ((mControllerMask & other.controllerMask) == 0) { return false; } + + // No conflicts when either button combo is empty. + if (other.combo == 0 || mCombo == 0) { + return false; + } + if ((other.combo & mCombo) == mCombo || (other.combo & mCombo) == other.combo) { return true; } @@ -141,4 +147,4 @@ int32_t ButtonComboInfoIF::ControllerTypeToChanIndex(const ButtonComboModule_Con } return -1; -} \ No newline at end of file +} diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index 53cdf4f..2e3411a 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -31,7 +31,7 @@ int ButtonComboInfoDown::UpdateInput( 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 [%08X]", mCombo, controller, pressedButtons.size(), pressedButtons.back(), mLabel.c_str(), getHandle().handle); + 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); int activatedIndex = -1; for (auto [index, pressedButton] : std::views::enumerate(pressedButtons)) { @@ -41,11 +41,11 @@ int ButtonComboInfoDown::UpdateInput( if (buttonsPressedChanged && buttonsPressedMatchCombo && !prevButtonsIncludedCombo) { if (mCallback != nullptr) { - DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X, context: %08X) for \"%s\" [handle: %08X], pressed down %08X", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo); + 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 %08X", getHandle()); + DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %p", getHandle().handle); } } prevButtonCombo = pressedButton; diff --git a/source/ButtonComboInfoHold.cpp b/source/ButtonComboInfoHold.cpp index b7513e7..30facfb 100644 --- a/source/ButtonComboInfoHold.cpp +++ b/source/ButtonComboInfoHold.cpp @@ -37,7 +37,7 @@ int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes con auto &holdInformation = mHoldInformation[chanIndex]; const auto latestButtonPress = pressedButtons.back(); - DEBUG_FUNCTION_LINE_VERBOSE("[HOLD ] Check button combo %08X on controller %08X (lastItem im pressedButtons (size %d) is %08X) for %s [%08X]", mCombo, controller, pressedButtons.size(), latestButtonPress, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE_VERBOSE("[HOLD ] Check button combo %08X on controller %08X (lastItem im pressedButtons (size %d) is %08X) for %s [%p]", mCombo, controller, pressedButtons.size(), latestButtonPress, mLabel.c_str(), getHandle().handle); const bool prevButtonsIncludedCombo = (holdInformation.prevButtonCombo & mCombo) == mCombo; // Make sure the combo can't be triggered on releasing const bool buttonsPressedChanged = holdInformation.prevButtonCombo != latestButtonPress; // Avoid "holding" the combo @@ -53,11 +53,11 @@ int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes con if (intervalInMs > mTargetDurationInMs && !holdInformation.callbackTriggered) { if (mCallback != nullptr) { - DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X context: %08X) for \"%s\" [handle: %08X], hold %08X for %d ms", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo, intervalInMs); + 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 %08X", getHandle()); + DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %p", getHandle().handle); } holdInformation.callbackTriggered = true; } @@ -69,7 +69,7 @@ int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes con } ButtonComboModule_Error ButtonComboInfoHold::setHoldDuration(const uint32_t holdDurationInMs) { - DEBUG_FUNCTION_LINE("Setting holdDurationInMs to %d for %s [%08X]", holdDurationInMs, mLabel.c_str(), getHandle().handle); + DEBUG_FUNCTION_LINE("Setting holdDurationInMs to %d for %s [%p]", holdDurationInMs, mLabel.c_str(), getHandle().handle); mTargetDurationInMs = holdDurationInMs; return BUTTON_COMBO_MODULE_ERROR_SUCCESS; } diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index 264e6eb..80a45f2 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include namespace { @@ -288,11 +290,6 @@ std::optional> ButtonComboManager::CreateComb err = BUTTON_COMBO_MODULE_ERROR_INCOMPATIBLE_OPTIONS_VERSION; return std::nullopt; } - if (options.buttonComboOptions.basicCombo.combo == 0 || - options.buttonComboOptions.basicCombo.controllerMask == BUTTON_COMBO_MODULE_CONTROLLER_NONE) { - err = BUTTON_COMBO_MODULE_ERROR_INVALID_COMBO; - return std::nullopt; - } if (options.callbackOptions.callback == nullptr) { err = BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; return std::nullopt; @@ -302,7 +299,7 @@ std::optional> ButtonComboManager::CreateComb switch (options.buttonComboOptions.type) { case BUTTON_COMBO_MODULE_COMBO_TYPE_HOLD_OBSERVER: observer = true; - __attribute__((fallthrough)); + [[fallthrough]]; case BUTTON_COMBO_MODULE_COMBO_TYPE_HOLD: { if (options.buttonComboOptions.optionalHoldForXMs == 0) { err = BUTTON_COMBO_MODULE_ERROR_DURATION_MISSING; @@ -319,7 +316,7 @@ std::optional> ButtonComboManager::CreateComb } case BUTTON_COMBO_MODULE_COMBO_TYPE_PRESS_DOWN_OBSERVER: observer = true; - __attribute__((fallthrough)); + [[fallthrough]]; case BUTTON_COMBO_MODULE_COMBO_TYPE_PRESS_DOWN: { err = BUTTON_COMBO_MODULE_ERROR_SUCCESS; return std::make_shared(options.metaOptions.label, @@ -367,20 +364,21 @@ void ButtonComboManager::AddCombo(std::shared_ptr newComboInf outHandle = newComboInfo->getHandle(); mCombos.emplace_front(std::move(newComboInfo)); - const auto block = hasActiveComboWithTVButton(); - VPADSetTVMenuInvalid(VPAD_CHAN_0, block); - VPADSetTVMenuInvalid(VPAD_CHAN_1, block); + UpdateTVMenuBlocking(); } ButtonComboModule_Error ButtonComboManager::RemoveCombo(ButtonComboModule_ComboHandle handle) { std::lock_guard lock(mMutex); + + if (mIsIterating) { + mCombosToRemove.push_back(handle); + return BUTTON_COMBO_MODULE_ERROR_SUCCESS; + } + if (!remove_first_if(mCombos, [handle](const auto &combo) { return combo->getHandle() == handle; })) { - DEBUG_FUNCTION_LINE_WARN("Failed to remove combo by handle %08X", handle); + DEBUG_FUNCTION_LINE_WARN("Failed to remove combo by handle %p", handle.handle); } else { - const auto block = hasActiveComboWithTVButton(); - - VPADSetTVMenuInvalid(VPAD_CHAN_0, block); - VPADSetTVMenuInvalid(VPAD_CHAN_1, block); + UpdateTVMenuBlocking(); } return BUTTON_COMBO_MODULE_ERROR_SUCCESS; @@ -398,12 +396,12 @@ ButtonComboModule_Error ButtonComboManager::GetButtonComboStatus(const ButtonCom return BUTTON_COMBO_MODULE_ERROR_SUCCESS; } -void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, VPADStatus *buffer, const uint32_t bufferSize, const VPADReadError *error) { +void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, std::span 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; } @@ -413,45 +411,80 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, VPADStatus *buffer 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(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; - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; - } - int activated = combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); - if (activated >= 0 && !combo->isObserver()) { - // suppress all buttons triggered - uint32_t triggered = buffer[usedBufferSize - activated - 1].trigger; - mVPADSuppressed[chan] |= triggered; + // Don't let the application see the suppressed buttons + entry.hold &= ~suppressed; + entry.trigger &= ~suppressed; + entry.release &= ~suppressed; + } +} + +void ButtonComboManager::UpdateTVMenuBlocking() { + const auto block = hasActiveComboWithTVButton(); + VPADSetTVMenuInvalid(VPAD_CHAN_0, block); + VPADSetTVMenuInvalid(VPAD_CHAN_1, block); +} + +int ButtonComboManager::UpdateInputsLocked(const ButtonComboModule_ControllerTypes controller, const std::span 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; + } + int idx = combo->UpdateInput(controller, pressedButtons); + if (idx != -1) { + if (when_triggered == -1 || idx < when_triggered) { + when_triggered = idx; } } } - // hide all suppressed buttons from the game, iterate from oldest sample to newest - for (uint32_t i = bufferSize - 1; i + 1 > 0; --i) { - // released buttons stop being suppressed - mVPADSuppressed[chan] &= ~buffer[i].release; - // hide the suppressed buttons - buffer[i].trigger &= ~mVPADSuppressed[chan]; - buffer[i].hold &= ~mVPADSuppressed[chan]; - buffer[i].release &= ~mVPADSuppressed[chan]; + mIsIterating--; + + // Remove pending removals if existing + if (mIsIterating == 0 && !mCombosToRemove.empty()) { + for (auto handle : mCombosToRemove) { + remove_first_if(mCombos, [handle](const auto &combo) { return combo->getHandle() == handle; }); + } + mCombosToRemove.clear(); + + // Update TV Menu blocking status once after all removals + UpdateTVMenuBlocking(); } + return when_triggered; } void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) { @@ -464,104 +497,78 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) return; } - auto &coreBtns = mWPADCoreBtns[chan]; - auto &extBtns = mWPADExtBtns[chan]; + unsigned ctrlIdx = static_cast(chan); + auto &coreBtnTracker = mWPADCoreBtns[ctrlIdx]; + auto &extBtnTracker = mWPADExtBtns[ctrlIdx]; - if (mWPADExtension[chan] != data->extensionType) { - mWPADExtension[chan] = data->extensionType; - extBtns.reset(); + coreBtnTracker.update(data->buttons); + + // Do not check for combos while the combo detection is active + if (mInButtonComboDetection) { + return; } + + const auto controller = convert(chan); + uint32_t pressedButtons = {}; switch (data->extensionType) { case WPAD_EXT_CORE: case WPAD_EXT_NUNCHUK: case WPAD_EXT_MPLUS: - case WPAD_EXT_MPLUS_NUNCHUK: - coreBtns.update(data->buttons); - extBtns.reset(); + case WPAD_EXT_MPLUS_NUNCHUK: { + pressedButtons = remapWiiMoteButtons(data->buttons); break; + } case WPAD_EXT_CLASSIC: case WPAD_EXT_MPLUS_CLASSIC: { - auto cdata = reinterpret_cast(data); - coreBtns.update(cdata->core.buttons); - extBtns.update(cdata->buttons); + const auto classic = reinterpret_cast(data); + pressedButtons = remapClassicButtons(classic->buttons); + extBtnTracker.update(classic->buttons); break; } case WPAD_EXT_PRO_CONTROLLER: { - auto pdata = reinterpret_cast(data); - coreBtns.reset(); - extBtns.update(pdata->buttons); + const auto proController = reinterpret_cast(data); + pressedButtons = remapProButtons(proController->buttons); + extBtnTracker.update(proController->buttons); break; } - default: // early out when we don't know how to handle extension + default: return; } - // Do not check for combos while the combo detection is active - if (!mInButtonComboDetection) { - const auto controller = convert(chan); - uint32_t pressedButtons = {}; - switch (data->extensionType) { - case WPAD_EXT_CORE: - case WPAD_EXT_NUNCHUK: - case WPAD_EXT_MPLUS: - case WPAD_EXT_MPLUS_NUNCHUK: { - pressedButtons = remapWiiMoteButtons(data->buttons); - break; - } - case WPAD_EXT_CLASSIC: - case WPAD_EXT_MPLUS_CLASSIC: { - const auto classic = reinterpret_cast(data); - pressedButtons = remapClassicButtons(classic->buttons); - break; - } - case WPAD_EXT_PRO_CONTROLLER: { - const auto proController = reinterpret_cast(data); - pressedButtons = remapProButtons(proController->buttons); - break; - } - } - { - std::lock_guard lock(mMutex); - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; - } - int activated = combo->UpdateInput(controller, std::span(&pressedButtons, 1)); - if (activated >= 0 && !combo->isObserver()) { - coreBtns.blockTriggered(); - extBtns.blockTriggered(); - } - } - } + // 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(); } - coreBtns.unblockReleased(); - extBtns.unblockReleased(); + // For both core and extensions: + // - Re-enable all buttons released. + // - Don't let the application see the suppressed buttons. - // modify data, to hide all suppressed buttons from the game + coreBtnTracker.unblockReleased(); + coreBtnTracker.suppressButtons(data->buttons); switch (data->extensionType) { - case WPAD_EXT_CORE: - case WPAD_EXT_NUNCHUK: - case WPAD_EXT_MPLUS: - case WPAD_EXT_MPLUS_NUNCHUK: - coreBtns.suppressButtons(data->buttons); - break; case WPAD_EXT_CLASSIC: case WPAD_EXT_MPLUS_CLASSIC: { - auto cdata = reinterpret_cast(data); - coreBtns.suppressButtons(cdata->core.buttons); - extBtns.suppressButtons(cdata->buttons); + extBtnTracker.unblockReleased(); + const auto classic = reinterpret_cast(data); + extBtnTracker.suppressButtons(classic->buttons); break; } case WPAD_EXT_PRO_CONTROLLER: { - auto pdata = reinterpret_cast(data); - extBtns.suppressButtons(pdata->buttons); + extBtnTracker.unblockReleased(); + const auto proController = reinterpret_cast(data); + extBtnTracker.suppressButtons(proController->buttons); break; } } + // Finish button suppression logic. } ButtonComboInfoIF *ButtonComboManager::GetComboInfoForHandle(const ButtonComboModule_ComboHandle handle) const { + std::lock_guard lock(mMutex); for (const auto &combo : mCombos) { if (combo->getHandle() == handle) { return combo.get(); @@ -625,9 +632,7 @@ ButtonComboModule_Error ButtonComboManager::UpdateButtonCombo(const ButtonComboM comboInfo->setStatus(CheckComboStatus(*comboInfo)); outComboStatus = comboInfo->getStatus(); - const auto block = hasActiveComboWithTVButton(); - VPADSetTVMenuInvalid(VPAD_CHAN_0, block); - VPADSetTVMenuInvalid(VPAD_CHAN_1, block); + UpdateTVMenuBlocking(); return BUTTON_COMBO_MODULE_ERROR_SUCCESS; } @@ -674,7 +679,7 @@ ButtonComboModule_Error ButtonComboManager::GetButtonComboInfoEx(const ButtonCom std::lock_guard lock(mMutex); const auto *comboInfo = GetComboInfoForHandle(handle); if (!comboInfo) { - DEBUG_FUNCTION_LINE_ERR("ButtonComboModule_GetButtonComboInfo failed to get manager for handle %08X", handle); + DEBUG_FUNCTION_LINE_ERR("ButtonComboModule_GetButtonComboInfo failed to get manager for handle %p", handle.handle); return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } outOptions = comboInfo->getComboInfoEx(); @@ -701,7 +706,7 @@ ButtonComboModule_Error ButtonComboManager::DetectButtonCombo_Blocking(const But } if (options.holdComboForInMs == 0 || options.holdAbortForInMs == 0 || options.abortButtonCombo == 0) { - DEBUG_FUNCTION_LINE_WARN("Failed to detect button combo: Invalid params. holdComboFor: %s ms, holdAbortFor: %d ms, abortButtonCombo: %08X", options.holdComboForInMs, options.holdAbortForInMs, options.abortButtonCombo); + DEBUG_FUNCTION_LINE_WARN("Failed to detect button combo: Invalid params. holdComboFor: %d ms, holdAbortFor: %d ms, abortButtonCombo: %08X", options.holdComboForInMs, options.holdAbortForInMs, options.abortButtonCombo); return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } @@ -733,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; @@ -797,7 +803,7 @@ ButtonComboModule_Error ButtonComboManager::DetectButtonCombo_Blocking(const But } if (holdFor >= holdForTarget) { - DEBUG_FUNCTION_LINE_INFO("Detected button combo %08X", lastHold); + DEBUG_FUNCTION_LINE("Detected button combo %08X", lastHold); outButtonCombo = static_cast(lastHold); result = BUTTON_COMBO_MODULE_ERROR_SUCCESS; break; diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index 9fae9b7..27e936b 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -21,16 +22,13 @@ class ButtonComboManager { static std::optional> CreateComboInfo(const ButtonComboModule_ComboOptions &options, ButtonComboModule_Error &err); - [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; - - void UpdateInputVPAD(VPADChan chan, VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); + void UpdateInputVPAD(VPADChan chan, std::span buffer, const VPADReadError *error); + void UpdateTVMenuBlocking(); void UpdateInputWPAD(WPADChan chan, WPADStatus *data); bool hasActiveComboWithTVButton(); - ButtonComboModule_ComboStatus CheckComboStatus(const ButtonComboInfoIF &other); - void AddCombo(std::shared_ptr newComboInfo, ButtonComboModule_ComboHandle &outHandle, ButtonComboModule_ComboStatus &outStatus); ButtonComboModule_Error RemoveCombo(ButtonComboModule_ComboHandle handle); @@ -58,14 +56,21 @@ class ButtonComboManager { ButtonComboModule_Error DetectButtonCombo_Blocking(const ButtonComboModule_DetectButtonComboOptions &options, ButtonComboModule_Buttons &outButtonCombo); private: + [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; + + int UpdateInputsLocked(ButtonComboModule_ControllerTypes controller, std::span pressedButtons); + + ButtonComboModule_ComboStatus CheckComboStatus(const ButtonComboInfoIF &other); + std::forward_list> mCombos; + std::vector mCombosToRemove; + int mIsIterating = 0; std::vector mVPADButtonBuffer; - std::mutex mMutex; - std::mutex mDetectButtonsMutex; + mutable std::recursive_mutex mMutex; + std::recursive_mutex mDetectButtonsMutex; bool mInButtonComboDetection = false; std::array mVPADSuppressed{}; std::array, 7> mWPADCoreBtns; std::array, 7> mWPADExtBtns; - std::array mWPADExtension{}; }; diff --git a/source/export.cpp b/source/export.cpp index 24c1e20..9529859 100644 --- a/source/export.cpp +++ b/source/export.cpp @@ -13,7 +13,8 @@ ButtonComboModule_Error ButtonComboModule_AddButtonCombo(const ButtonComboModule return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } @@ -26,7 +27,7 @@ ButtonComboModule_Error ButtonComboModule_AddButtonCombo(const ButtonComboModule ButtonComboModule_ComboHandle handle; ButtonComboModule_ComboStatus tmpStatus = BUTTON_COMBO_MODULE_COMBO_STATUS_INVALID_STATUS; - gButtonComboManager->AddCombo(*comboInfoMaybe, handle, tmpStatus); + comboManager->AddCombo(*comboInfoMaybe, handle, tmpStatus); if (outStatus) { *outStatus = tmpStatus; } *outHandle = handle; @@ -38,12 +39,13 @@ ButtonComboModule_Error ButtonComboModule_RemoveButtonCombo(const ButtonComboMod return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->RemoveCombo(handle); + return comboManager->RemoveCombo(handle); } ButtonComboModule_Error ButtonComboModule_GetButtonComboStatus(const ButtonComboModule_ComboHandle handle, @@ -52,12 +54,13 @@ ButtonComboModule_Error ButtonComboModule_GetButtonComboStatus(const ButtonCombo return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->GetButtonComboStatus(handle, *outComboStatus); + return comboManager->GetButtonComboStatus(handle, *outComboStatus); } ButtonComboModule_Error ButtonComboModule_UpdateButtonComboMeta(const ButtonComboModule_ComboHandle handle, @@ -65,12 +68,14 @@ ButtonComboModule_Error ButtonComboModule_UpdateButtonComboMeta(const ButtonComb if (handle == nullptr || options == nullptr) { return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->UpdateButtonComboMeta(handle, *options); + return comboManager->UpdateButtonComboMeta(handle, *options); } ButtonComboModule_Error ButtonComboModule_UpdateButtonComboCallback(const ButtonComboModule_ComboHandle handle, @@ -78,12 +83,14 @@ ButtonComboModule_Error ButtonComboModule_UpdateButtonComboCallback(const Button if (handle == nullptr || options == nullptr) { return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->UpdateButtonComboCallback(handle, *options); + return comboManager->UpdateButtonComboCallback(handle, *options); } ButtonComboModule_Error ButtonComboModule_UpdateControllerMask(const ButtonComboModule_ComboHandle handle, @@ -93,13 +100,14 @@ ButtonComboModule_Error ButtonComboModule_UpdateControllerMask(const ButtonCombo return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } ButtonComboModule_ComboStatus tmpStatus = BUTTON_COMBO_MODULE_COMBO_STATUS_INVALID_STATUS; - auto res = gButtonComboManager->UpdateControllerMask(handle, controllerMask, tmpStatus); + auto res = comboManager->UpdateControllerMask(handle, controllerMask, tmpStatus); if (outComboStatus) { *outComboStatus = tmpStatus; } return res; } @@ -111,13 +119,14 @@ ButtonComboModule_Error ButtonComboModule_UpdateButtonCombo(const ButtonComboMod return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } ButtonComboModule_ComboStatus tmpStatus = BUTTON_COMBO_MODULE_COMBO_STATUS_INVALID_STATUS; - auto res = gButtonComboManager->UpdateButtonCombo(handle, combo, tmpStatus); + auto res = comboManager->UpdateButtonCombo(handle, combo, tmpStatus); if (outComboStatus) { *outComboStatus = tmpStatus; } return res; } @@ -128,12 +137,13 @@ ButtonComboModule_Error ButtonComboModule_UpdateHoldDuration(const ButtonComboMo return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->UpdateHoldDuration(handle, holdDurationInFrames); + return comboManager->UpdateHoldDuration(handle, holdDurationInFrames); } ButtonComboModule_Error ButtonComboModule_GetButtonComboMeta(const ButtonComboModule_ComboHandle handle, @@ -142,12 +152,13 @@ ButtonComboModule_Error ButtonComboModule_GetButtonComboMeta(const ButtonComboMo return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->GetButtonComboMeta(handle, *outOptions); + return comboManager->GetButtonComboMeta(handle, *outOptions); } ButtonComboModule_Error ButtonComboModule_GetButtonComboCallback(const ButtonComboModule_ComboHandle handle, @@ -156,12 +167,13 @@ ButtonComboModule_Error ButtonComboModule_GetButtonComboCallback(const ButtonCom return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->GetButtonComboCallback(handle, *outOptions); + return comboManager->GetButtonComboCallback(handle, *outOptions); } ButtonComboModule_Error ButtonComboModule_GetButtonComboInfoEx(const ButtonComboModule_ComboHandle handle, @@ -170,12 +182,13 @@ ButtonComboModule_Error ButtonComboModule_GetButtonComboInfoEx(const ButtonCombo return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->GetButtonComboInfoEx(handle, *outOptions); + return comboManager->GetButtonComboInfoEx(handle, *outOptions); } ButtonComboModule_Error ButtonComboModule_CheckComboAvailable(const ButtonComboModule_ButtonComboOptions *options, @@ -183,12 +196,13 @@ ButtonComboModule_Error ButtonComboModule_CheckComboAvailable(const ButtonComboM if (options == nullptr || outStatus == nullptr) { return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - *outStatus = gButtonComboManager->CheckComboAvailable(*options); + *outStatus = comboManager->CheckComboAvailable(*options); return BUTTON_COMBO_MODULE_ERROR_SUCCESS; } @@ -198,12 +212,13 @@ ButtonComboModule_Error ButtonComboModule_DetectButtonCombo_Blocking(const Butto return BUTTON_COMBO_MODULE_ERROR_INVALID_ARGUMENT; } - if (!gButtonComboManager) { + const auto comboManager = gButtonComboManager; + if (!comboManager) { DEBUG_FUNCTION_LINE_ERR("gButtonComboManager was nullptr"); return BUTTON_COMBO_MODULE_ERROR_UNKNOWN_ERROR; } - return gButtonComboManager->DetectButtonCombo_Blocking(*options, *outButtonCombo); + return comboManager->DetectButtonCombo_Blocking(*options, *outButtonCombo); } ButtonComboModule_Error ButtonComboModule_GetVersion(ButtonComboModule_APIVersion *outVersion) { diff --git a/source/function_patches.cpp b/source/function_patches.cpp index d890c72..c0835eb 100644 --- a/source/function_patches.cpp +++ b/source/function_patches.cpp @@ -11,10 +11,12 @@ DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buffer_size, VPADReadError *error) { VPADReadError real_error; const int32_t result = real_VPADRead(chan, buffer, buffer_size, &real_error); - if (result > 0 && real_error == VPAD_READ_SUCCESS && gButtonComboManager) { - gButtonComboManager->UpdateInputVPAD(chan, buffer, result > static_cast(buffer_size) ? buffer_size : result, error); - } + if (result > 0 && real_error == VPAD_READ_SUCCESS) { + if (const auto comboManager = gButtonComboManager; comboManager) { + comboManager->UpdateInputVPAD(chan, std::span(buffer, result), error); + } + } if (error) { *error = real_error; } @@ -24,8 +26,8 @@ DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buf DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatus *data) { real_WPADRead(chan, data); - if (gButtonComboManager) { - gButtonComboManager->UpdateInputWPAD(chan, data); + if (const auto comboManager = gButtonComboManager; comboManager) { + comboManager->UpdateInputWPAD(chan, data); } } struct WUT_PACKED CCRCDCCallbackData { @@ -38,8 +40,8 @@ DECL_FUNCTION(void, __VPADBASEAttachCallback, CCRCDCCallbackData *data, void *co real___VPADBASEAttachCallback(data, context); if (data && data->attached) { - if (gButtonComboManager) { - const bool block = gButtonComboManager->hasActiveComboWithTVButton(); + if (const auto comboManager = gButtonComboManager; comboManager) { + const bool block = comboManager->hasActiveComboWithTVButton(); VPADSetTVMenuInvalid(data->chan, block); } } diff --git a/source/globals.cpp b/source/globals.cpp index 0faee48..c88a785 100644 --- a/source/globals.cpp +++ b/source/globals.cpp @@ -1,4 +1,4 @@ #include "globals.h" #include "ButtonComboManager.h" -std::unique_ptr gButtonComboManager = {}; \ No newline at end of file +std::shared_ptr gButtonComboManager = {}; \ No newline at end of file diff --git a/source/globals.h b/source/globals.h index d94388d..0fb4b30 100644 --- a/source/globals.h +++ b/source/globals.h @@ -2,4 +2,4 @@ class ButtonComboManager; -extern std::unique_ptr gButtonComboManager; \ No newline at end of file +extern std::shared_ptr gButtonComboManager; \ No newline at end of file diff --git a/source/logger.h b/source/logger.h index d065dc2..2255709 100644 --- a/source/logger.h +++ b/source/logger.h @@ -8,19 +8,24 @@ extern "C" { #endif -#define LOG_APP_TYPE "M" -#define LOG_APP_NAME "buttoncombo_module" +#define LOG_APP_TYPE "M" +#define LOG_APP_NAME "buttoncombo_module" -#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) -#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) -#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", FMT, ##ARGS) +#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", "", FMT, ##ARGS) -#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS) +#define CONSOLE_COLOR_RED "\033[31m" +#define CONSOLE_COLOR_YELLOW "\033[33m" +#define CONSOLE_COLOR_CYAN "\033[36m" +#define CONSOLE_COLOR_RESET "\033[0m" -#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \ - do { \ - LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ +#define LOG_EX_DEFAULT(LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ##ARGS) + +#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ARGS...) \ + do { \ + LOG_FUNC(LOG_COLOR "[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ } while (0) #ifdef DEBUG @@ -28,22 +33,20 @@ extern "C" { #ifdef VERBOSE_DEBUG #define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "", "", FMT, ##ARGS); -#define DEBUG_FUNCTION_LINE_LOADER_VERBOSE(FMT, ARGS...) LOG_EX_DEFAULT(((void (*)(const char *, ...))((uint32_t *) 0x010028d0)), "", "\n", FMT, ##ARGS) #else -#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) -#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0) -#define DEBUG_FUNCTION_LINE_LOADER_VERBOSE(FMT, ARGS...) while (0) +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0) #endif #define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "## WARN## ", "", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "## INFO## ", "", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_RED, "## ERROR## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_YELLOW, "##WARN ## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_CYAN, "##INFO ## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS); +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET, FMT, ##ARGS); #else @@ -51,22 +54,18 @@ extern "C" { #define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) -#define DEBUG_FUNCTION_LINE_LOADER_VERBOSE(FMT, ARGS...) while (0) - #define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0) #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0) -#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "## WARN## ", "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "## INFO## ", "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_YELLOW, "##WARN ## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_CYAN, "##INFO ## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS); +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS); #endif -#define DEBUG_FUNCTION_LINE_LOADER_ERR(FMT, ARGS...) LOG_EX_DEFAULT(((void (*)(const char *, ...))((uint32_t *) 0x010028d0)), "##ERROR## ", "\n", FMT, ##ARGS) - void initLogging(); void deinitLogging(); diff --git a/source/main.cpp b/source/main.cpp index 74123f6..dca8138 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -14,7 +14,7 @@ WUMS_MODULE_EXPORT_NAME("homebrew_buttoncombo"); WUMS_MODULE_SKIP_INIT_FINI(); WUMS_DEPENDS_ON(homebrew_functionpatcher); -#define MODULE_VERSION "v0.1.0" +#define MODULE_VERSION "v0.2.1" WUMS_INITIALIZE() { initLogging();