From 9f28d0002a051e891f3c73d1589a20154275c7a8 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 28 Nov 2019 16:04:46 -0500 Subject: [PATCH] Added support for hidLa (controller libapplet). --- nx/include/switch.h | 1 + nx/include/switch/applets/hid_la.h | 152 +++++++++++++++++++++ nx/source/applets/hid_la.c | 209 +++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 nx/include/switch/applets/hid_la.h create mode 100644 nx/source/applets/hid_la.c diff --git a/nx/include/switch.h b/nx/include/switch.h index c902da8e..578f839b 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -124,6 +124,7 @@ extern "C" { #include "switch/applets/libapplet.h" #include "switch/applets/album_la.h" #include "switch/applets/friends_la.h" +#include "switch/applets/hid_la.h" #include "switch/applets/pctlauth.h" #include "switch/applets/psel.h" #include "switch/applets/error.h" diff --git a/nx/include/switch/applets/hid_la.h b/nx/include/switch/applets/hid_la.h new file mode 100644 index 00000000..d63cec7b --- /dev/null +++ b/nx/include/switch/applets/hid_la.h @@ -0,0 +1,152 @@ +/** + * @file hid_la.h + * @brief Wrapper for using the controller LibraryApplet. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../services/hid.h" + +/// Type values for HidLaControllerSupportArgPrivate::type. +typedef enum { + HidLaControllerSupportMode_ShowControllerSupport = 0, ///< ShowControllerSupport + HidLaControllerSupportMode_ShowControllerStrapGuide = 1, ///< [3.0.0+] ShowControllerStrapGuide + HidLaControllerSupportMode_ShowControllerFirmwareUpdate = 2, ///< [3.0.0+] ShowControllerFirmwareUpdate +} HidLaControllerSupportMode; + +/// ControllerSupportCaller +typedef enum { + HidLaControllerSupportCaller_Application = 0, ///< Application, this is the default. + HidLaControllerSupportCaller_System = 1, ///< System. Skips the firmware-update confirmation dialog. This has the same affect as using the controller-update option from qlaunch System Settings. +} HidLaControllerSupportCaller; + +/// ControllerSupportArgPrivate +typedef struct { + u32 private_size; ///< Size of this ControllerSupportArgPrivate struct. + u32 arg_size; ///< Size of the storage following this one (\ref HidLaControllerSupportArg or \ref HidLaControllerFirmwareUpdateArg). + u8 flag0; ///< Flag0 + u8 flag1; ///< Flag1 + u8 mode; ///< \ref HidLaControllerSupportMode + u8 controller_support_caller; ///< Always zero except with ShowControllerFirmwareUpdateForSystem, which sets this to the input param. + u32 npad_style_set; ///< Output from \ref hidGetSupportedNpadStyleSet. With ShowControllerSupportForSystem on pre-3.0.0 this is value 0. + u32 npad_joy_hold_type; ///< Output from \ref hidGetNpadJoyHoldType. With ShowControllerSupportForSystem on pre-3.0.0 this is value 1. +} HidLaControllerSupportArgPrivate; + +/// Common header used by HidLaControllerSupportArg*. +/// max_supported_players is 4 on pre-8.0.0, 8 on [8.0.0+]. player_count_min and player_count_max are overriden with value 4 when larger than value 4, during conversion handling for \ref HidLaControllerSupportArg on pre-8.0.0. +typedef struct { + s8 player_count_min; ///< playerCountMin. Must be >=0 and <=max_supported_players. + s8 player_count_max; ///< playerCountMax. Must be >=1 and <=max_supported_players. + u8 enable_take_over_connection; ///< enableTakeOverConnection, non-zero to enable. Disconnects the controllers when not enabled. + u8 enable_left_justify; ///< enableLeftJustify, non-zero to enable. + u8 enable_permit_joy_dual; ///< enablePermitJoyDual, non-zero to enable. + u8 enable_single_mode; ///< enableSingleMode, non-zero to enable. Enables using a single player in handheld-mode, dual-mode, or single-mode (player_count_* are overriden). Using handheld-mode is not allowed if this is not enabled. + u8 enable_identification_color; ///< When non-zero enables using identification_color. +} HidLaControllerSupportArgHeader; + +/// Identification color used by HidLaControllerSupportArg*. When HidLaControllerSupportArgHeader::enable_identification_color is set this controls the color of the UI player box outline. +typedef struct { + u8 r; ///< Red color component. + u8 g; ///< Green color component. + u8 b; ///< Blue color component. + u8 a; ///< Alpha color component. +} HidLaControllerSupportArgColor; + +/// ControllerSupportArg for [1.0.0+]. +typedef struct { + HidLaControllerSupportArgHeader hdr; ///< \ref HidLaControllerSupportArgHeader + HidLaControllerSupportArgColor identification_color[4]; ///< \ref HidLaControllerSupportArgColor for each player, see HidLaControllerSupportArgHeader::enable_identification_color. + u8 enable_explain_text; ///< Enables using the ExplainText data when non-zero. + char explain_text[4][0x81]; ///< ExplainText for each player, NUL-terminated UTF-8 strings. +} HidLaControllerSupportArgV3; + +/// ControllerSupportArg for [8.0.0+], converted to \ref HidLaControllerSupportArgV3 on pre-8.0.0. +typedef struct { + HidLaControllerSupportArgHeader hdr; ///< \ref HidLaControllerSupportArgHeader + HidLaControllerSupportArgColor identification_color[8]; ///< \ref HidLaControllerSupportArgColor for each player, see HidLaControllerSupportArgHeader::enable_identification_color. + u8 enable_explain_text; ///< Enables using the ExplainText data when non-zero. + char explain_text[8][0x81]; ///< ExplainText for each player, NUL-terminated UTF-8 strings. +} HidLaControllerSupportArg; + +/// ControllerFirmwareUpdateArg +typedef struct { + u8 enable_force_update; ///< enableForceUpdate, non-zero to enable. Default is 0. Forces a firmware update when enabled, without an UI option to skip it. + u8 pad[3]; ///< Padding. +} HidLaControllerFirmwareUpdateArg; + +/// ControllerSupportResultInfo. First 8-bytes from the applet output storage. +typedef struct { + s8 player_count; ///< playerCount. + u8 pad[3]; ///< Padding. + u32 selected_id; ///< \ref HidControllerID, selectedId. +} HidLaControllerSupportResultInfo; + +/// Struct for the applet output storage. +typedef struct { + HidLaControllerSupportResultInfo info; ///< \ref HidLaControllerSupportResultInfo + u32 res; ///< Output res value. +} HidLaControllerSupportResultInfoInternal; + +/** + * @brief Initializes a \ref HidLaControllerSupportArg with the defaults. + * @note This clears the arg, then does: HidLaControllerSupportArgHeader::player_count_min = 0, HidLaControllerSupportArgHeader::player_count_max = 4, HidLaControllerSupportArgHeader::enable_take_over_connection = 1, HidLaControllerSupportArgHeader::enable_left_justify = 1, and HidLaControllerSupportArgHeader::enable_permit_joy_dual = 1. + * @note If preferred, you can also memset \ref HidLaControllerSupportArg manually and initialize it yourself. + * @param[out] arg \ref HidLaControllerSupportArg + */ +void hidLaCreateControllerSupportArg(HidLaControllerSupportArg *arg); + +/** + * @brief Initializes a \ref HidLaControllerFirmwareUpdateArg with the defaults. + * @note This just uses memset() with the arg. + * @param[out] arg \ref HidLaControllerFirmwareUpdateArg + */ +void hidLaCreateControllerFirmwareUpdateArg(HidLaControllerFirmwareUpdateArg *arg); + +/** + * @brief Sets the ExplainText for the specified player and \ref HidLaControllerSupportArg. + * @note This string is displayed in the UI box for the player. + * @note HidLaControllerSupportArg::enable_explain_text must be set, otherwise this ExplainText is ignored. + * @param arg \ref HidLaControllerSupportArg + * @param[in] str Input ExplainText UTF-8 string, max length is 0x80 excluding NUL-terminator. + + @oaram[in] id Player controller, must be <8. + */ +Result hidLaSetExplainText(HidLaControllerSupportArg *arg, const char *str, HidControllerID id); + +/** + * @brief Launches the applet for ControllerSupport. + * @note This seems to only display the applet UI when doing so is actually needed? This doesn't apply to \ref hidLaShowControllerSupportForSystem. + * @param[out] result_info \ref HidLaControllerSupportResultInfo. Optional, can be NULL. + * @param[in] arg \ref HidLaControllerSupportArg + */ +Result hidLaShowControllerSupport(HidLaControllerSupportResultInfo *result_info, const HidLaControllerSupportArg *arg); + +/** + * @brief Launches the applet for ControllerStrapGuide. + * @note Only available on [3.0.0+]. + */ +Result hidLaShowControllerStrapGuide(void); + +/** + * @brief Launches the applet for ControllerFirmwareUpdate. + * @note Only available on [3.0.0+]. + * @param[in] arg \ref HidLaControllerFirmwareUpdateArg + */ +Result hidLaShowControllerFirmwareUpdate(const HidLaControllerFirmwareUpdateArg *arg); + +/** + * @brief This is the system version of \ref hidLaShowControllerSupport. + * @param[out] result_info \ref HidLaControllerSupportResultInfo. Optional, can be NULL. + * @param[in] arg \ref HidLaControllerSupportArg + * @param[in] flag Input flag. When true, the applet displays the menu as if launched by qlaunch. + */ +Result hidLaShowControllerSupportForSystem(HidLaControllerSupportResultInfo *result_info, const HidLaControllerSupportArg *arg, bool flag); + +/** + * @brief This is the system version of \ref hidLaShowControllerFirmwareUpdate. + * @note Only available on [3.0.0+]. + * @param[in] arg \ref HidLaControllerFirmwareUpdateArg + * @param[in] caller \ref HidLaControllerSupportCaller + */ +Result hidLaShowControllerFirmwareUpdateForSystem(const HidLaControllerFirmwareUpdateArg *arg, HidLaControllerSupportCaller caller); + diff --git a/nx/source/applets/hid_la.c b/nx/source/applets/hid_la.c new file mode 100644 index 00000000..0167bec9 --- /dev/null +++ b/nx/source/applets/hid_la.c @@ -0,0 +1,209 @@ +#include +#include "libapplet_internal.h" +#include "applets/hid_la.h" +#include "runtime/hosversion.h" + +static Result _hidLaShow(const HidLaControllerSupportArgPrivate *private_arg, const void* arg, size_t arg_size, void* reply, size_t reply_size) { + Result rc=0; + LibAppletArgs commonargs; + AppletHolder holder; + u32 version=0x3; // [1.0.0+] + + if (hosversionAtLeast(8,0,0)) + version = 0x7; + else if (hosversionAtLeast(6,0,0)) + version = 0x5; + else if (hosversionAtLeast(3,0,0)) + version = 0x4; + + rc = appletCreateLibraryApplet(&holder, AppletId_controller, LibAppletMode_AllForeground); + if (R_FAILED(rc)) return rc; + + libappletArgsCreate(&commonargs, version); + libappletArgsSetPlayStartupSound(&commonargs, (private_arg->flag1!=0) && (private_arg->flag0!=0)); + + if (R_SUCCEEDED(rc)) rc = libappletArgsPush(&commonargs, &holder); + if (R_SUCCEEDED(rc)) rc = libappletPushInData(&holder, private_arg, sizeof(*private_arg)); + if (R_SUCCEEDED(rc)) rc = libappletPushInData(&holder, arg, arg_size); + if (R_SUCCEEDED(rc)) rc = libappletStart(&holder); + if (R_SUCCEEDED(rc)) libappletPopOutData(&holder, reply, reply_size, NULL); // Official sw ignores the rc/transfer_size. + appletHolderClose(&holder); + + return rc; +} + +static Result _hidLaShowControllerSupportCore(HidLaControllerSupportResultInfo *result_info, const HidLaControllerSupportArg *arg, const HidLaControllerSupportArgPrivate *private_arg) { + Result rc=0; + HidLaControllerSupportResultInfoInternal res={0}; + HidLaControllerSupportArgV3 arg_v3; + const void* arg_ptr = arg; + size_t arg_size = sizeof(*arg); + + if (private_arg->mode == HidLaControllerSupportMode_ShowControllerFirmwareUpdate) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + if (hosversionBefore(8,0,0)) { + memset(&arg_v3, 0, sizeof(arg_v3)); + arg_ptr = &arg_v3; + arg_size = sizeof(arg_v3); + + memcpy(&arg_v3.hdr, &arg->hdr, sizeof(arg->hdr)); + memcpy(arg_v3.identification_color, arg->identification_color, sizeof(arg_v3.identification_color)); + arg_v3.enable_explain_text = arg->enable_explain_text; + memcpy(arg_v3.explain_text, arg->explain_text, sizeof(arg_v3.explain_text)); + + if (arg_v3.hdr.player_count_min > 4) arg_v3.hdr.player_count_min = 4; + if (arg_v3.hdr.player_count_max > 4) arg_v3.hdr.player_count_max = 4; + } + + rc = _hidLaShow(private_arg, arg_ptr, arg_size, &res, sizeof(res)); + if (R_SUCCEEDED(rc)) { + if (result_info) { + *result_info = res.info; + result_info->selected_id = hidControllerIDFromOfficial(result_info->selected_id); + } + + if (res.res != 0) { + rc = MAKERESULT(Module_Libnx, LibnxError_LibAppletBadExit); // Official sw would return different values for 2/{other values}, but we won't do so. + } + } + + return rc; +} + +static Result _hidLaShowControllerFirmwareUpdateCore(const HidLaControllerFirmwareUpdateArg *arg, const HidLaControllerSupportArgPrivate *private_arg) { + Result rc=0; + HidLaControllerSupportResultInfoInternal res={0}; + + if (hosversionBefore(3,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + if (private_arg->mode != HidLaControllerSupportMode_ShowControllerFirmwareUpdate) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + rc = _hidLaShow(private_arg, arg, sizeof(*arg), &res, sizeof(res)); + if (R_SUCCEEDED(rc)) { + if (res.res != 0) { + rc = MAKERESULT(Module_Libnx, LibnxError_LibAppletBadExit); + } + } + + return rc; +} + +static Result _hidLaSetupControllerSupportArgPrivate(HidLaControllerSupportArgPrivate *private_arg) { + Result rc=0; + HidControllerType type; + HidJoyHoldType hold_type; + + rc = hidGetSupportedNpadStyleSet(&type); + if (R_SUCCEEDED(rc)) rc = hidGetNpadJoyHoldType(&hold_type); + + if (R_SUCCEEDED(rc)) { + private_arg->npad_style_set = type; + private_arg->npad_joy_hold_type = hold_type; + } + + return rc; +} + +void hidLaCreateControllerSupportArg(HidLaControllerSupportArg *arg) { + memset(arg, 0, sizeof(*arg)); + arg->hdr.player_count_min = 0; + arg->hdr.player_count_max = 4; + arg->hdr.enable_take_over_connection = 1; + arg->hdr.enable_left_justify = 1; + arg->hdr.enable_permit_joy_dual = 1; +} + +void hidLaCreateControllerFirmwareUpdateArg(HidLaControllerFirmwareUpdateArg *arg) { + memset(arg, 0, sizeof(*arg)); +} + +Result hidLaSetExplainText(HidLaControllerSupportArg *arg, const char *str, HidControllerID id) { + if (id >= 8) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + memset(arg->explain_text[id], 0, sizeof(arg->explain_text[id])); + strncpy(arg->explain_text[id], str, sizeof(arg->explain_text[id])-1); + + return 0; +} + +Result hidLaShowControllerSupport(HidLaControllerSupportResultInfo *result_info, const HidLaControllerSupportArg *arg) { + Result rc=0; + HidLaControllerSupportArgPrivate private_arg = { + .private_size = sizeof(private_arg), .arg_size = sizeof(*arg), .mode = HidLaControllerSupportMode_ShowControllerSupport + }; + + rc = _hidLaSetupControllerSupportArgPrivate(&private_arg); + + if (R_SUCCEEDED(rc)) rc = _hidLaShowControllerSupportCore(result_info, arg, &private_arg); + + return rc; +} + +Result hidLaShowControllerStrapGuide(void) { + Result rc=0; + HidLaControllerSupportArg arg; + HidLaControllerSupportArgPrivate private_arg = { + .private_size = sizeof(private_arg), .arg_size = sizeof(arg), .mode = HidLaControllerSupportMode_ShowControllerStrapGuide + }; + + if (hosversionBefore(3,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + hidLaCreateControllerSupportArg(&arg); + + rc = _hidLaSetupControllerSupportArgPrivate(&private_arg); + + if (R_SUCCEEDED(rc)) rc = _hidLaShowControllerSupportCore(NULL, &arg, &private_arg); + + return rc; +} + +Result hidLaShowControllerFirmwareUpdate(const HidLaControllerFirmwareUpdateArg *arg) { + Result rc=0; + HidLaControllerSupportArgPrivate private_arg = { + .private_size = sizeof(private_arg), .arg_size = sizeof(*arg), .mode = HidLaControllerSupportMode_ShowControllerFirmwareUpdate + }; + + rc = _hidLaSetupControllerSupportArgPrivate(&private_arg); + + if (R_SUCCEEDED(rc)) rc = _hidLaShowControllerFirmwareUpdateCore(arg, &private_arg); + + return rc; +} + +Result hidLaShowControllerSupportForSystem(HidLaControllerSupportResultInfo *result_info, const HidLaControllerSupportArg *arg, bool flag) { + Result rc=0; + HidLaControllerSupportArgPrivate private_arg = { + .private_size = sizeof(private_arg), .arg_size = sizeof(*arg), + .flag0 = flag!=0, .flag1 = 1, .mode = HidLaControllerSupportMode_ShowControllerSupport + }; + + if (hosversionAtLeast(3,0,0)) { + rc = _hidLaSetupControllerSupportArgPrivate(&private_arg); + } + else { + private_arg.npad_style_set = 0; + private_arg.npad_joy_hold_type = HidJoyHoldType_Horizontal; + } + + if (R_SUCCEEDED(rc)) rc = _hidLaShowControllerSupportCore(result_info, arg, &private_arg); + + return rc; +} + +Result hidLaShowControllerFirmwareUpdateForSystem(const HidLaControllerFirmwareUpdateArg *arg, HidLaControllerSupportCaller caller) { + Result rc=0; + HidLaControllerSupportArgPrivate private_arg = { + .private_size = sizeof(private_arg), .arg_size = sizeof(*arg), + .flag1 = 1, .mode = HidLaControllerSupportMode_ShowControllerFirmwareUpdate, .controller_support_caller = caller + }; + + rc = _hidLaSetupControllerSupportArgPrivate(&private_arg); + + if (R_SUCCEEDED(rc)) rc = _hidLaShowControllerFirmwareUpdateCore(arg, &private_arg); + + return rc; +} +