diff --git a/source/main.cpp b/source/main.cpp index 66f369b..dfa0aa7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -30,6 +30,8 @@ #include "kernel.h" #include "module/ModuleDataFactory.h" #include "utils/DrawUtils.h" +#include "utils/InputUtils.h" +#include "utils/PairUtils.h" // clang-format off #define MEMORY_REGION_START 0x00A00000 @@ -157,16 +159,11 @@ int main(int argc, char **argv) { DEBUG_FUNCTION_LINE_ERR("No config found"); } - VPADReadError err; - VPADStatus vpad_data; - VPADRead(VPAD_CHAN_0, &vpad_data, 1, &err); + InputUtils::Init(); - uint32_t btn = 0; - if (err == VPAD_READ_SUCCESS) { - btn = vpad_data.hold | vpad_data.trigger; - } + InputUtils::InputData input = InputUtils::getControllerInput(); - if (forceMenu || (btn & VPAD_BUTTON_X) == VPAD_BUTTON_X) { + if (forceMenu || ((input.trigger | input.hold) & VPAD_BUTTON_X) == VPAD_BUTTON_X) { DEBUG_FUNCTION_LINE_VERBOSE("Open menu!"); environment_path = EnvironmentSelectionScreen(environmentPaths, autobootIndex); if (environmentPaths.empty()) { @@ -175,6 +172,7 @@ int main(int argc, char **argv) { DEBUG_FUNCTION_LINE_VERBOSE("Selected %s", environment_path.c_str()); } } + InputUtils::DeInit(); } RevertMainHook(); @@ -258,16 +256,6 @@ int main(int argc, char **argv) { return 0; } -#define COLOR_WHITE Color(0xffffffff) -#define COLOR_BLACK Color(0, 0, 0, 255) -#define COLOR_RED Color(237, 28, 36, 255) -#define COLOR_BACKGROUND Color(0, 40, 100, 255) -#define COLOR_TEXT COLOR_WHITE -#define COLOR_TEXT2 Color(0xB3ffffff) -#define COLOR_AUTOBOOT Color(0xaeea00ff) -#define COLOR_BORDER Color(204, 204, 204, 255) -#define COLOR_BORDER_HIGHLIGHTED Color(0x3478e4ff) - std::string EnvironmentSelectionScreen(const std::map &payloads, int32_t autobootIndex) { OSScreenInit(); @@ -295,32 +283,32 @@ std::string EnvironmentSelectionScreen(const std::map uint32_t selected = autobootIndex > 0 ? autobootIndex : 0; int autoBoot = autobootIndex; - bool redraw = true; - while (true) { - VPADStatus vpad{}; - VPADRead(VPAD_CHAN_0, &vpad, 1, nullptr); - - if (vpad.trigger & VPAD_BUTTON_UP) { - if (selected > 0) { - selected--; - redraw = true; + { + PairMenu pairMenu; + while (true) { + if (pairMenu.ProcessPairScreen()) { + continue; } - } else if (vpad.trigger & VPAD_BUTTON_DOWN) { - if (selected < payloads.size() - 1) { - selected++; - redraw = true; - } - } else if (vpad.trigger & VPAD_BUTTON_A) { - break; - } else if (vpad.trigger & VPAD_BUTTON_X) { - autoBoot = -1; - redraw = true; - } else if (vpad.trigger & VPAD_BUTTON_Y) { - autoBoot = selected; - redraw = true; - } - 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 < payloads.size() - 1) { + selected++; + } + } else if (input.trigger & VPAD_BUTTON_A) { + break; + } else if (input.trigger & VPAD_BUTTON_X) { + autoBoot = -1; + } else if (input.trigger & VPAD_BUTTON_Y) { + autoBoot = selected; + } + + DrawUtils::beginDraw(); DrawUtils::clear(COLOR_BACKGROUND); @@ -368,8 +356,6 @@ std::string EnvironmentSelectionScreen(const std::map } DrawUtils::endDraw(); - - redraw = false; } } diff --git a/source/utils/DrawUtils.h b/source/utils/DrawUtils.h index 874fb4b..e5c9fa0 100644 --- a/source/utils/DrawUtils.h +++ b/source/utils/DrawUtils.h @@ -3,9 +3,19 @@ #include "schrift.h" #include +#define COLOR_WHITE Color(0xffffffff) +#define COLOR_BLACK Color(0, 0, 0, 255) +#define COLOR_RED Color(237, 28, 36, 255) +#define COLOR_BACKGROUND Color(0, 40, 100, 255) +#define COLOR_TEXT COLOR_WHITE +#define COLOR_TEXT2 Color(0xB3ffffff) +#define COLOR_AUTOBOOT Color(0xaeea00ff) +#define COLOR_BORDER Color(204, 204, 204, 255) +#define COLOR_BORDER_HIGHLIGHTED Color(0x3478e4ff) + // visible screen sizes -#define SCREEN_WIDTH 854 -#define SCREEN_HEIGHT 480 +#define SCREEN_WIDTH 854 +#define SCREEN_HEIGHT 480 union Color { explicit Color(uint32_t color) { diff --git a/source/utils/InputUtils.cpp b/source/utils/InputUtils.cpp new file mode 100644 index 0000000..d910ba1 --- /dev/null +++ b/source/utils/InputUtils.cpp @@ -0,0 +1,134 @@ +#include "InputUtils.h" +#include +#include +#include + +uint32_t remapWiiMoteButtons(uint32_t buttons) { + uint32_t convButtons = 0; + + if (buttons & WPAD_BUTTON_LEFT) + convButtons |= VPAD_BUTTON_LEFT; + + if (buttons & WPAD_BUTTON_RIGHT) + convButtons |= VPAD_BUTTON_RIGHT; + + if (buttons & WPAD_BUTTON_DOWN) + convButtons |= VPAD_BUTTON_DOWN; + + if (buttons & WPAD_BUTTON_UP) + convButtons |= VPAD_BUTTON_UP; + + if (buttons & WPAD_BUTTON_PLUS) + convButtons |= VPAD_BUTTON_PLUS; + + if (buttons & WPAD_BUTTON_2) + convButtons |= VPAD_BUTTON_Y; + + if (buttons & WPAD_BUTTON_1) + convButtons |= VPAD_BUTTON_X; + + if (buttons & WPAD_BUTTON_B) + convButtons |= VPAD_BUTTON_B; + + if (buttons & WPAD_BUTTON_A) + convButtons |= VPAD_BUTTON_A; + + if (buttons & WPAD_BUTTON_MINUS) + convButtons |= VPAD_BUTTON_MINUS; + + if (buttons & WPAD_BUTTON_HOME) + convButtons |= VPAD_BUTTON_HOME; + + return convButtons; +} + +uint32_t remapClassicButtons(uint32_t buttons) { + uint32_t convButtons = 0; + + if (buttons & WPAD_CLASSIC_BUTTON_LEFT) + convButtons |= VPAD_BUTTON_LEFT; + + if (buttons & WPAD_CLASSIC_BUTTON_RIGHT) + convButtons |= VPAD_BUTTON_RIGHT; + + if (buttons & WPAD_CLASSIC_BUTTON_DOWN) + convButtons |= VPAD_BUTTON_DOWN; + + if (buttons & WPAD_CLASSIC_BUTTON_UP) + convButtons |= VPAD_BUTTON_UP; + + if (buttons & WPAD_CLASSIC_BUTTON_PLUS) + convButtons |= VPAD_BUTTON_PLUS; + + if (buttons & WPAD_CLASSIC_BUTTON_X) + convButtons |= VPAD_BUTTON_X; + + if (buttons & WPAD_CLASSIC_BUTTON_Y) + convButtons |= VPAD_BUTTON_Y; + + if (buttons & WPAD_CLASSIC_BUTTON_B) + convButtons |= VPAD_BUTTON_B; + + if (buttons & WPAD_CLASSIC_BUTTON_A) + convButtons |= VPAD_BUTTON_A; + + if (buttons & WPAD_CLASSIC_BUTTON_MINUS) + convButtons |= VPAD_BUTTON_MINUS; + + if (buttons & WPAD_CLASSIC_BUTTON_HOME) + convButtons |= VPAD_BUTTON_HOME; + + if (buttons & WPAD_CLASSIC_BUTTON_ZR) + convButtons |= VPAD_BUTTON_ZR; + + if (buttons & WPAD_CLASSIC_BUTTON_ZL) + convButtons |= VPAD_BUTTON_ZL; + + if (buttons & WPAD_CLASSIC_BUTTON_R) + convButtons |= VPAD_BUTTON_R; + + if (buttons & WPAD_CLASSIC_BUTTON_L) + convButtons |= VPAD_BUTTON_L; + + return convButtons; +} + +InputUtils::InputData InputUtils::getControllerInput() { + InputData inputData{}; + VPADStatus vpadStatus{}; + VPADReadError vpadError = VPAD_READ_UNINITIALIZED; + if (VPADRead(VPAD_CHAN_0, &vpadStatus, 1, &vpadError) > 0 && vpadError == VPAD_READ_SUCCESS) { + inputData.trigger = vpadStatus.trigger; + inputData.hold = vpadStatus.hold; + inputData.release = vpadStatus.release; + } + + KPADStatus kpadStatus{}; + KPADError kpadError = KPAD_ERROR_UNINITIALIZED; + for (int32_t i = 0; i < 4; i++) { + if (KPADReadEx((KPADChan) i, &kpadStatus, 1, &kpadError) > 0) { + if (kpadError == KPAD_ERROR_OK && kpadStatus.extensionType != 0xFF) { + if (kpadStatus.extensionType == WPAD_EXT_CORE || kpadStatus.extensionType == WPAD_EXT_NUNCHUK) { + inputData.trigger |= remapWiiMoteButtons(kpadStatus.trigger); + inputData.hold |= remapWiiMoteButtons(kpadStatus.hold); + inputData.release |= remapWiiMoteButtons(kpadStatus.release); + } else { + inputData.trigger |= remapClassicButtons(kpadStatus.classic.trigger); + inputData.hold |= remapClassicButtons(kpadStatus.classic.hold); + inputData.release |= remapClassicButtons(kpadStatus.classic.release); + } + } + } + } + + return inputData; +} + +void InputUtils::Init() { + KPADInit(); + WPADEnableURCC(1); +} + +void InputUtils::DeInit() { + KPADShutdown(); +} \ No newline at end of file diff --git a/source/utils/InputUtils.h b/source/utils/InputUtils.h new file mode 100644 index 0000000..a3c44c0 --- /dev/null +++ b/source/utils/InputUtils.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +class InputUtils { +public: + typedef struct InputData { + uint32_t trigger = 0; + uint32_t hold = 0; + uint32_t release = 0; + } InputData; + + static void Init(); + static void DeInit(); + + static InputData getControllerInput(); +}; diff --git a/source/utils/PairUtils.cpp b/source/utils/PairUtils.cpp new file mode 100644 index 0000000..5741c79 --- /dev/null +++ b/source/utils/PairUtils.cpp @@ -0,0 +1,241 @@ +#include "PairUtils.h" +#include "DrawUtils.h" +#include "InputUtils.h" +#include "logger.h" +#include "utils.h" +#include +#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 0 + 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/utils/PairUtils.h b/source/utils/PairUtils.h new file mode 100644 index 0000000..e29af72 --- /dev/null +++ b/source/utils/PairUtils.h @@ -0,0 +1,43 @@ +#pragma once + +#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/utils/utils.h b/source/utils/utils.h index f334596..fc68d4f 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -15,3 +15,12 @@ template std::shared_ptr make_shared_nothrow(Args &&...args) noexcept(noexcept(T(std::forward(args)...))) { return std::shared_ptr(new (std::nothrow) T(std::forward(args)...)); } + +template +std::string string_format(const std::string &format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +}