libwupc - A WiiU Pro Controller Library for Wii Homebrew Applications

Discussion in 'Wii U - Hacking & Backup Loaders' started by FIX94, Sep 15, 2014.

  1. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    Hey!
    So I finally made a small library which you can implement into your wii homebrew application in order to use the WiiU Pro Controller, I've added a usage.txt file to it.
    https://github.com/FIX94/libwupc
    Just check out the source to get all the data you need.
    Let me know if you find any issues with it, so far I only tested it with one controller so I have no idea if it can even handle multiple controllers properly.
    This library is implemented for example into wiiflow, nintendont, postloader, fceugx, snes9xgx and vbagx (get those 3 from my signature).
    Have Fun!
     


  2. ShadowOne333

    ShadowOne333 GBAtemp Guru

    Member
    6,881
    4,367
    Jan 17, 2013
    Mexico
    Wow awesome!
    Now a lot of homebrew emus and apps will be able to handle the Wii U Pro controller.
    THANK YOU FIX! :D
     
    Margen67 likes this.
  3. pedro702

    pedro702 GBAtemp Guru

    Member
    6,357
    1,997
    Mar 3, 2014
    Portugal
    lets hope someone adds this to not64 and wiisx xD
     
    Margen67 likes this.
  4. Relys

    Relys Master of Computer Science

    Member
    863
    788
    Jan 5, 2007
    United States
    Nice release bro.
     
    Margen67 likes this.
  5. tmv_josue

    tmv_josue GBAtemp Fan

    Member
    390
    60
    May 18, 2010
    Mexico
    CancĂșn
    Good Job FIX :yayu:
     
    Margen67 likes this.
  6. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Thank you for the release. Time to make good use of this.

    I ran into a few issues:
    1. Maybe I'm missing something, but the analog stick "buttons" (e.g. the buttons similar to L3/R3 on PS3 controllers) don't seem to show up in the WUPCData struct's button member when pressed. Every other button does, however.

    2. The button values that the Pro Controller reports don't match up with the Classic Controller like usage.txt suggests (for example, proControllerButtons & CLASSIC_CTRL_BUTTON_RIGHT to read the dpad being pressed to the right will not work). Here's an enum I wrote which has correct values:
    Code:
    enum ProControllerButtons
    {
        PRO_CTRL_BUTTON_START = 0x04000000,
        PRO_CTRL_BUTTON_SELECT = 0x10000000,
        PRO_CTRL_BUTTON_HOME = 0x08000000,
        PRO_CTRL_BUTTON_A = 0x00100000,
        PRO_CTRL_BUTTON_B = 0x00400000,
        PRO_CTRL_BUTTON_Y = 0x00200000,
        PRO_CTRL_BUTTON_X = 0x00080000,
        PRO_CTRL_BUTTON_L = 0x20000000,
        PRO_CTRL_BUTTON_R = 0x02000000,
        PRO_CTRL_BUTTON_ZL = 0x00800000,
        PRO_CTRL_BUTTON_ZR = 0x00040000,
        PRO_CTRL_BUTTON_UP = 0x00010000,
        PRO_CTRL_BUTTON_DOWN = 0x40000000,
        PRO_CTRL_BUTTON_LEFT = 0x00020000,
        PRO_CTRL_BUTTON_RIGHT = 0x80000000,
    };
    
    3. It doesn't seem to allow regular Wii remotes to connect correctly, even if there is no Pro Controller connected. Once an application begins, the remote acts like it connects, then the LED turns off and no further response is seen. I'm using the gold Wii remote with built-in MotionPlus that came with the Skyward Sword bundle from a few years ago. The odd thing is I just tested with Nintendont 2.160's loader, which also supports this library, and it doesn't seem to mind having both connected at once.

    That was caused by a custom libOGC I was using. I forgot, heh.

    3. The player LEDs are not changed properly (e.g. if a Wii remote is already connected as player 1, and a Pro Controller is connected, the Pro Controller also gets the player 1 LED).

    4. It doesn't seem to respond to WPAD_Disconnect, or other WPAD related commands, so I'm not really sure how to force the controller to disconnect (for example, for an auto-disconnect battery saver feature) or tell if the controller is connected (besides checking if the WUPCData struct returned from WUPC_Data(int chan) isn't a nullptr).

    Other than that, it's working swimmingly.

    Here's a sample test homebrew I wrote (based on the libOGC default template) to test the functionality of the controller, maybe it'll help someone learn how to use the library. The only thing it lacks is testing the left/right analog push buttons, since the library doesn't seem to support them yet.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <gccore.h>
    #include <wiiuse/wpad.h>
    #include <wupc/wupc.h>
    #include <stdarg.h>
    #include <ogc/lwp_watchdog.h>
     
    static void *xfb = NULL;
    static GXRModeObj *rmode = NULL;
     
    static int HWButton = -1;
     
    enum HardwareButtons
    {
        RESET_BUTTON,
        POWER_BUTTON
    };
     
    enum ProControllerButtons
    {
        PRO_CTRL_BUTTON_START = 0x04000000,
        PRO_CTRL_BUTTON_SELECT = 0x10000000,
        PRO_CTRL_BUTTON_HOME = 0x08000000,
        PRO_CTRL_BUTTON_A = 0x00100000,
        PRO_CTRL_BUTTON_B = 0x00400000,
        PRO_CTRL_BUTTON_Y = 0x00200000,
        PRO_CTRL_BUTTON_X = 0x00080000,
        PRO_CTRL_BUTTON_L = 0x20000000,
        PRO_CTRL_BUTTON_R = 0x02000000,
        PRO_CTRL_BUTTON_ZL = 0x00800000,
        PRO_CTRL_BUTTON_ZR = 0x00040000,
        PRO_CTRL_BUTTON_UP = 0x00010000,
        PRO_CTRL_BUTTON_DOWN = 0x40000000,
        PRO_CTRL_BUTTON_LEFT = 0x00020000,
        PRO_CTRL_BUTTON_RIGHT = 0x80000000,
    };
     
    void PowerCallback()
    {
        HWButton = POWER_BUTTON;
    }
     
    void ResetCallback()
    {
        HWButton = RESET_BUTTON;
    }
     
    void print_on_row(const int row, const char * const fmt, ...)
    {
        printf("\x1b[%d;0H", row);
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
    }
     
    int main(int argc, char **argv)
    {
        // Initialise the video system
        VIDEO_Init();
     
        // This function initialises the attached controllers
        WUPC_Init();
        WPAD_Init();
     
        // Obtain the preferred video mode from the system
        // This will correspond to the settings in the Wii menu
        rmode = VIDEO_GetPreferredMode(NULL);
     
        // Allocate memory for the display in the uncached region
        xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
     
        // Initialise the console, required for printf
        console_init(xfb, 20, 20, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth*VI_DISPLAY_PIX_SZ);
     
        // Set up the video registers with the chosen mode
        VIDEO_Configure(rmode);
     
        // Tell the video hardware where our display memory is
        VIDEO_SetNextFramebuffer(xfb);
     
        // Make the display visible
        VIDEO_SetBlack(FALSE);
     
        // Flush the video register changes to the hardware
        VIDEO_Flush();
     
        // Wait for Video setup to complete
        VIDEO_WaitVSync();
        if (rmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync();
     
        SYS_SetPowerCallback(PowerCallback);
        SYS_SetResetCallback(ResetCallback);
     
        int row = 0;
        u32 proControllerButtons = 0;
        s16 proControllerLeftStickX = 0;
        s16 proControllerLeftStickY = 0;
        s16 proControllerRightStickX = 0;
        s16 proControllerRightStickY = 0;
     
        bool exitComboPressed = false;
        bool rumbling = false;
        bool rumble = false;
        bool proControllerIsConnected = false;
        u32 timeToRumble = 0;
     
        struct WUPCData *proControllerData = NULL;
     
        while (!exitComboPressed)
        {
            VIDEO_ClearFrameBuffer(rmode, xfb, COLOR_BLACK);
            VIDEO_SetBlack(FALSE);
     
            // Call WPAD_ScanPads each loop, this reads the latest controller states
            WPAD_ScanPads();
     
            proControllerData = WUPC_Data(0);
            if (proControllerData != NULL)
            {
                proControllerButtons = proControllerData->button;
                proControllerLeftStickX = WUPC_lStickX(0);
                proControllerLeftStickY = WUPC_lStickY(0);
                proControllerRightStickX = WUPC_rStickX(0);
                proControllerRightStickY = WUPC_rStickY(0);
                proControllerIsConnected = true;
            }
            else proControllerIsConnected = false;
     
            exitComboPressed = (proControllerButtons & PRO_CTRL_BUTTON_SELECT)
                            && (proControllerButtons & PRO_CTRL_BUTTON_START);
     
            row = 3;
     
            print_on_row(++row, "Pro Controller is %s", proControllerIsConnected ? "connected" : "not connected. Please connect it to test it.\nOr press reset or power on your console to exit.");
     
            if (proControllerIsConnected)
            {
                print_on_row(row, "Press select and start at the same time on your Pro Controller to exit.");
                print_on_row(++row, "Press L and R at the same time to test rumble.");
                print_on_row(++row, "Raw button status: %08x", proControllerButtons);
                print_on_row(++row, "Left Stick X: %d", proControllerLeftStickX);
                print_on_row(++row, "Left Stick Y : %d", proControllerLeftStickY);
                print_on_row(++row, "Right Stick X: %d", proControllerRightStickX);
                print_on_row(++row, "Right Stick Y : %d", proControllerRightStickY);
                print_on_row(++row, "Select is: %s", proControllerButtons & PRO_CTRL_BUTTON_SELECT ? "pressed" : "not pressed");
                print_on_row(++row, "Start is: %s", proControllerButtons & PRO_CTRL_BUTTON_START ? "pressed" : "not pressed");
                print_on_row(++row, "Home is: %s", proControllerButtons & PRO_CTRL_BUTTON_HOME ? "pressed" : "not pressed");
                print_on_row(++row, "A is: %s", proControllerButtons & PRO_CTRL_BUTTON_A ? "pressed" : "not pressed");
                print_on_row(++row, "B is: %s", proControllerButtons & PRO_CTRL_BUTTON_B ? "pressed" : "not pressed");
                print_on_row(++row, "Y is: %s", proControllerButtons & PRO_CTRL_BUTTON_Y ? "pressed" : "not pressed");
                print_on_row(++row, "X is: %s", proControllerButtons & PRO_CTRL_BUTTON_X ? "pressed" : "not pressed");
                print_on_row(++row, "L is: %s", proControllerButtons & PRO_CTRL_BUTTON_L ? "pressed" : "not pressed");
                print_on_row(++row, "R is: %s", proControllerButtons & PRO_CTRL_BUTTON_R ? "pressed" : "not pressed");
                print_on_row(++row, "ZL is: %s", proControllerButtons & PRO_CTRL_BUTTON_ZL ? "pressed" : "not pressed");
                print_on_row(++row, "ZR is: %s", proControllerButtons & PRO_CTRL_BUTTON_ZR ? "pressed" : "not pressed");
                print_on_row(++row, "Up is: %s", proControllerButtons & PRO_CTRL_BUTTON_UP ? "pressed" : "not pressed");
                print_on_row(++row, "Down is: %s", proControllerButtons & PRO_CTRL_BUTTON_DOWN ? "pressed" : "not pressed");
                print_on_row(++row, "Left is: %s", proControllerButtons & PRO_CTRL_BUTTON_LEFT ? "pressed" : "not pressed");
                print_on_row(++row, "Right is: %s", proControllerButtons & PRO_CTRL_BUTTON_RIGHT ? "pressed" : "not pressed");
     
                rumble = (proControllerButtons & PRO_CTRL_BUTTON_L)
                    && (proControllerButtons & PRO_CTRL_BUTTON_R);
     
                if (rumble)
                {
                    if (!rumbling)
                    {
                        rumbling = true;
                        timeToRumble = ticks_to_millisecs(gettime());
                        WUPC_Rumble(0, true);
                    }
                }
     
                // Stop rumbling after 250ms.
                if (ticks_to_millisecs(gettime()) > timeToRumble + 250 && rumbling)
                {
                    WUPC_Rumble(0, false);
                    rumbling = false;
                }
            }
     
            if (HWButton == POWER_BUTTON)
                SYS_ResetSystem(SYS_POWEROFF_STANDBY, 0, 0);
     
            if (HWButton == RESET_BUTTON)
                SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);
     
            // Wait for the next frame
            VIDEO_WaitVSync();
        }
     
        WUPC_Shutdown();
        WPAD_Shutdown();
        exit(0);
     
        return 0;
    }
    
     
    Margen67 and (deleted member) like this.
  7. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    Yes that is correct, I dont handle those yet so they fall out right now.
    You might look at the wrong place then, check out wiiuse/wpad.h, the "WPAD_CLASSIC_BUTTON_" definitions are equal to the wiiu pro controller.
    The player LEDs are handled by wiiuse internally so libwupc also has a internal player tracker, so they are separate from each other.
    Yes thats correct, I still need to add a separate disconnect function, so far only WUPC_Shutdown can do that. Also WUPCData being NULL was intended to check if its connected ;)

    Thanks for the feedback, I'll think about an update to add a proper disconnect function and see if I cant add the two buttons pressed in.
     
    Margen67 likes this.
  8. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    just made a tiny update, note that its untested:
    -added battery status, left and right stick button, charging and usb connected status to the WUPCData struct
    -added WUPC_Disconnect function which should disconnect the controller in the specified channel and update all other pro controller channels
     
  9. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Thanks for pointing me to the right header. I didn't know there were two sets of CC button masks..kinda weird. Anyway, thanks for the update. Hopefully in the future there will be a way to sync the player states. Until then, may I suggest adding a function like so?

    Code:
    void WUPC_SetLED(u8 chan, u8 led)
    {
        if (chan >= CHAN_MAX || __WUPC_Connected[chan] == NULL)
            return;
     
        if (led >= CHAN_MAX)
           led = CHAN_MAX - 1;
     
        u8 buf[2];
        buf[0] = 0x11;
        buf[1] = __WUPC_LEDState[led];
        bte_senddata(__WUPC_Connected[chan]->sock, buf, 2);
    }
    Then applications can decide which LED a given Pro Controller can have by simply passing 0-3 in the led variable, to turn whichever player's LED on.
     
  10. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    Thats highly unlikely, the wiiuse variable is static so we cannot access it from another library, in order to do that we would need to modify wiiuse which I dont want to do to be honest. Also just setting some player led wont mean that it actually is the player so I dont see any reasoning behind it.
     
  11. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Well, it's pretty confusing to see more than one controller with the same player LED, considering every other Wii game and homebrew shows players what player number they are that way. I guess I'll just add it myself since it's open source.
     
  12. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    well its just as separate as gc controllers are but sure, if you wanna fake a status go ahead heh.
     
  13. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Hold on, are you saying we could have up to 4 Wii remotes and 4 Wii U Pro Controllers connected at once, for a total of up to 8 devices? I thought the Wii's Bluetooth could only handle 6 or something (I forget where I read that, though).
     
  14. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    I dont see any reason on why you shouldnt be able to connect multiple devices at once...
     
    daxtsu likes this.
  15. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Well, if that's the case, what you're saying makes sense. I guess I was thinking too much inside the box by being limited to only 4 Bluetooth devices in general. I'm sorry for getting a bit snippy earlier.
     
  16. duffmmann

    duffmmann GBAtemp Psycho!

    Member
    3,649
    1,483
    Mar 11, 2009
    United States
    Yes! I can't wait to see this get implemented across my favorite emulators (I'm looking at you WiiSX, SNES9XGX, GenPlusGX, VBAGX, and all the others!)
     
  17. the_randomizer

    the_randomizer The Temp's official fox whisperer

    Member
    21,210
    10,078
    Apr 29, 2011
    United States
    Dr. Wahwee's castle
    Why am I now just learning about this? Awesome news!!
     
  18. daxtsu

    daxtsu GBAtemp Guru

    Member
    5,539
    3,930
    Jun 9, 2007
    Antarctica
    Learning about what? This library? Just gotta come out of the Nintendont thread sometimes. :P

    As we speak, I'm attempting to add support for this into WiiXplorer. It's probably the application I use the second most after postLoader (I spend most of my time developing or changing files on my USB drive). Shouldn't be too hard to do.
     
    OfficerJeffrey likes this.
  19. duffmmann

    duffmmann GBAtemp Psycho!

    Member
    3,649
    1,483
    Mar 11, 2009
    United States

    Not to be rude, but why do you use Wiixplorer to change files on your usb drive? Wouldn't it be easier to do so on your computer. Don't get me wrong, I use WiiXplorer from time to time, but it isn't very often.
     
  20. FIX94
    OP

    FIX94 Global Moderator

    Global Moderator
    7,076
    8,868
    Dec 3, 2009
    Germany
    ???
    I've modded snes9xgx to test so far, you can follow the link in my signature if you wanna test it.
     
    scubamage, filfat, Taleweaver and 4 others like this.