From ff2491959da6fa40b9964e753314ea07c4ab3ec0 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sun, 11 Jan 2026 15:43:47 +0100 Subject: [PATCH 01/14] Fix compiling with latest devkitppc --- source/ButtonComboInfoDown.cpp | 2 +- source/ButtonComboInfoHold.cpp | 2 +- source/ButtonComboManager.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index 275b6e2..024fc64 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -42,7 +42,7 @@ void ButtonComboInfoDown::UpdateInput( 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); } 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 959fdb0..9c9d088 100644 --- a/source/ButtonComboInfoHold.cpp +++ b/source/ButtonComboInfoHold.cpp @@ -55,7 +55,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co mCallback(controller, getHandle(), mContext); } 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; } diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index e451e71..c043214 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -375,7 +375,7 @@ void ButtonComboManager::AddCombo(std::shared_ptr newComboInf ButtonComboModule_Error ButtonComboManager::RemoveCombo(ButtonComboModule_ComboHandle handle) { std::lock_guard lock(mMutex); 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(); @@ -603,7 +603,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(); @@ -630,7 +630,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; } From 90857a1651668739b0d638e7bb99f4bc8edcc61a Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 08:14:14 +0100 Subject: [PATCH 02/14] Update the README --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 16 deletions(-) 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 From 998f3676f67cfd0e42c57af6a6d1b9dea29223c7 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 08:19:55 +0100 Subject: [PATCH 03/14] Work on copies of the gButtonComboManager to avoid potential use after free --- source/export.cpp | 67 +++++++++++++++++++++++-------------- source/function_patches.cpp | 16 +++++---- source/globals.cpp | 2 +- source/globals.h | 2 +- 4 files changed, 52 insertions(+), 35 deletions(-) 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..89ed590 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, buffer, result > static_cast(buffer_size) ? buffer_size : 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 From 24790bddef74e7d2193e47e007497df6965187f1 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 08:21:36 +0100 Subject: [PATCH 04/14] Change from std::mutex to std::recursive_mutex, make mMutex mutable to use it in "GetComboForHandle" --- source/ButtonComboManager.cpp | 1 + source/ButtonComboManager.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index c043214..e768ef6 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -491,6 +491,7 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) } 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(); diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index afead3b..6ce1a92 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -58,7 +58,7 @@ class ButtonComboManager { private: std::forward_list> mCombos; std::vector mVPADButtonBuffer; - std::mutex mMutex; - std::mutex mDetectButtonsMutex; + mutable std::recursive_mutex mMutex; + std::recursive_mutex mDetectButtonsMutex; bool mInButtonComboDetection = false; }; \ No newline at end of file From 147aedbd8a3103d99022ec8b17945cd6eaeafa28 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 09:59:22 +0100 Subject: [PATCH 05/14] Move TVMenuBlocking checks into a helper function --- source/ButtonComboManager.cpp | 20 ++++++++++---------- source/ButtonComboManager.h | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index e768ef6..7b4966d 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace { @@ -367,9 +368,7 @@ 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) { @@ -377,10 +376,7 @@ ButtonComboModule_Error ButtonComboManager::RemoveCombo(ButtonComboModule_ComboH if (!remove_first_if(mCombos, [handle](const auto &combo) { return combo->getHandle() == 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; @@ -436,6 +432,12 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus * continue; } combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + +void ButtonComboManager::UpdateTVMenuBlocking() { + const auto block = hasActiveComboWithTVButton(); + VPADSetTVMenuInvalid(VPAD_CHAN_0, block); + VPADSetTVMenuInvalid(VPAD_CHAN_1, block); +} } } } @@ -555,9 +557,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; } diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index 6ce1a92..8a75206 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -22,6 +22,7 @@ class ButtonComboManager { [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; void UpdateInputVPAD(VPADChan chan, const VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); + void UpdateTVMenuBlocking(); void UpdateInputWPAD(WPADChan chan, WPADStatus *data); From 39d36e7b789a02e512a8a39070eec3aa8c9b7334 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 10:11:45 +0100 Subject: [PATCH 06/14] Make private used function actually private --- source/ButtonComboManager.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index 8a75206..5dddd8e 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -19,8 +19,6 @@ class ButtonComboManager { static std::optional> CreateComboInfo(const ButtonComboModule_ComboOptions &options, ButtonComboModule_Error &err); - [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; - void UpdateInputVPAD(VPADChan chan, const VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); void UpdateTVMenuBlocking(); @@ -28,8 +26,6 @@ class ButtonComboManager { 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); @@ -57,6 +53,10 @@ class ButtonComboManager { ButtonComboModule_Error DetectButtonCombo_Blocking(const ButtonComboModule_DetectButtonComboOptions &options, ButtonComboModule_Buttons &outButtonCombo); private: + [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; + + ButtonComboModule_ComboStatus CheckComboStatus(const ButtonComboInfoIF &other); + std::forward_list> mCombos; std::vector mVPADButtonBuffer; mutable std::recursive_mutex mMutex; From 25555ee7a15c826a1476263921d351ea7f3d4950 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 10:12:30 +0100 Subject: [PATCH 07/14] Avoid potential issues when trying to remove a button combo from callback --- source/ButtonComboManager.cpp | 45 ++++++++++++++++++++++++----------- source/ButtonComboManager.h | 4 ++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index 7b4966d..3952437 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -373,6 +373,12 @@ void ButtonComboManager::AddCombo(std::shared_ptr newComboInf 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 %p", handle.handle); } else { @@ -427,18 +433,36 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus * mVPADButtonBuffer[usedBufferSize - i - 1] = remapVPADButtons(buffer[i].hold); } - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; - } - combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + UpdateInputsLocked(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + } +} void ButtonComboManager::UpdateTVMenuBlocking() { const auto block = hasActiveComboWithTVButton(); VPADSetTVMenuInvalid(VPAD_CHAN_0, block); VPADSetTVMenuInvalid(VPAD_CHAN_1, block); } + +void ButtonComboManager::UpdateInputsLocked(const ButtonComboModule_ControllerTypes controller, const std::span pressedButtons) { + 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); + } + 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(); } } @@ -481,15 +505,8 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) default: return; } - { - std::lock_guard lock(mMutex); - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; - } - combo->UpdateInput(controller, std::span(&pressedButtons, 1)); - } - } + + UpdateInputsLocked(controller, std::span(&pressedButtons, 1)); } ButtonComboInfoIF *ButtonComboManager::GetComboInfoForHandle(const ButtonComboModule_ComboHandle handle) const { diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index 5dddd8e..65b3454 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -55,9 +55,13 @@ class ButtonComboManager { private: [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; + void 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; mutable std::recursive_mutex mMutex; std::recursive_mutex mDetectButtonsMutex; From 818d0fb39573dd8d29b14f3560a552cafbe6eb69 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 16:42:44 +0100 Subject: [PATCH 08/14] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 523d7cd..15e7022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM ghcr.io/wiiu-env/devkitppc:20241128 +FROM ghcr.io/wiiu-env/devkitppc:20250608 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 -WORKDIR project +WORKDIR /project From b02558769a65bd2d65c5eff402ac1117cc6ab731 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:13:46 +0000 Subject: [PATCH 09/14] Bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- .github/workflows/pr.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 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 }}") From efda84006eedb6d7145853ba775418a547dc02b9 Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 12 Jan 2026 16:42:44 +0100 Subject: [PATCH 10/14] Update Dockerfile --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 15e7022..2ad74eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM ghcr.io/wiiu-env/devkitppc:20250608 +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:20260112-e8efe9d /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260208 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20260225 /artifacts $DEVKITPRO WORKDIR /project From 2f44e4cdd68a1ea8c98cf38ee5329a24683a8dbf Mon Sep 17 00:00:00 2001 From: Maschell Date: Tue, 20 Jan 2026 18:16:34 +0100 Subject: [PATCH 11/14] Fix compiling with debug flag --- .gitignore | 1 + Makefile | 2 +- source/ButtonComboInfo.cpp | 10 +++++----- source/ButtonComboInfoDown.cpp | 4 ++-- source/ButtonComboInfoHold.cpp | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) 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/Makefile b/Makefile index 2168b7d..bdfc792 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/source/ButtonComboInfo.cpp b/source/ButtonComboInfo.cpp index 305582c..809aecd 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(); } diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index 024fc64..068bb1d 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -30,7 +30,7 @@ void 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); for (const auto &pressedButton : pressedButtons) { const bool prevButtonsIncludedCombo = (prevButtonCombo & mCombo) == mCombo; // Make sure the combo can't be triggered on releasing @@ -39,7 +39,7 @@ void 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); } else { DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %p", getHandle().handle); diff --git a/source/ButtonComboInfoHold.cpp b/source/ButtonComboInfoHold.cpp index 9c9d088..d656a47 100644 --- a/source/ButtonComboInfoHold.cpp +++ b/source/ButtonComboInfoHold.cpp @@ -35,7 +35,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co 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 @@ -51,7 +51,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co 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); } else { @@ -66,7 +66,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co } 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; } From bfff1fa7855033bc6a1745829553efdbd98ee897 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 18 Apr 2026 17:59:04 +0200 Subject: [PATCH 12/14] Update logging to include colors for warnings/errors --- source/logger.h | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) 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(); From 7a843a5fc5bcad0af4afaf234fc2f2cb18102705 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 18 Apr 2026 17:59:13 +0200 Subject: [PATCH 13/14] Update Dockerfile --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2ad74eb..e5ffbc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ghcr.io/wiiu-env/devkitppc:20260225 -COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20260112-e8efe9d /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260208 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20260225 /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 From 83e791708f785a1223d80e9f5f025a71b2fd1900 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 18 Apr 2026 17:59:22 +0200 Subject: [PATCH 14/14] Bump Version --- source/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/main.cpp b/source/main.cpp index 74123f6..dd95b31 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.0" WUMS_INITIALIZE() { initLogging();