From 5746aff0118da43f2fd05d32e13666e60001b35b Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 14 Jun 2023 17:10:46 +0200 Subject: [PATCH] Implement support for pairing a controller or GamePad --- source/InputUtils.h | 2 +- source/MenuUtils.cpp | 367 +++++++++++++++++++++++-------------------- source/PairUtils.cpp | 241 ++++++++++++++++++++++++++++ source/PairUtils.h | 45 ++++++ source/main.cpp | 6 +- 5 files changed, 487 insertions(+), 174 deletions(-) create mode 100644 source/PairUtils.cpp create mode 100644 source/PairUtils.h diff --git a/source/InputUtils.h b/source/InputUtils.h index 4e44544..a3c44c0 100644 --- a/source/InputUtils.h +++ b/source/InputUtils.h @@ -1,6 +1,6 @@ #pragma once #include - +#include class InputUtils { public: diff --git a/source/MenuUtils.cpp b/source/MenuUtils.cpp index 2d80a17..565764d 100644 --- a/source/MenuUtils.cpp +++ b/source/MenuUtils.cpp @@ -2,24 +2,21 @@ #include "ACTAccountInfo.h" #include "DrawUtils.h" #include "InputUtils.h" +#include "PairUtils.h" #include "icon_png.h" #include "logger.h" #include "utils.h" #include "version.h" #include -#include #include #include #include #include #include #include -#include -#include #include #include #include -#include #define AUTOBOOT_MODULE_VERSION "v0.1.3" @@ -59,6 +56,48 @@ void writeAutobootOption(std::string &configPath, int32_t autobootOption) { } } +void drawMenuScreen(const std::map &menu, uint32_t selectedIndex, uint32_t autobootIndex) { + DrawUtils::beginDraw(); + DrawUtils::clear(COLOR_BACKGROUND); + + // draw buttons + uint32_t index = 8 + 24 + 8 + 4; + for (uint32_t i = 0; i < menu.size(); i++) { + if (i == (uint32_t) selectedIndex) { + DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 4, COLOR_BORDER_HIGHLIGHTED); + } else { + DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 2, (i == (uint32_t) autobootIndex) ? COLOR_AUTOBOOT : COLOR_BORDER); + } + + std::string curName = std::next(menu.begin(), i)->second; + + DrawUtils::setFontSize(24); + DrawUtils::setFontColor((i == (uint32_t) autobootIndex) ? COLOR_AUTOBOOT : COLOR_TEXT); + DrawUtils::print(16 * 2, index + 8 + 24, curName.c_str()); + index += 42 + 8; + } + + DrawUtils::setFontColor(COLOR_TEXT); + + // draw top bar + DrawUtils::setFontSize(24); + DrawUtils::drawPNG(16, 2, icon_png); + DrawUtils::print(64 + 2, 6 + 24, "Boot Selector"); + DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); + DrawUtils::setFontSize(16); + DrawUtils::print(SCREEN_WIDTH - 16, 6 + 24, AUTOBOOT_MODULE_VERSION AUTOBOOT_MODULE_VERSION_EXTRA, true); + + // draw bottom bar + DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); + DrawUtils::setFontSize(18); + DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate "); + DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true); + const char *autobootHints = "\ue002/\ue046 Clear Autoboot / \ue003/\ue045 Select Autoboot"; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true); + + DrawUtils::endDraw(); +} + int32_t handleMenuScreen(std::string &configPath, int32_t autobootOptionInput, const std::map &menu) { auto screenBuffer = DrawUtils::InitOSScreen(); if (!screenBuffer) { @@ -96,80 +135,41 @@ int32_t handleMenuScreen(std::string &configPath, int32_t autobootOptionInput, c } } - bool redraw = true; - while (true) { - InputUtils::InputData buttons = InputUtils::getControllerInput(); + { + PairMenu pairMenu; - if (buttons.trigger & VPAD_BUTTON_UP) { - selectedIndex--; - - if (selectedIndex < 0) { - selectedIndex = 0; + while (true) { + if (pairMenu.ProcessPairScreen()) { + continue; } - redraw = true; - } else if (buttons.trigger & VPAD_BUTTON_DOWN) { - if (!menu.empty()) { - selectedIndex++; + InputUtils::InputData input = InputUtils::getControllerInput(); + if (input.trigger & VPAD_BUTTON_UP) { + selectedIndex--; - if ((uint32_t) selectedIndex >= menu.size()) { - selectedIndex = menu.size() - 1; - } - redraw = true; - } - } else if (buttons.trigger & VPAD_BUTTON_A) { - break; - } else if (buttons.trigger & VPAD_BUTTON_X) { - autobootIndex = -1; - redraw = true; - } else if (buttons.trigger & VPAD_BUTTON_Y) { - autobootIndex = selectedIndex; - redraw = true; - } - - if (redraw) { - DrawUtils::beginDraw(); - DrawUtils::clear(COLOR_BACKGROUND); - - // draw buttons - uint32_t index = 8 + 24 + 8 + 4; - for (uint32_t i = 0; i < menu.size(); i++) { - if (i == (uint32_t) selectedIndex) { - DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 4, COLOR_BORDER_HIGHLIGHTED); - } else { - DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 2, (i == (uint32_t) autobootIndex) ? COLOR_AUTOBOOT : COLOR_BORDER); + if (selectedIndex < 0) { + selectedIndex = 0; } - std::string curName = std::next(menu.begin(), i)->second; + } else if (input.trigger & VPAD_BUTTON_DOWN) { + if (!menu.empty()) { + selectedIndex++; - DrawUtils::setFontSize(24); - DrawUtils::setFontColor((i == (uint32_t) autobootIndex) ? COLOR_AUTOBOOT : COLOR_TEXT); - DrawUtils::print(16 * 2, index + 8 + 24, curName.c_str()); - index += 42 + 8; + if ((uint32_t) selectedIndex >= menu.size()) { + selectedIndex = menu.size() - 1; + } + } + } else if (input.trigger & VPAD_BUTTON_A) { + break; + } else if (input.trigger & (VPAD_BUTTON_X | VPAD_BUTTON_MINUS)) { + autobootIndex = -1; + } else if (input.trigger & (VPAD_BUTTON_Y | VPAD_BUTTON_PLUS)) { + autobootIndex = selectedIndex; } - DrawUtils::setFontColor(COLOR_TEXT); - // draw top bar - DrawUtils::setFontSize(24); - DrawUtils::drawPNG(16, 2, icon_png); - DrawUtils::print(64 + 2, 6 + 24, "Boot Selector"); - DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); - DrawUtils::setFontSize(16); - DrawUtils::print(SCREEN_WIDTH - 16, 6 + 24, AUTOBOOT_MODULE_VERSION AUTOBOOT_MODULE_VERSION_EXTRA, true); - - // draw bottom bar - DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); - DrawUtils::setFontSize(18); - DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate "); - DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true); - const char *autobootHints = "\ue002 Clear Autoboot / \ue003 Select Autoboot"; - DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true); - - DrawUtils::endDraw(); - - redraw = false; + drawMenuScreen(menu, selectedIndex, autobootIndex); } } @@ -216,26 +216,27 @@ nn::act::SlotNo handleAccountSelectScreen(const std::vector 0) { - selected--; - redraw = true; + { + PairMenu pairMenu; + while (true) { + if (pairMenu.ProcessPairScreen()) { + continue; } - } else if (buttons.trigger & VPAD_BUTTON_DOWN) { - if (selected < (int32_t) data.size() - 1) { - selected++; - redraw = true; - } - } else if (buttons.trigger & VPAD_BUTTON_A) { - break; - } - if (redraw) { + InputUtils::InputData input = InputUtils::getControllerInput(); + if (input.trigger & VPAD_BUTTON_UP) { + if (selected > 0) { + selected--; + } + } else if (input.trigger & VPAD_BUTTON_DOWN) { + if (selected < (int32_t) data.size() - 1) { + selected++; + } + } else if (input.trigger & VPAD_BUTTON_A) { + break; + } + + DrawUtils::beginDraw(); DrawUtils::clear(COLOR_BACKGROUND); @@ -306,8 +307,6 @@ nn::act::SlotNo handleAccountSelectScreen(const std::vector +#include +#include +#include +#include +#include +#include + +void PairMenu::drawPairKPADScreen() const { + DrawUtils::beginDraw(); + DrawUtils::clear(COLOR_BACKGROUND); + + DrawUtils::setFontColor(COLOR_TEXT); + + DrawUtils::setFontSize(26); + + std::string textLine1 = "Press the SYNC Button on the controller you want to pair."; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine1.c_str()) / 2, 40, textLine1.c_str(), true); + + + WPADExtensionType ext{}; + for (int i = 0; i < 4; i++) { + bool isConnected = WPADProbe((WPADChan) i, &ext) == 0; + std::string textLine = string_format("Slot %d: ", i + 1); + if (isConnected) { + textLine += ext == WPAD_EXT_PRO_CONTROLLER ? "Pro Controller" : "Wiimote"; + } else { + textLine += "No controller"; + } + + DrawUtils::print(300, 140 + (i * 30), textLine.c_str()); + } + + DrawUtils::setFontSize(26); + + std::string gamepadSyncText1 = "If you are pairing a Wii U GamePad, press the SYNC Button"; + std::string gamepadSyncText2 = "on your Wii U console one more time"; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(gamepadSyncText1.c_str()) / 2, SCREEN_HEIGHT - 100, gamepadSyncText1.c_str(), true); + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(gamepadSyncText2.c_str()) / 2, SCREEN_HEIGHT - 70, gamepadSyncText2.c_str(), true); + + DrawUtils::setFontSize(16); + + const char *exitHints = "Press \ue001 to return"; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHints) / 2, SCREEN_HEIGHT - 8, exitHints, true); + + DrawUtils::endDraw(); +} + +void PairMenu::drawPairScreen() const { + DrawUtils::beginDraw(); + DrawUtils::clear(COLOR_BACKGROUND); + + DrawUtils::setFontColor(COLOR_TEXT); + + // Convert the pin to symbols and set the text + static char pinSymbols[][4] = { + "\u2660", + "\u2665", + "\u2666", + "\u2663"}; + + uint32_t pincode = mGamePadPincode; + + std::string pin = std::string(pinSymbols[(pincode / 1000) % 10]) + + pinSymbols[(pincode / 100) % 10] + + pinSymbols[(pincode / 10) % 10] + + pinSymbols[pincode % 10]; + + std::string textLine1 = "Press the SYNC Button on the Wii U GamePad,"; + std::string textLine2 = "and enter the four symbols shown below."; + + DrawUtils::setFontSize(26); + + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine1.c_str()) / 2, 60, textLine1.c_str(), true); + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine2.c_str()) / 2, 100, textLine2.c_str(), true); + + DrawUtils::setFontSize(100); + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(pin.c_str()) / 2, (SCREEN_HEIGHT / 2) + 40, pin.c_str(), true); + + DrawUtils::setFontSize(20); + + std::string textLine3 = string_format("(%d seconds remaining) ", mGamePadSyncTimeout - (uint32_t) (OSTicksToSeconds(OSGetTime() - mSyncGamePadStartTime))); + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine3.c_str()) / 2, SCREEN_HEIGHT - 80, textLine3.c_str(), true); + + DrawUtils::setFontSize(26); + + std::string textLine4 = "Press the SYNC Button on the Wii U console to exit."; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine4.c_str()) / 2, SCREEN_HEIGHT - 40, textLine4.c_str(), true); + + DrawUtils::endDraw(); +} + +PairMenu::PairMenu() { + CCRSysInit(); + + mState = STATE_WAIT; + mGamePadSyncTimeout = 120; + + // Initialize IM + mIMHandle = IM_Open(); + if (mIMHandle < 0) { + DEBUG_FUNCTION_LINE_ERR("PairMenu: IM_Open failed"); + OSFatal("PairMenu: IM_Open failed"); + } + mIMRequest = (IMRequest *) memalign(0x40, sizeof(IMRequest)); + + // Allocate a separate request for IM_CancelGetEventNotify to avoid conflict with the pending IM_GetEventNotify request + mIMCancelRequest = (IMRequest *) memalign(0x40, sizeof(IMRequest)); + + if (!mIMRequest || !mIMCancelRequest) { + DEBUG_FUNCTION_LINE_ERR("Failed to allocate im request"); + OSFatal("PairMenu: Failed to allocate im request"); + } + + mIMEventMask = IM_EVENT_SYNC; + + // Notify about sync button events + IM_GetEventNotify(mIMHandle, mIMRequest, &mIMEventMask, PairMenu::SyncButtonCallback, this); +} + +PairMenu::~PairMenu() { + // Close IM + IM_CancelGetEventNotify(mIMHandle, mIMCancelRequest, nullptr, nullptr); + IM_Close(mIMHandle); + if (mIMCancelRequest) { + free(mIMCancelRequest); + mIMCancelRequest = {}; + } + if (mIMRequest) { + free(mIMRequest); + mIMRequest = {}; + } + + // Deinit CCRSys + CCRSysExit(); +} + +extern "C" bool WPADStartSyncDevice(); + +bool PairMenu::ProcessPairScreen() { + switch (mState) { + case STATE_SYNC_WPAD: { + // WPAD syncing stops after ~18 seconds, make sure to restart it. + if ((uint32_t) OSTicksToSeconds(OSGetTime() - mSyncWPADStartTime) >= 18) { + WPADStartSyncDevice(); + mSyncWPADStartTime = OSGetTime(); + } + + InputUtils::InputData input = InputUtils::getControllerInput(); + + // Stop syncing when pressing A or B. + if (input.trigger & (VPAD_BUTTON_A | VPAD_BUTTON_B)) { + mState = STATE_WAIT; + } + + break; + } + case STATE_SYNC_GAMEPAD: { + if (CCRSysGetPincode(&mGamePadPincode) != 0) { + DEBUG_FUNCTION_LINE_ERR("CCRSysGetPincode failed"); + mState = STATE_WAIT; + break; + } + + // Start pairing to slot 1 (second gamepad) + if (CCRSysStartPairing(0, mGamePadSyncTimeout) != 0) { + DEBUG_FUNCTION_LINE_ERR("CCRSysStartPairing failed."); + mState = STATE_WAIT; + break; + } + + // Pairing has started, save start time + mSyncGamePadStartTime = OSGetTime(); + mState = STATE_PAIRING; + + DEBUG_FUNCTION_LINE("Started GamePad syncing."); + + break; + } + case STATE_PAIRING: { + // Get the current pairing state + CCRSysPairingState pairingState = CCRSysGetPairingState(); + if (pairingState == CCR_SYS_PAIRING_TIMED_OUT) { + DEBUG_FUNCTION_LINE("GamePad SYNC timed out."); + // Pairing has timed out or was cancelled + CCRSysStopPairing(); + mState = STATE_WAIT; + } else if (pairingState == CCR_SYS_PAIRING_FINISHED) { + DEBUG_FUNCTION_LINE("GamePad paired."); + mState = STATE_WAIT; + } + break; + } + case STATE_CANCEL: { + CCRSysStopPairing(); + mState = STATE_WAIT; + break; + } + case STATE_WAIT: + break; + } + switch (mState) { + case STATE_WAIT: { + return false; + } + case STATE_SYNC_WPAD: + drawPairKPADScreen(); + break; + case STATE_SYNC_GAMEPAD: + case STATE_PAIRING: + case STATE_CANCEL: { + drawPairScreen(); + break; + } + } + return true; +} + + +void PairMenu::SyncButtonCallback(IOSError error, void *arg) { + auto *pairMenu = (PairMenu *) arg; + + if (error == IOS_ERROR_OK && pairMenu && (pairMenu->mIMEventMask & IM_EVENT_SYNC)) { + if (pairMenu->mState == STATE_WAIT) { + pairMenu->mState = STATE_SYNC_WPAD; + // We need to restart the WPAD pairing every 18 seconds. For the timing we need to save the current time. + pairMenu->mSyncWPADStartTime = OSGetTime(); + } else if (pairMenu->mState == STATE_SYNC_WPAD) { + pairMenu->mState = STATE_SYNC_GAMEPAD; + } else if (pairMenu->mState == STATE_SYNC_GAMEPAD || pairMenu->mState == STATE_PAIRING) { + pairMenu->mState = STATE_CANCEL; + } + OSMemoryBarrier(); + IM_GetEventNotify(pairMenu->mIMHandle, pairMenu->mIMRequest, &pairMenu->mIMEventMask, PairMenu::SyncButtonCallback, pairMenu); + } +} diff --git a/source/PairUtils.h b/source/PairUtils.h new file mode 100644 index 0000000..9cf6a37 --- /dev/null +++ b/source/PairUtils.h @@ -0,0 +1,45 @@ +#pragma once + +#include "MenuUtils.h" +#include "logger.h" +#include +#include +#include +#include +#include +#include + + +class PairMenu { +public: + PairMenu(); + + ~PairMenu(); + + bool ProcessPairScreen(); + + static void SyncButtonCallback(IOSError error, void *arg); + + void drawPairScreen() const; + + void drawPairKPADScreen() const; + +private: + enum PairMenuState { + STATE_WAIT, // Wait for SYNC button press + STATE_SYNC_WPAD, + STATE_SYNC_GAMEPAD, + STATE_PAIRING, + STATE_CANCEL, + }; + + IOSHandle mIMHandle{}; + IMRequest *mIMRequest{}; + IMRequest *mIMCancelRequest{}; + OSTime mSyncWPADStartTime = 0; + OSTime mSyncGamePadStartTime = 0; + uint32_t mGamePadPincode = 0; + PairMenuState mState = STATE_WAIT; + uint32_t mGamePadSyncTimeout = 120; + IMEventMask mIMEventMask{}; +}; \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index bbb59ca..c9d03de 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -51,6 +50,8 @@ int32_t main(int32_t argc, char **argv) { OSFatal("AutobootModule: Mocha_InitLibrary failed"); } + InputUtils::InputData buttons = InputUtils::getControllerInput(); + FSAInit(); auto client = FSAAddClient(nullptr); if (client > 0) { @@ -70,8 +71,6 @@ int32_t main(int32_t argc, char **argv) { DEBUG_FUNCTION_LINE_ERR("Failed to create FSA Client"); } - InputUtils::InputData buttons = InputUtils::getControllerInput(); - bool showvHBL = getVWiiHBLTitleId() != 0; bool showHBL = false; std::string configPath = "fs:/vol/external01/wiiu/autoboot.cfg"; @@ -135,7 +134,6 @@ int32_t main(int32_t argc, char **argv) { } InputUtils::DeInit(); - Mocha_DeInitLibrary(); deinitLogging();