From 70ea18a477ac2f287434652b35c284012596e3fd Mon Sep 17 00:00:00 2001 From: yellows8 Date: Mon, 30 Jun 2025 15:48:05 -0400 Subject: [PATCH] ldn: Added *ActionFrame, ldnSetHomeChannel, *TxPower. --- nx/include/switch/services/ldn.h | 75 +++++++++++++++ nx/source/services/ldn.c | 153 +++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/nx/include/switch/services/ldn.h b/nx/include/switch/services/ldn.h index 8ece9c55..5a106fcd 100644 --- a/nx/include/switch/services/ldn.h +++ b/nx/include/switch/services/ldn.h @@ -193,6 +193,15 @@ typedef struct { u8 reserved_x16[0xA]; ///< Cleared to zero for the tmp struct. } LdnNetworkConfig; +/// ActionFrameSettings +typedef struct { + s64 local_communication_id; ///< LocalCommunicationId (Same handling as LdnNetworkConfig::local_communication_id) + u8 reserved[0x34]; ///< Reserved + u16 security_mode; ///< SecurityMode (Must be 1-2, internally this is overriden) + u16 passphrase_size; ///< PassphraseSize (Must be 0x10-0x40) + u8 passphrase[0x40]; ///< Passphrase +} LdnActionFrameSettings; + ///@name ldn:m ///@{ @@ -476,5 +485,71 @@ Result ldnDisconnect(void); */ Result ldnSetOperationMode(LdnOperationMode mode); +/** + * @brief EnableActionFrame + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_Initialized. + * @param[in] settings \ref LdnActionFrameSettings + */ +Result ldnEnableActionFrame(const LdnActionFrameSettings *settings); + +/** + * @brief DisableActionFrame + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_Initialized. + */ +Result ldnDisableActionFrame(void); + +/** + * @brief SendActionFrame + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_AccessPointCreated / ::LdnState_StationOpened. + * @param[in] data Data buffer. + * @param[in] size Data buffer size. + * @param[in] destination Destination \ref LdnMacAddress. + * @param[in] bssid Bssid \ref LdnMacAddress. + * @param[in] channel Channel, must be non-zero. + * @param[in] flags MessageFlag bit0 clear = block until the data can be sent, set = return error when the data can't be sent. + */ +Result ldnSendActionFrame(const void* data, size_t size, LdnMacAddress destination, LdnMacAddress bssid, s16 channel, u32 flags); + +/** + * @brief RecvActionFrame + * @note Only available on [18.0.0+]. + * @note \ref ldnEnableActionFrame must be used prior to this. + * @param[out] data Output data buffer. + * @param[in] size Max size of the data buffer. + * @param[out] addr0 First \ref LdnMacAddress. + * @param[out] addr1 Second \ref LdnMacAddress. + * @param[out] channel Channel + * @param[out] out_size Output size. + * @param[out] link_level LinkLevel + * @param[in] flags MessageFlag bit0 clear = block until data is available, set = return error when data is not available. + */ +Result ldnRecvActionFrame(void* data, size_t size, LdnMacAddress *addr0, LdnMacAddress *addr1, s16 *channel, u32 *out_size, s32 *link_level, u32 flags); + +/** + * @brief SetHomeChannel + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_StationOpened. + * @param[in] channel Channel, must be non-zero. + */ +Result ldnSetHomeChannel(s16 channel); + +/** + * @brief SetTxPower + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_AccessPoint* / ::LdnState_Station*. + * @param[in] power Power, must be 0x0..0xFF. + */ +Result ldnSetTxPower(s16 power); + +/** + * @brief ResetTxPower + * @note Only available on [18.0.0+]. + * @note \ref LdnState must be ::LdnState_AccessPoint* / ::LdnState_Station*. + */ +Result ldnResetTxPower(void); + ///@} diff --git a/nx/source/services/ldn.c b/nx/source/services/ldn.c index 705ddcfe..160a02d9 100644 --- a/nx/source/services/ldn.c +++ b/nx/source/services/ldn.c @@ -140,6 +140,10 @@ static Result _ldnCmdInU32NoOut(Service* srv, u32 in, u32 cmd_id) { return serviceDispatchIn(srv, cmd_id, in); } +static Result _ldnCmdInU16NoOut(Service* srv, u16 in, u32 cmd_id) { + return serviceDispatchIn(srv, cmd_id, in); +} + static Result _ldnCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { return serviceDispatchOut(srv, cmd_id, *out); } @@ -226,6 +230,26 @@ static void _ldnCopyNetworkConfig(const LdnNetworkConfig *in, LdnNetworkConfig * out->local_communication_version = in->local_communication_version; } +static s16 _ldnChannelToOldBand(s16 channel) { + if (channel < 15) return 24; + else return 50; +} + +static u16 _ldnChannelToChannelBand(s16 channel) { + if (!channel) return channel; + u16 tmp_channel = channel & 0x3FF; // Official sw just masks with u16. + + u16 band = 0x3F; // Invalid + if (tmp_channel < 15) band = 2; + else if (tmp_channel >= 32 && tmp_channel < 178) band = 5; + + return channel | (band<<10); +} + +static s16 _ldnChannelBandToChannel(u16 val) { + return val & 0x3FF; +} + Result ldnGetState(LdnState *out) { u32 tmp=0; Result rc = _ldnCmdNoInOutU32(&g_ldnSrv, &tmp, 0); @@ -426,3 +450,132 @@ Result ldnSetOperationMode(LdnOperationMode mode) { return _ldnCmdInU32NoOut(&g_ldnSrv, mode, 402); } +Result ldnEnableActionFrame(const LdnActionFrameSettings *settings) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return serviceDispatchIn(&g_ldnSrv, 500, *settings); +} + +Result ldnDisableActionFrame(void) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _ldnCmdNoIO(&g_ldnSrv, 501); +} + +Result ldnSendActionFrame(const void* data, size_t size, LdnMacAddress destination, LdnMacAddress bssid, s16 channel, u32 flags) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + s16 tmp_band = 0, tmp_channel = 0; + if (hosversionBefore(20,0,0)) { + tmp_channel = channel; + tmp_band = _ldnChannelToOldBand(channel); + } + else + tmp_band = _ldnChannelToChannelBand(channel); + + const struct { + LdnMacAddress destination; + LdnMacAddress bssid; + s16 band; // These are a single u16 with [20.0.0+]. + s16 channel; + u32 flags; + } in = { destination, bssid, tmp_band, tmp_channel, flags}; + + return serviceDispatchIn(&g_ldnSrv, 502, in, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = { { data, size } }, + ); +} + +Result ldnRecvActionFrame(void* data, size_t size, LdnMacAddress *addr0, LdnMacAddress *addr1, s16 *channel, u32 *out_size, s32 *link_level, u32 flags) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + struct { + LdnMacAddress addr0; + LdnMacAddress addr1; + s16 band; // These are a single u16 with [20.0.0+]. + s16 channel; + u32 size; + s32 link_level; + } out; + + Result rc = serviceDispatchInOut(&g_ldnSrv, 503, flags, out, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = { { data, size } }, + ); + if (R_SUCCEEDED(rc)) { + if (addr0) *addr0 = out.addr0; + if (addr1) *addr1 = out.addr1; + if (channel) { + if (hosversionBefore(20,0,0)) + *channel = out.channel; + else + *channel = _ldnChannelBandToChannel(out.band); + } + if (out_size) *out_size = out.size; + if (link_level) *link_level = out.link_level; + } + return rc; +} + +Result ldnSetHomeChannel(s16 channel) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + s16 tmp_band = 0, tmp_channel = 0; + if (hosversionBefore(20,0,0)) { + tmp_channel = channel; + tmp_band = _ldnChannelToOldBand(channel); + + const struct { + s16 band; + s16 channel; + } in = { tmp_band, tmp_channel }; + + return serviceDispatchIn(&g_ldnSrv, 505, in); + } + else + tmp_band = _ldnChannelToChannelBand(channel); + + return _ldnCmdInU16NoOut(&g_ldnSrv, tmp_band, 505); +} + +Result ldnSetTxPower(s16 power) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _ldnCmdInU16NoOut(&g_ldnSrv, power, 600); +} + +Result ldnResetTxPower(void) { + if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(18,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _ldnCmdNoIO(&g_ldnSrv, 601); +} +