diff --git a/nx/include/switch.h b/nx/include/switch.h index 6a541cbd..5af99746 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -93,6 +93,8 @@ extern "C" { #include "switch/services/set.h" #include "switch/services/ssl.h" #include "switch/services/lr.h" +#include "switch/services/bt.h" +#include "switch/services/btdrv.h" #include "switch/services/spl.h" #include "switch/services/ncm.h" #include "switch/services/psc.h" diff --git a/nx/include/switch/services/bt.h b/nx/include/switch/services/bt.h new file mode 100644 index 00000000..200b1646 --- /dev/null +++ b/nx/include/switch/services/bt.h @@ -0,0 +1,131 @@ +/** + * @file bt.h + * @brief Bluetooth user (bt) service IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../kernel/event.h" +#include "../services/btdrv.h" +#include "../sf/service.h" + +/// Initialize bt. Only available on [5.0.0+]. +Result btInitialize(void); + +/// Exit bt. +void btExit(void); + +/// Gets the Service object for the actual bt service session. +Service* btGetServiceSession(void); + +/** + * @brief LeClientReadCharacteristic + * @note This is essentially the same as \ref btdrvReadGattCharacteristic. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btLeClientReadCharacteristic(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief LeClientReadDescriptor + * @note This is essentially the same as \ref btdrvReadGattDescriptor. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] id2 \ref BtdrvGattId + */ +Result btLeClientReadDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2); + +/** + * @brief LeClientWriteCharacteristic + * @note This is essentially the same as \ref btdrvWriteGattCharacteristic. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] flag2 Flag + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btLeClientWriteCharacteristic(bool flag, u8 unk, bool flag2, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const void* buffer, size_t size); + +/** + * @brief LeClientWriteDescriptor + * @note This is essentially the same as \ref btdrvWriteGattDescriptor. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] id2 \ref BtdrvGattId + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btLeClientWriteDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2, const void* buffer, size_t size); + +/** + * @brief LeClientRegisterNotification + * @note This is essentially the same as \ref btdrvRegisterGattNotification. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btLeClientRegisterNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief LeClientDeregisterNotification + * @note This is essentially the same as \ref btdrvUnregisterGattNotification. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btLeClientDeregisterNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief SetLeResponse + * @param[in] unk Unknown + * @param[in] uuid0 \ref BtdrvGattAttributeUuid + * @param[in] uuid1 \ref BtdrvGattAttributeUuid + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btSetLeResponse(u8 unk, const BtdrvGattAttributeUuid *uuid0, const BtdrvGattAttributeUuid *uuid1, const void* buffer, size_t size); + +/** + * @brief LeSendIndication + * @param[in] unk Unknown + * @param[in] flag Flag + * @param[in] uuid0 \ref BtdrvGattAttributeUuid + * @param[in] uuid1 \ref BtdrvGattAttributeUuid + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btLeSendIndication(u8 unk, bool flag, const BtdrvGattAttributeUuid *uuid0, const BtdrvGattAttributeUuid *uuid1, const void* buffer, size_t size); + +/** + * @brief GetLeEventInfo + * @note This is identical to \ref btdrvGetLeEventInfo except different state is used. + * @note The state used by this is reset after writing the data to output. + * @param[in] buffer Output buffer. 0x400-bytes from state is written here. + * @param[in] size Output buffer size. + * @oaram[out] type Output BleEventType. + */ +Result btGetLeEventInfo(void* buffer, size_t size, u32 *type); + +/** + * @brief RegisterBleEvent + * @note This is identical to \ref btdrvRegisterBleHidEvent except different state is used. + * @note The Event must be closed by the user once finished with it. + * @param[out] out_event Output Event with autoclear=true. + */ +Result btRegisterBleEvent(Event* out_event); + diff --git a/nx/include/switch/services/btdrv.h b/nx/include/switch/services/btdrv.h new file mode 100644 index 00000000..40ab163b --- /dev/null +++ b/nx/include/switch/services/btdrv.h @@ -0,0 +1,181 @@ +/** + * @file btdrv.h + * @brief Bluetooth driver (btdrv) service IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../kernel/event.h" +#include "../sf/service.h" + +/// Address +typedef struct { + u8 address[0x6]; ///< Address +} BtdrvAddress; + +/// AdapterProperty +typedef struct { + u8 unk_x0[0x103]; ///< Unknown +} BtdrvAdapterProperty; + +/// BluetoothPinCode +typedef struct { + char code[0x10]; ///< PinCode +} BtdrvBluetoothPinCode; + +/// HidData, for pre-9.0.0. +typedef struct { + u16 size; ///< Size of data. + u8 data[0x280]; ///< Data +} BtdrvHidData; + +/// HidReport, for [9.0.0+]. +typedef struct { + u16 size; ///< Size of data. + u8 data[0x2BC]; ///< Data +} BtdrvHidReport; + +/// PlrStatistics +typedef struct { + u8 unk_x0[0x84]; ///< Unknown +} BtdrvPlrStatistics; + +/// PlrList +typedef struct { + u8 unk_x0[0xA4]; ///< Unknown +} BtdrvPlrList; + +/// ChannelMapList +typedef struct { + u8 unk_x0[0x88]; ///< Unknown +} BtdrvChannelMapList; + +/// LeConnectionParams +typedef struct { + u8 unk_x0[0x14]; ///< Unknown +} BtdrvLeConnectionParams; + +/// BleConnectionParameter +typedef struct { + u8 unk_x0[0xC]; ///< Unknown +} BtdrvBleConnectionParameter; + +/// BleAdvertisePacketData +typedef struct { + u8 unk_x0[0xCC]; ///< Unknown +} BtdrvBleAdvertisePacketData; + +/// BleAdvertiseFilter +typedef struct { + u8 unk_x0[0x3E]; ///< Unknown +} BtdrvBleAdvertiseFilter; + +/// GattAttributeUuid +typedef struct { + u8 unk_x0[0x14]; ///< Unknown +} BtdrvGattAttributeUuid; + +/// GattId +typedef struct { + u8 unk_x0[0x18]; ///< Unknown +} BtdrvGattId; + +/// Initialize btdrv. +Result btdrvInitialize(void); + +/// Exit btdrv. +void btdrvExit(void); + +/// Gets the Service object for the actual btdrv service session. +Service* btdrvGetServiceSession(void); + +/** + * @brief ReadGattCharacteristic + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btdrvReadGattCharacteristic(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief ReadGattDescriptor + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] id2 \ref BtdrvGattId + */ +Result btdrvReadGattDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2); + +/** + * @brief WriteGattCharacteristic + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] flag2 Flag + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btdrvWriteGattCharacteristic(bool flag, u8 unk, bool flag2, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const void* buffer, size_t size); + +/** + * @brief WriteGattDescriptor + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] unk2 Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + * @param[in] id2 \ref BtdrvGattId + * @param[in] buffer Input buffer. + * @param[in] size Input buffer size. + */ +Result btdrvWriteGattDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2, const void* buffer, size_t size); + +/** + * @brief RegisterGattNotification + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btdrvRegisterGattNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief UnregisterGattNotification + * @note Only available on [5.0.0+]. + * @param[in] flag Flag + * @param[in] unk Unknown + * @param[in] id0 \ref BtdrvGattId + * @param[in] id1 \ref BtdrvGattId + */ +Result btdrvUnregisterGattNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1); + +/** + * @brief GetLeEventInfo + * @note Only available on [5.0.0+]. + * @note The state used by this is reset after writing the data to output. + * @param[in] buffer Output buffer. 0x400-bytes from state is written here. + * @param[in] size Output buffer size. + * @oaram[out] type Output BleEventType. + */ +Result btdrvGetLeEventInfo(void* buffer, size_t size, u32 *type); + +/** + * @brief RegisterBleHidEvent + * @note Only available on [5.0.0+]. + * @note The Event must be closed by the user once finished with it. + * @param[out] out_event Output Event with autoclear=true. + */ +Result btdrvRegisterBleHidEvent(Event* out_event); + diff --git a/nx/source/services/bt.c b/nx/source/services/bt.c new file mode 100644 index 00000000..2d2af8cc --- /dev/null +++ b/nx/source/services/bt.c @@ -0,0 +1,178 @@ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include "service_guard.h" +#include "runtime/hosversion.h" +#include "services/bt.h" +#include "services/applet.h" + +static Service g_btSrv; + +NX_GENERATE_SERVICE_GUARD(bt); + +Result _btInitialize(void) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return smGetService(&g_btSrv, "bt"); +} + +void _btCleanup(void) { + serviceClose(&g_btSrv); +} + +Service* btGetServiceSession(void) { + return &g_btSrv; +} + +Result btLeClientReadCharacteristic(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1) { + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + u64 AppletResourceUserId; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 0, in, + .in_send_pid = true, + ); +} + +Result btLeClientReadDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2) { + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + BtdrvGattId id2; + u64 AppletResourceUserId; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1, *id2, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 1, in, + .in_send_pid = true, + ); +} + +Result btLeClientWriteCharacteristic(bool flag, u8 unk, bool flag2, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const void* buffer, size_t size) { + const struct { + u8 flag; + u8 unk; + u8 flag2; + u8 pad; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + u64 AppletResourceUserId; + } in = { flag!=0, unk, flag2!=0, 0, unk2, *id0, *id1, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 2, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + .in_send_pid = true, + ); +} + +Result btLeClientWriteDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2, const void* buffer, size_t size) { + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + BtdrvGattId id2; + u64 AppletResourceUserId; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1, *id2, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 3, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + .in_send_pid = true, + ); +} + +static Result _btLeClientNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1, u32 cmd_id) { + const struct { + u8 flag; + u8 pad[3]; + u32 unk; + BtdrvGattId id0; + BtdrvGattId id1; + u64 AppletResourceUserId; + } in = { flag!=0, {0}, unk, *id0, *id1, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, cmd_id, in, + .in_send_pid = true, + ); +} + +Result btLeClientRegisterNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1) { + return _btLeClientNotification(flag, unk, id0, id1, 4); +} + +Result btLeClientDeregisterNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1) { + return _btLeClientNotification(flag, unk, id0, id1, 5); +} + +Result btSetLeResponse(u8 unk, const BtdrvGattAttributeUuid *uuid0, const BtdrvGattAttributeUuid *uuid1, const void* buffer, size_t size) { + const struct { + u8 unk; + u8 pad[3]; + BtdrvGattAttributeUuid uuid0; + BtdrvGattAttributeUuid uuid1; + u8 pad2[4]; + u64 AppletResourceUserId; + } in = { unk, {0}, *uuid0, *uuid1, {0}, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 6, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + .in_send_pid = true, + ); +} + +Result btLeSendIndication(u8 unk, bool flag, const BtdrvGattAttributeUuid *uuid0, const BtdrvGattAttributeUuid *uuid1, const void* buffer, size_t size) { + const struct { + u8 unk; + u8 flag; + u8 pad[2]; + BtdrvGattAttributeUuid uuid0; + BtdrvGattAttributeUuid uuid1; + u8 pad2[4]; + u64 AppletResourceUserId; + } in = { unk, flag!=0, {0}, *uuid0, *uuid1, {0}, appletGetAppletResourceUserId() }; + + return serviceDispatchIn(&g_btSrv, 7, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + .in_send_pid = true, + ); +} + +Result btGetLeEventInfo(void* buffer, size_t size, u32 *type) { + u64 AppletResourceUserId = appletGetAppletResourceUserId(); + return serviceDispatchInOut(&g_btSrv, 8, AppletResourceUserId, *type, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + .buffers = { { buffer, size } }, + .in_send_pid = true, + ); +} + +Result btRegisterBleEvent(Event* out_event) { + Handle tmp_handle = INVALID_HANDLE; + Result rc = 0; + u64 AppletResourceUserId = appletGetAppletResourceUserId(); + + rc = serviceDispatchIn(&g_btSrv, 9, AppletResourceUserId, + .in_send_pid = true, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &tmp_handle, + ); + if (R_SUCCEEDED(rc)) eventLoadRemote(out_event, tmp_handle, true); + return rc; +} + diff --git a/nx/source/services/btdrv.c b/nx/source/services/btdrv.c new file mode 100644 index 00000000..9cb8bbb5 --- /dev/null +++ b/nx/source/services/btdrv.c @@ -0,0 +1,153 @@ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include "service_guard.h" +#include "runtime/hosversion.h" +#include "services/btdrv.h" + +static Service g_btdrvSrv; + +NX_GENERATE_SERVICE_GUARD(btdrv); + +Result _btdrvInitialize(void) { + return smGetService(&g_btdrvSrv, "btdrv"); +} + +void _btdrvCleanup(void) { + serviceClose(&g_btdrvSrv); +} + +Service* btdrvGetServiceSession(void) { + return &g_btdrvSrv; +} + +Result btdrvReadGattCharacteristic(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 89 : 90; + + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1}; + + return serviceDispatchIn(&g_btdrvSrv, cmd_id, in); +} + +Result btdrvReadGattDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 90 : 91; + + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + BtdrvGattId id2; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1, *id2 }; + + return serviceDispatchIn(&g_btdrvSrv, cmd_id, in); +} + +Result btdrvWriteGattCharacteristic(bool flag, u8 unk, bool flag2, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const void* buffer, size_t size) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 91 : 92; + + const struct { + u8 flag; + u8 unk; + u8 flag2; + u8 pad; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + } in = { flag!=0, unk, flag2!=0, 0, unk2, *id0, *id1 }; + + return serviceDispatchIn(&g_btdrvSrv, cmd_id, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + ); +} + +Result btdrvWriteGattDescriptor(bool flag, u8 unk, u32 unk2, const BtdrvGattId *id0, const BtdrvGattId *id1, const BtdrvGattId *id2, const void* buffer, size_t size) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 92 : 93; + + const struct { + u8 flag; + u8 unk; + u8 pad[2]; + u32 unk2; + BtdrvGattId id0; + BtdrvGattId id1; + BtdrvGattId id2; + } in = { flag!=0, unk, {0}, unk2, *id0, *id1, *id2 }; + + return serviceDispatchIn(&g_btdrvSrv, cmd_id, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { buffer, size } }, + ); +} + +static Result _btdrvGattNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1, u32 cmd_id) { + const struct { + u8 flag; + u8 pad[3]; + u32 unk; + BtdrvGattId id0; + BtdrvGattId id1; + } in = { flag!=0, {0}, unk, *id0, *id1 }; + + return serviceDispatchIn(&g_btdrvSrv, cmd_id, in); +} + +Result btdrvRegisterGattNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _btdrvGattNotification(flag, unk, id0, id1, 94); +} + +Result btdrvUnregisterGattNotification(bool flag, u32 unk, const BtdrvGattId *id0, const BtdrvGattId *id1) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 93 : 95; + + return _btdrvGattNotification(flag, unk, id0, id1, cmd_id); +} + +Result btdrvGetLeEventInfo(void* buffer, size_t size, u32 *type) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 95 : 96; + + return serviceDispatchOut(&g_btdrvSrv, cmd_id, *type, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + .buffers = { { buffer, size } }, + ); +} + +Result btdrvRegisterBleHidEvent(Event* out_event) { + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + u32 cmd_id = hosversionBefore(6,0,0) ? 96 : 97; + + Handle tmp_handle = INVALID_HANDLE; + Result rc = 0; + + rc = serviceDispatch(&g_btdrvSrv, cmd_id, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &tmp_handle, + ); + if (R_SUCCEEDED(rc)) eventLoadRemote(out_event, tmp_handle, true); + return rc; +} +