mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-21 12:32:40 +02:00
Added support for ldn.
This commit is contained in:
parent
3d726ed78c
commit
02ed902558
@ -112,6 +112,7 @@ extern "C" {
|
||||
#include "switch/services/notif.h"
|
||||
#include "switch/services/mii.h"
|
||||
#include "switch/services/miiimg.h"
|
||||
#include "switch/services/ldn.h"
|
||||
|
||||
#include "switch/display/binder.h"
|
||||
#include "switch/display/parcel.h"
|
||||
|
461
nx/include/switch/services/ldn.h
Normal file
461
nx/include/switch/services/ldn.h
Normal file
@ -0,0 +1,461 @@
|
||||
/**
|
||||
* @file ldn.h
|
||||
* @brief LDN (local network communications) IPC wrapper. See also: https://switchbrew.org/wiki/LDN_services
|
||||
* @author yellows8
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../types.h"
|
||||
#include "../sf/service.h"
|
||||
#include "../kernel/event.h"
|
||||
|
||||
typedef enum {
|
||||
LdnServiceType_User = 0, ///< Initializes ldn:u.
|
||||
LdnServiceType_System = 1, ///< Initializes ldn:s.
|
||||
} LdnServiceType;
|
||||
|
||||
/// State loaded by \ref ldnmGetStateForMonitor / \ref ldnGetState.
|
||||
typedef enum {
|
||||
LdnState_None = 0, ///< None
|
||||
LdnState_Initialized = 1, ///< Initialized
|
||||
LdnState_AccessPointOpened = 2, ///< AccessPointOpened (\ref ldnOpenAccessPoint)
|
||||
LdnState_AccessPointCreated = 3, ///< AccessPointCreated (\ref ldnCreateNetwork / \ref ldnCreateNetworkPrivate)
|
||||
LdnState_StationOpened = 4, ///< StationOpened (\ref ldnOpenStation)
|
||||
LdnState_StationConnected = 5, ///< StationConnected (\ref ldnConnect / \ref ldnConnectPrivate)
|
||||
LdnState_Error = 6, ///< Error
|
||||
} LdnState;
|
||||
|
||||
/// DisconnectReason loaded by \ref ldnGetDisconnectReason.
|
||||
typedef enum {
|
||||
LdnDisconnectReason_None = 0, ///< None
|
||||
LdnDisconnectReason_User = 1, ///< User
|
||||
LdnDisconnectReason_SystemRequest = 2, ///< SystemRequest
|
||||
LdnDisconnectReason_DestroyedByAdmin = 3, ///< DestroyedByAdmin
|
||||
LdnDisconnectReason_DestroyedBySystemRequest = 4, ///< DestroyedBySystemRequest
|
||||
LdnDisconnectReason_Admin = 5, ///< Admin
|
||||
LdnDisconnectReason_SignalLost = 6, ///< SignalLost
|
||||
} LdnDisconnectReason;
|
||||
|
||||
/// ScanFilterFlags
|
||||
typedef enum {
|
||||
LdnScanFilterFlags_LocalCommunicationId = BIT(0), ///< When set, enables using LdnScanFilter::local_communication_id.
|
||||
LdnScanFilterFlags_NetworkId = BIT(1), ///< When set, enables using LdnScanFilter::network_id.
|
||||
LdnScanFilterFlags_Unknown2 = BIT(2), ///< When set, enables using LdnScanFilter::unk_x20.
|
||||
LdnScanFilterFlags_MacAddr = BIT(3), ///< When set, enables using LdnScanFilter::mac_addr. Only available with \ref ldnScanPrivate.
|
||||
LdnScanFilterFlags_Ssid = BIT(4), ///< When set, enables using the LdnScanFilter::ssid.
|
||||
LdnScanFilterFlags_Unknown5 = BIT(5), ///< When set, enables using LdnScanFilter::unk_xA.
|
||||
} LdnScanFilterFlags;
|
||||
|
||||
/// AcceptPolicy
|
||||
typedef enum {
|
||||
LdnAcceptPolicy_Unknown0 = 0, ///< Unknown
|
||||
LdnAcceptPolicy_Unknown1 = 1, ///< Unknown
|
||||
LdnAcceptPolicy_Unknown2 = 2, ///< Unknown
|
||||
LdnAcceptPolicy_Unknown3 = 3, ///< Unknown
|
||||
} LdnAcceptPolicy;
|
||||
|
||||
/// OperationMode
|
||||
typedef enum {
|
||||
LdnOperationMode_Unknown0 = 0, ///< Unknown
|
||||
LdnOperationMode_Unknown1 = 1, ///< Unknown
|
||||
} LdnOperationMode;
|
||||
|
||||
/// WirelessControllerRestriction
|
||||
typedef enum {
|
||||
LdnWirelessControllerRestriction_Unknown0 = 0, ///< Unknown
|
||||
LdnWirelessControllerRestriction_Unknown1 = 1, ///< Unknown
|
||||
} LdnWirelessControllerRestriction;
|
||||
|
||||
/// Ipv4Address. This is essentially the same as struct in_addr - hence this can be used with standard sockets (byteswap required).
|
||||
typedef struct {
|
||||
u32 addr; ///< Address
|
||||
} LdnIpv4Address;
|
||||
|
||||
/// SubnetMask. This is essentially the same as struct in_addr - hence this can be used with standard sockets (byteswap required).
|
||||
typedef struct {
|
||||
u32 mask; ///< Mask
|
||||
} LdnSubnetMask;
|
||||
|
||||
/// MacAddress
|
||||
typedef struct {
|
||||
u8 addr[6]; ///< Address
|
||||
} LdnMacAddress;
|
||||
|
||||
/// Ssid
|
||||
typedef struct {
|
||||
u8 len; ///< Length excluding NUL-terminator, must be 0x1-0x20.
|
||||
char str[0x21]; ///< SSID string including NUL-terminator, str[len_field] must be 0. The chars in this string must be be in the range of 0x20-0x7F, for when the Ssid is converted to a string (otherwise the byte written to the string will be 0).
|
||||
} LdnSsid;
|
||||
|
||||
/// NodeLatestUpdate
|
||||
typedef struct {
|
||||
u8 flag; ///< Flag, the field in state is reset to zero by \ref ldnGetNetworkInfoLatestUpdate after loading it.
|
||||
u8 reserved[0x7]; ///< Not initialized with \ref ldnGetNetworkInfoLatestUpdate.
|
||||
} LdnNodeLatestUpdate;
|
||||
|
||||
/// AddressEntry
|
||||
typedef struct {
|
||||
LdnIpv4Address ip_addr; ///< \ref LdnIpv4Address
|
||||
LdnMacAddress mac_addr; ///< \ref LdnMacAddress
|
||||
u8 pad[0x2]; ///< Padding
|
||||
} LdnAddressEntry;
|
||||
|
||||
/// NodeInfo
|
||||
typedef struct {
|
||||
LdnIpv4Address ip_addr; ///< \ref LdnIpv4Address
|
||||
LdnMacAddress mac_addr; ///< \ref LdnMacAddress
|
||||
s8 id; ///< ID / index
|
||||
u8 is_connected; ///< IsConnected flag
|
||||
char nickname[0x20]; ///< LdnUserConfig::nickname
|
||||
u8 unk_x2C[0x2]; ///< Unknown
|
||||
s16 local_communication_version; ///< LocalCommunicationVersion
|
||||
u8 unk_x30[0x10]; ///< Unknown
|
||||
} LdnNodeInfo;
|
||||
|
||||
/// UserConfig. The input struct is copied to a tmp struct, which is then used with the cmd.
|
||||
typedef struct {
|
||||
char nickname[0x20]; ///< NUL-terminated string for the user nickname.
|
||||
u8 reserved[0x10]; ///< Cleared to zero for the tmp struct.
|
||||
} LdnUserConfig;
|
||||
|
||||
/// NetworkInfo
|
||||
typedef struct {
|
||||
u64 local_communication_id; ///< LocalCommunicationId
|
||||
u8 unk_x8[0x2]; ///< Unknown
|
||||
u16 unk_xA; ///< Unknown
|
||||
u8 unk_xC[0x4]; ///< Unknown
|
||||
u8 network_id[0x10]; ///< Last 0x10-bytes of LdnSecurityParameter::data. NetworkId which is used to generate/overwrite the ssid. With \ref ldnScan / \ref ldnScanPrivate, this is only done after filtering when unk_x4B is value 0x2.
|
||||
LdnMacAddress mac_addr; ///< \ref LdnMacAddress
|
||||
LdnSsid ssid; ///< \ref LdnSsid
|
||||
s16 network_channel; ///< NetworkChannel
|
||||
s8 link_level; ///< LinkLevel
|
||||
u8 unk_x4B; ///< Unknown
|
||||
u8 unk_x4C[0x4]; ///< Unknown
|
||||
u8 unk_x50[0x10]; ///< First 0x10-bytes of LdnSecurityParameter::data.
|
||||
u16 sec_type; ///< LdnSecurityConfig::type
|
||||
u8 unk_x62[0x4]; ///< Unknown
|
||||
s8 unk_x66; ///< Unknown
|
||||
u8 participant_num; ///< ParticipantNum, number of set entries in nodes. If unk_x4B is not 0x2, ParticipantNum should be handled as if it's 0.
|
||||
LdnNodeInfo nodes[8]; ///< Array of \ref LdnNodeInfo, starting with the AccessPoint node.
|
||||
u8 unk_x268[0x2]; ///< Unknown
|
||||
u16 advertise_data_size; ///< AdvertiseData size (\ref ldnSetAdvertiseData)
|
||||
u8 advertise_data[0x180]; ///< AdvertiseData (\ref ldnSetAdvertiseData)
|
||||
u8 unk_x3EC[0x8C]; ///< Unknown
|
||||
u64 unk_x478; ///< Unknown
|
||||
} LdnNetworkInfo;
|
||||
|
||||
/// ScanFilter. The input struct is copied to a tmp struct, which is then used with the cmd (\ref ldnScan and \ref ldnScanPrivate).
|
||||
typedef struct {
|
||||
s64 local_communication_id; ///< See ::LdnScanFilterFlags_LocalCommunicationId. When enabled, this will be overwritten if it's -1 (written data is from the user-process control.nacp, with value 0 used instead if loading fails). During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter field must match LdnNetworkInfo::local_communication_id.
|
||||
u8 pad_x8[0x2]; ///< Padding
|
||||
u16 unk_xA; ///< See ::LdnScanFilterFlags_Unknown5. During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter field must match LdnNetworkInfo::unk_xA.
|
||||
u8 pad_xC[0x4]; ///< Padding
|
||||
u8 network_id[0x10]; ///< See ::LdnScanFilterFlags_NetworkId. During filtering if enabled, LdnNetworkInfo::unk_x4B must match 0x2, and this ScanFilter data must match LdnNetworkInfo::network_id.
|
||||
u32 unk_x20; ///< See ::LdnScanFilterFlags_Unknown2. When enabled, this must be <=0x3, and during filtering must match LdnNetworkInfo::unk_x4B.
|
||||
LdnMacAddress mac_addr; ///< \ref LdnMacAddress (::LdnScanFilterFlags_MacAddr, during filtering if enabled this must match LdnNetworkInfo::mac_addr)
|
||||
LdnSsid ssid; ///< \ref LdnSsid (::LdnScanFilterFlags_Ssid, during filtering if enabled this must match LdnNetworkInfo::ssid)
|
||||
u8 reserved[0x10]; ///< Cleared to zero for the tmp struct.
|
||||
u32 flags; ///< Bitmask for \ref LdnScanFilterFlags. Masked with value 0x37 for \ref ldnScan, with \ref ldnScanPrivate this is masked with 0x3F.
|
||||
} LdnScanFilter;
|
||||
|
||||
/// SecurityConfig
|
||||
typedef struct {
|
||||
u16 type; ///< Type, a default of value 0x1 can be used here. Overwritten by \ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnect, \ref ldnConnectPrivate.
|
||||
u16 data_size; ///< Data size. Must be 0x10-0x40.
|
||||
u8 data[0x40]; ///< Data, used with key derivation.
|
||||
} LdnSecurityConfig;
|
||||
|
||||
/// SecurityParameter
|
||||
typedef struct {
|
||||
u8 data[0x20]; ///< Data. The data used by \ref ldnCreateNetwork internally is randomly-generated.
|
||||
} LdnSecurityParameter;
|
||||
|
||||
/// NetworkConfig. The input struct is copied to a tmp struct, which is then used with the cmd (\ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnectPrivate).
|
||||
typedef struct {
|
||||
s64 local_communication_id; ///< LdnNetworkInfo::local_communication_id. \ref ldnCreateNetwork, \ref ldnCreateNetworkPrivate, \ref ldnConnect, \ref ldnConnectPrivate: When -1, this is overwritten with the first LocalCommunicationId from the user-process control.nacp, if loading fails value 0 is written instead. Otherwise when not -1, if control.nacp loading is successful, this field must match one of the LocalCommunicationIds from there.
|
||||
u8 reserved_x8[2]; ///< Cleared to zero for the tmp struct.
|
||||
u16 unk_xA; ///< LdnNetworkInfo::unk_xA
|
||||
u8 reserved_xC[4]; ///< Cleared to zero for the tmp struct.
|
||||
s16 network_channel; ///< LdnNetworkInfo::network_channel. Channel, can be zero. Overwritten internally by \ref ldnCreateNetwork.
|
||||
s8 unk_x12; ///< LdnNetworkInfo::unk_x66. \ref ldnCreateNetwork / \ref ldnCreateNetworkPrivate: Must be 0x1-0x8.
|
||||
u8 reserved_x13; ///< Cleared to zero for the tmp struct.
|
||||
s16 local_communication_version; ///< LdnNetworkInfo::local_communication_version. Must not be negative.
|
||||
u8 reserved_x16[0xA]; ///< Cleared to zero for the tmp struct.
|
||||
} LdnNetworkConfig;
|
||||
|
||||
///@name ldn:m
|
||||
///@{
|
||||
|
||||
/// Initialize ldn:m.
|
||||
Result ldnmInitialize(void);
|
||||
|
||||
/// Exit ldn:m.
|
||||
void ldnmExit(void);
|
||||
|
||||
/// Gets the Service object for IMonitorService.
|
||||
Service* ldnmGetServiceSession_MonitorService(void);
|
||||
|
||||
/**
|
||||
* @brief GetStateForMonitor
|
||||
* @param[out] out \ref LdnState
|
||||
*/
|
||||
Result ldnmGetStateForMonitor(LdnState *out);
|
||||
|
||||
/**
|
||||
* @brief GetNetworkInfoForMonitor
|
||||
* @param[out] out \ref LdnNetworkInfo
|
||||
*/
|
||||
Result ldnmGetNetworkInfoForMonitor(LdnNetworkInfo *out);
|
||||
|
||||
/**
|
||||
* @brief GetIpv4AddressForMonitor
|
||||
* @param[out] addr \ref LdnIpv4Address
|
||||
* @param[out] mask \ref LdnSubnetMask
|
||||
*/
|
||||
Result ldnmGetIpv4AddressForMonitor(LdnIpv4Address *addr, LdnSubnetMask *mask);
|
||||
|
||||
/**
|
||||
* @brief GetSecurityParameterForMonitor
|
||||
* @note Not exposed by official sw.
|
||||
* @param[out] out \ref LdnSecurityParameter
|
||||
*/
|
||||
Result ldnmGetSecurityParameterForMonitor(LdnSecurityParameter *out);
|
||||
|
||||
/**
|
||||
* @brief GetNetworkConfigForMonitor
|
||||
* @note Not exposed by official sw.
|
||||
* @param[out] out \ref LdnNetworkConfig
|
||||
*/
|
||||
Result ldnmGetNetworkConfigForMonitor(LdnNetworkConfig *out);
|
||||
|
||||
///@}
|
||||
|
||||
///@name ldn
|
||||
///@{
|
||||
|
||||
/// Initialize ldn.
|
||||
Result ldnInitialize(LdnServiceType service_type);
|
||||
|
||||
/// Exit ldn.
|
||||
void ldnExit(void);
|
||||
|
||||
/// Gets the Service object for IUserLocalCommunicationService/ISystemLocalCommunicationService.
|
||||
Service* ldnGetServiceSession_LocalCommunicationService(void);
|
||||
|
||||
/**
|
||||
* @brief GetState
|
||||
* @param[out] out \ref LdnState
|
||||
*/
|
||||
Result ldnGetState(LdnState *out);
|
||||
|
||||
/**
|
||||
* @brief GetNetworkInfo
|
||||
* @param[out] out \ref LdnNetworkInfo
|
||||
*/
|
||||
Result ldnGetNetworkInfo(LdnNetworkInfo *out);
|
||||
|
||||
/**
|
||||
* @brief GetIpv4Address
|
||||
* @param[out] addr \ref LdnIpv4Address
|
||||
* @param[out] mask \ref LdnSubnetMask
|
||||
*/
|
||||
Result ldnGetIpv4Address(LdnIpv4Address *addr, LdnSubnetMask *mask);
|
||||
|
||||
/**
|
||||
* @brief GetDisconnectReason
|
||||
* @param[out] out \ref LdnDisconnectReason
|
||||
*/
|
||||
Result ldnGetDisconnectReason(LdnDisconnectReason *out);
|
||||
|
||||
/**
|
||||
* @brief GetSecurityParameter
|
||||
* @param[out] out \ref LdnSecurityParameter
|
||||
*/
|
||||
Result ldnGetSecurityParameter(LdnSecurityParameter *out);
|
||||
|
||||
/**
|
||||
* @brief GetNetworkConfig
|
||||
* @param[out] out \ref LdnNetworkConfig
|
||||
*/
|
||||
Result ldnGetNetworkConfig(LdnNetworkConfig *out);
|
||||
|
||||
/**
|
||||
* @brief AttachStateChangeEvent
|
||||
* @note The Event must be closed by the user once finished with it.
|
||||
* @param[out] out_event Output Event with autoclear=true.
|
||||
*/
|
||||
Result ldnAttachStateChangeEvent(Event* out_event);
|
||||
|
||||
/**
|
||||
* @brief GetNetworkInfoLatestUpdate
|
||||
* @param[out] network_info \ref LdnNetworkInfo
|
||||
* @param[out] nodes Output array of \ref LdnNodeLatestUpdate.
|
||||
* @param[in] count Size of the nodes array in entries, must be 8.
|
||||
*/
|
||||
Result ldnGetNetworkInfoLatestUpdate(LdnNetworkInfo *network_info, LdnNodeLatestUpdate *nodes, s32 count);
|
||||
|
||||
/**
|
||||
* @brief Scan
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointCreated, ::LdnState_StationOpened, or ::LdnState_StationConnected.
|
||||
* @note This is the same as \ref ldnScanPrivate (minus the masking for LdnScanFilter::flags), except this has the same channel-override functionality as \ref ldnCreateNetwork.
|
||||
* @param[in] channel Channel, value 0 can be used for this.
|
||||
* @param[in] filter \ref LdnScanFilter
|
||||
* @param[out] network_info Output array of \ref LdnNetworkInfo.
|
||||
* @param[in] count Size of the network_info array in entries. Must be at least 1, this is clamped to a maximum of 0x18 internally.
|
||||
* @param[out] total_out Total output entries.
|
||||
*/
|
||||
Result ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out);
|
||||
|
||||
/**
|
||||
* @brief ScanPrivate
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointCreated, ::LdnState_StationOpened, or ::LdnState_StationConnected.
|
||||
* @note See \ref ldnScan.
|
||||
* @param[in] channel Channel, value 0 can be used for this.
|
||||
* @param[in] filter \ref LdnScanFilter
|
||||
* @param[out] network_info Output array of \ref LdnNetworkInfo.
|
||||
* @param[in] count Size of the network_info array in entries. Must be at least 1, this is clamped to a maximum of 0x18 internally.
|
||||
* @param[out] total_out Total output entries.
|
||||
*/
|
||||
Result ldnScanPrivate(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out);
|
||||
|
||||
/**
|
||||
* @brief SetWirelessControllerRestriction
|
||||
* @note Only available on [5.0.0+].
|
||||
* @note \ref LdnState must be ::LdnState_Initialized.
|
||||
* @param[in] restriction \ref LdnWirelessControllerRestriction
|
||||
*/
|
||||
Result ldnSetWirelessControllerRestriction(LdnWirelessControllerRestriction restriction);
|
||||
|
||||
/**
|
||||
* @brief OpenAccessPoint
|
||||
* @note \ref LdnState must be ::LdnState_Initialized, this eventually sets the State to ::LdnState_AccessPointOpened.
|
||||
*/
|
||||
Result ldnOpenAccessPoint(void);
|
||||
|
||||
/**
|
||||
* @brief CloseAccessPoint
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated, this eventually sets the State to ::LdnState_Initialized.
|
||||
* @note Used automatically internally by \ref ldnExit if needed.
|
||||
*/
|
||||
Result ldnCloseAccessPoint(void);
|
||||
|
||||
/**
|
||||
* @brief CreateNetwork
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened, this eventually sets the State to ::LdnState_AccessPointCreated.
|
||||
* @param[in] sec_config \ref LdnSecurityConfig
|
||||
* @param[in] user_config \ref LdnUserConfig
|
||||
* @param[in] network_config \ref LdnNetworkConfig
|
||||
*/
|
||||
Result ldnCreateNetwork(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config);
|
||||
|
||||
/**
|
||||
* @brief CreateNetworkPrivate
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened, this eventually sets the State to ::LdnState_AccessPointCreated.
|
||||
* @note This is the same as \ref ldnCreateNetwork besides the additional user-specified params, and with this cmd LdnNetworkConfig::channel is not overwritten (unlike \ref ldnCreateNetwork).
|
||||
* @param[in] sec_config \ref LdnSecurityConfig
|
||||
* @param[in] sec_param \ref LdnSecurityParameter
|
||||
* @param[in] user_config \ref LdnUserConfig
|
||||
* @param[in] network_config \ref LdnNetworkConfig
|
||||
* @param[in] addrs Input array of \ref LdnAddressEntry. This can be NULL.
|
||||
* @param[in] count Size of the addrs array in entries. This must be <=8. This can be 0, in which case the network will be non-Private like \ref ldnCreateNetwork.
|
||||
*/
|
||||
Result ldnCreateNetworkPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config, const LdnAddressEntry *addrs, s32 count);
|
||||
|
||||
/**
|
||||
* @brief DestroyNetwork
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointCreated, this eventually sets the State to ::LdnState_AccessPointOpened.
|
||||
*/
|
||||
Result ldnDestroyNetwork(void);
|
||||
|
||||
/**
|
||||
* @brief Reject
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointCreated.
|
||||
* @param[in] addr \ref LdnIpv4Address
|
||||
*/
|
||||
Result ldnReject(LdnIpv4Address addr);
|
||||
|
||||
/**
|
||||
* @brief SetAdvertiseData
|
||||
* @note An empty buffer (buffer=NULL/size=0) can be used to reset the AdvertiseData size in state to zero.
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated.
|
||||
* @param[in] buffer Input buffer containing arbitrary user data.
|
||||
* @param[in] size Input buffer size, must be <=0x180. If this isn't enough space, you can for example also periodically use this cmd with different regions of your data with some sequence_number field (or use sockets while connected to the network).
|
||||
*/
|
||||
Result ldnSetAdvertiseData(const void* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief SetStationAcceptPolicy
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated.
|
||||
* @param[in] policy \ref LdnAcceptPolicy
|
||||
*/
|
||||
Result ldnSetStationAcceptPolicy(LdnAcceptPolicy policy);
|
||||
|
||||
/**
|
||||
* @brief AddAcceptFilterEntry
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated.
|
||||
* @param[in] addr \ref LdnMacAddress. If you want, you can also pass LdnNodeInfo::mac_addr for this.
|
||||
*/
|
||||
Result ldnAddAcceptFilterEntry(LdnMacAddress addr);
|
||||
|
||||
/**
|
||||
* @brief ClearAcceptFilter
|
||||
* @note \ref LdnState must be ::LdnState_AccessPointOpened or ::LdnState_AccessPointCreated.
|
||||
*/
|
||||
Result ldnClearAcceptFilter(void);
|
||||
|
||||
/**
|
||||
* @brief OpenStation
|
||||
* @note \ref LdnState must be ::LdnState_Initialized, this eventually sets the State to ::LdnState_StationOpened.
|
||||
*/
|
||||
Result ldnOpenStation(void);
|
||||
|
||||
/**
|
||||
* @brief CloseStation
|
||||
* @note \ref LdnState must be ::LdnState_StationOpened or ::LdnState_StationConnected, this eventually sets the State to ::LdnState_Initialized.
|
||||
* @note Used automatically internally by \ref ldnExit if needed.
|
||||
*/
|
||||
Result ldnCloseStation(void);
|
||||
|
||||
/**
|
||||
* @brief Connect
|
||||
* @note \ref LdnState must be ::LdnState_StationOpened, this eventually sets the State to ::LdnState_StationConnected.
|
||||
* @note This is identical to \ref ldnConnectPrivate besides the used params, the code overwriting LdnSecurityConfig::type also differs.
|
||||
* @param[in] sec_config \ref LdnSecurityConfig
|
||||
* @param[in] user_config \ref LdnUserConfig
|
||||
* @param[in] version LocalCommunicationVersion, this must be 0x0-0x7FFF.
|
||||
* @param[in] option ConnectOption bitmask, must be <=0x1. You can use value 0 for example here.
|
||||
* @param[in] network_info \ref LdnNetworkInfo
|
||||
*/
|
||||
Result ldnConnect(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkInfo *network_info);
|
||||
|
||||
/**
|
||||
* @brief ConnectPrivate
|
||||
* @note \ref LdnState must be ::LdnState_StationOpened, this eventually sets the State to ::LdnState_StationConnected.
|
||||
* @note See \ref ldnConnect.
|
||||
* @param[in] sec_config \ref LdnSecurityConfig
|
||||
* @param[in] sec_param \ref LdnSecurityParameter
|
||||
* @param[in] user_config \ref LdnUserConfig
|
||||
* @param[in] version LocalCommunicationVersion, this must be 0x0-0x7FFF.
|
||||
* @param[in] option ConnectOption bitmask, must be <=0x1. You can use value 0 for example here.
|
||||
* @param[in] network_config \ref LdnNetworkConfig
|
||||
*/
|
||||
Result ldnConnectPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkConfig *network_config);
|
||||
|
||||
/**
|
||||
* @brief Disconnect
|
||||
* @note \ref LdnState must be ::LdnState_StationConnected, this eventually sets the State to ::LdnState_StationOpened.
|
||||
*/
|
||||
Result ldnDisconnect(void);
|
||||
|
||||
/**
|
||||
* @brief SetOperationMode
|
||||
* @note Only available on [4.0.0+].
|
||||
* @note Only available with ::LdnServiceType_System.
|
||||
* @note \ref LdnState must be ::LdnState_Initialized.
|
||||
* @param[in] mode \ref LdnOperationMode
|
||||
*/
|
||||
Result ldnSetOperationMode(LdnOperationMode mode);
|
||||
|
||||
///@}
|
||||
|
421
nx/source/services/ldn.c
Normal file
421
nx/source/services/ldn.c
Normal file
@ -0,0 +1,421 @@
|
||||
#define NX_SERVICE_ASSUME_NON_DOMAIN
|
||||
#include <string.h>
|
||||
#include "service_guard.h"
|
||||
#include "services/ldn.h"
|
||||
#include "runtime/hosversion.h"
|
||||
|
||||
static LdnServiceType g_ldnServiceType;
|
||||
|
||||
static Service g_ldnSrv;
|
||||
static Service g_ldnmSrv;
|
||||
|
||||
static Result _ldnGetSession(Service* srv, Service* srv_out, u32 cmd_id);
|
||||
|
||||
static Result _ldnCmdNoIO(Service* srv, u32 cmd_id);
|
||||
|
||||
static Result _ldnCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id);
|
||||
|
||||
static Result _ldnCmdInitialize(void);
|
||||
static Result _ldnCmdInitialize2(u32 inval);
|
||||
|
||||
static Result _ldnGetNetworkInfo(Service* srv, LdnNetworkInfo *out);
|
||||
|
||||
static Result _ldnGetIpv4Address(Service* srv, LdnIpv4Address *addr, LdnSubnetMask *mask);
|
||||
|
||||
static Result _ldnGetSecurityParameter(Service* srv, LdnSecurityParameter *out);
|
||||
static Result _ldnGetNetworkConfig(Service* srv, LdnNetworkConfig *out);
|
||||
|
||||
// ldn:m
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(ldnm);
|
||||
|
||||
Result _ldnmInitialize(void) {
|
||||
Service srv_creator={0};
|
||||
Result rc = smGetService(&srv_creator, "ldn:m");
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = _ldnGetSession(&srv_creator, &g_ldnmSrv, 0); // CreateMonitorService
|
||||
serviceClose(&srv_creator);
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = _ldnCmdNoIO(&g_ldnmSrv, 100); // InitializeMonitor
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _ldnmCleanup(void) {
|
||||
if (serviceIsActive(&g_ldnmSrv)) _ldnCmdNoIO(&g_ldnmSrv, 101); // FinalizeMonitor
|
||||
serviceClose(&g_ldnmSrv);
|
||||
}
|
||||
|
||||
Service* ldnmGetServiceSession_MonitorService(void) {
|
||||
return &g_ldnmSrv;
|
||||
}
|
||||
|
||||
Result ldnmGetStateForMonitor(LdnState *out) {
|
||||
u32 tmp=0;
|
||||
Result rc = _ldnCmdNoInOutU32(&g_ldnmSrv, &tmp, 0);
|
||||
if (R_SUCCEEDED(rc) && out) *out = tmp;
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ldnmGetNetworkInfoForMonitor(LdnNetworkInfo *out) {
|
||||
return _ldnGetNetworkInfo(&g_ldnmSrv, out);
|
||||
}
|
||||
|
||||
Result ldnmGetIpv4AddressForMonitor(LdnIpv4Address *addr, LdnSubnetMask *mask) {
|
||||
return _ldnGetIpv4Address(&g_ldnmSrv, addr, mask);
|
||||
}
|
||||
|
||||
Result ldnmGetSecurityParameterForMonitor(LdnSecurityParameter *out) {
|
||||
return _ldnGetSecurityParameter(&g_ldnmSrv, out);
|
||||
}
|
||||
|
||||
Result ldnmGetNetworkConfigForMonitor(LdnNetworkConfig *out) {
|
||||
return _ldnGetNetworkConfig(&g_ldnmSrv, out);
|
||||
}
|
||||
|
||||
// ldn
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD_PARAMS(ldn, (LdnServiceType service_type), (service_type));
|
||||
|
||||
Result _ldnInitialize(LdnServiceType service_type) {
|
||||
Service srv_creator={0};
|
||||
Result rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||
g_ldnServiceType = service_type;
|
||||
switch (g_ldnServiceType) {
|
||||
case LdnServiceType_User:
|
||||
rc = smGetService(&srv_creator, "ldn:u");
|
||||
break;
|
||||
case LdnServiceType_System:
|
||||
rc = smGetService(&srv_creator, "ldn:s");
|
||||
break;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = _ldnGetSession(&srv_creator, &g_ldnSrv, 0); // CreateSystemLocalCommunicationService/CreateUserLocalCommunicationService
|
||||
serviceClose(&srv_creator);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (hosversionAtLeast(7,0,0))
|
||||
rc = _ldnCmdInitialize2(0x1);
|
||||
else
|
||||
rc = _ldnCmdInitialize();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _ldnCleanup(void) {
|
||||
if (serviceIsActive(&g_ldnSrv)) _ldnCmdNoIO(&g_ldnSrv, 401); // Finalize(System)
|
||||
serviceClose(&g_ldnSrv);
|
||||
}
|
||||
|
||||
Service* ldnGetServiceSession_LocalCommunicationService(void) {
|
||||
return &g_ldnSrv;
|
||||
}
|
||||
|
||||
static Result _ldnGetSession(Service* srv, Service* srv_out, u32 cmd_id) {
|
||||
return serviceDispatch(srv, cmd_id,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = srv_out,
|
||||
);
|
||||
}
|
||||
|
||||
static Result _ldnCmdGetEvent(Service* srv, Event* out_event, bool autoclear, u32 cmd_id) {
|
||||
Handle event = INVALID_HANDLE;
|
||||
Result rc = serviceDispatch(srv, cmd_id,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = &event,
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
eventLoadRemote(out_event, event, autoclear);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _ldnCmdNoIO(Service* srv, u32 cmd_id) {
|
||||
return serviceDispatch(srv, cmd_id);
|
||||
}
|
||||
|
||||
static Result _ldnCmdInU32NoOut(Service* srv, u32 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);
|
||||
}
|
||||
|
||||
static Result _ldnCmdInitialize(void) {
|
||||
u64 reserved=0;
|
||||
return serviceDispatchIn(&g_ldnSrv, 400, reserved,
|
||||
.in_send_pid = true,
|
||||
);
|
||||
}
|
||||
|
||||
static Result _ldnCmdInitialize2(u32 inval) {
|
||||
const struct {
|
||||
u32 inval;
|
||||
u32 pad;
|
||||
u64 reserved;
|
||||
} in = { inval, 0, 0};
|
||||
|
||||
u32 cmd_id = g_ldnServiceType == LdnServiceType_User ? 402 : 403;
|
||||
return serviceDispatchIn(&g_ldnSrv, cmd_id, in,
|
||||
.in_send_pid = true,
|
||||
);
|
||||
}
|
||||
|
||||
static Result _ldnGetNetworkInfo(Service* srv, LdnNetworkInfo *out) {
|
||||
return serviceDispatch(srv, 1,
|
||||
.buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out },
|
||||
.buffers = { { out, sizeof(*out) } },
|
||||
);
|
||||
}
|
||||
|
||||
static Result _ldnGetIpv4Address(Service* srv, LdnIpv4Address *addr, LdnSubnetMask *mask) {
|
||||
struct {
|
||||
LdnIpv4Address addr;
|
||||
LdnSubnetMask mask;
|
||||
} out;
|
||||
|
||||
Result rc = serviceDispatchOut(srv, 2, out);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (addr) *addr = out.addr;
|
||||
if (mask) *mask = out.mask;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _ldnGetDisconnectReason(Service* srv, LdnDisconnectReason *out) {
|
||||
s16 tmp=0;
|
||||
Result rc = serviceDispatchOut(srv, 3, tmp);
|
||||
if (R_SUCCEEDED(rc) && out) *out = tmp;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static Result _ldnGetSecurityParameter(Service* srv, LdnSecurityParameter *out) {
|
||||
return serviceDispatchOut(srv, 4, *out);
|
||||
}
|
||||
|
||||
static Result _ldnGetNetworkConfig(Service* srv, LdnNetworkConfig *out) {
|
||||
return serviceDispatchOut(srv, 5, *out);
|
||||
}
|
||||
|
||||
static Result _ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out, u32 cmd_id) {
|
||||
const struct {
|
||||
s16 channel;
|
||||
u8 pad[6];
|
||||
LdnScanFilter filter;
|
||||
} in = { channel, {0}, *filter};
|
||||
|
||||
s16 out=0;
|
||||
Result rc = serviceDispatchInOut(&g_ldnSrv, cmd_id, in, out,
|
||||
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out },
|
||||
.buffers = { { network_info, count*sizeof(LdnNetworkInfo) } },
|
||||
);
|
||||
if (R_SUCCEEDED(rc) && total_out) *total_out = out;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void _ldnCopyNetworkConfig(const LdnNetworkConfig *in, LdnNetworkConfig *out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
out->local_communication_id = in->local_communication_id;
|
||||
out->unk_xA = in->unk_xA;
|
||||
out->network_channel = in->network_channel;
|
||||
out->unk_x12 = in->unk_x12;
|
||||
out->local_communication_version = in->local_communication_version;
|
||||
}
|
||||
|
||||
Result ldnGetState(LdnState *out) {
|
||||
u32 tmp=0;
|
||||
Result rc = _ldnCmdNoInOutU32(&g_ldnSrv, &tmp, 0);
|
||||
if (R_SUCCEEDED(rc) && out) *out = tmp;
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ldnGetNetworkInfo(LdnNetworkInfo *out) {
|
||||
return _ldnGetNetworkInfo(&g_ldnSrv, out);
|
||||
}
|
||||
|
||||
Result ldnGetIpv4Address(LdnIpv4Address *addr, LdnSubnetMask *mask) {
|
||||
return _ldnGetIpv4Address(&g_ldnSrv, addr, mask);
|
||||
}
|
||||
|
||||
Result ldnGetDisconnectReason(LdnDisconnectReason *out) {
|
||||
return _ldnGetDisconnectReason(&g_ldnSrv, out);
|
||||
}
|
||||
|
||||
Result ldnGetSecurityParameter(LdnSecurityParameter *out) {
|
||||
return _ldnGetSecurityParameter(&g_ldnSrv, out);
|
||||
}
|
||||
|
||||
Result ldnGetNetworkConfig(LdnNetworkConfig *out) {
|
||||
return _ldnGetNetworkConfig(&g_ldnSrv, out);
|
||||
}
|
||||
|
||||
Result ldnAttachStateChangeEvent(Event* out_event) {
|
||||
return _ldnCmdGetEvent(&g_ldnSrv, out_event, true, 100);
|
||||
}
|
||||
|
||||
Result ldnGetNetworkInfoLatestUpdate(LdnNetworkInfo *network_info, LdnNodeLatestUpdate *nodes, s32 count) {
|
||||
return serviceDispatch(&g_ldnSrv, 101,
|
||||
.buffer_attrs = {
|
||||
SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out,
|
||||
SfBufferAttr_HipcPointer | SfBufferAttr_Out,
|
||||
},
|
||||
.buffers = {
|
||||
{ network_info, sizeof(*network_info) },
|
||||
{ nodes, count*sizeof(LdnNodeLatestUpdate) },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Result ldnScan(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out) {
|
||||
LdnScanFilter tmp_filter = *filter;
|
||||
tmp_filter.flags &= 0x37;
|
||||
return _ldnScan(channel, &tmp_filter, network_info, count, total_out, 102);
|
||||
}
|
||||
|
||||
Result ldnScanPrivate(s32 channel, const LdnScanFilter *filter, LdnNetworkInfo *network_info, s32 count, s32 *total_out) {
|
||||
LdnScanFilter tmp_filter = *filter;
|
||||
tmp_filter.flags &= 0x3F;
|
||||
return _ldnScan(channel, filter, network_info, count, total_out, 103);
|
||||
}
|
||||
|
||||
Result ldnSetWirelessControllerRestriction(LdnWirelessControllerRestriction restriction) {
|
||||
if (hosversionBefore(5,0,0))
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
|
||||
return _ldnCmdInU32NoOut(&g_ldnSrv, restriction, 104);
|
||||
}
|
||||
|
||||
Result ldnOpenAccessPoint(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 200);
|
||||
}
|
||||
|
||||
Result ldnCloseAccessPoint(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 201);
|
||||
}
|
||||
|
||||
Result ldnCreateNetwork(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config) {
|
||||
LdnNetworkConfig tmp_network_config;
|
||||
LdnUserConfig tmp_user={0};
|
||||
memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname));
|
||||
_ldnCopyNetworkConfig(network_config, &tmp_network_config);
|
||||
|
||||
const struct {
|
||||
LdnSecurityConfig sec_config;
|
||||
LdnUserConfig user_config;
|
||||
u32 pad;
|
||||
LdnNetworkConfig network_config;
|
||||
} in = { *sec_config, tmp_user, 0, tmp_network_config };
|
||||
|
||||
return serviceDispatchIn(&g_ldnSrv, 202, in);
|
||||
}
|
||||
|
||||
Result ldnCreateNetworkPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, const LdnNetworkConfig *network_config, const LdnAddressEntry *addrs, s32 count) {
|
||||
LdnNetworkConfig tmp_network_config;
|
||||
LdnUserConfig tmp_user={0};
|
||||
memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname));
|
||||
_ldnCopyNetworkConfig(network_config, &tmp_network_config);
|
||||
|
||||
const struct {
|
||||
LdnSecurityConfig sec_config;
|
||||
LdnSecurityParameter sec_param;
|
||||
LdnUserConfig user_config;
|
||||
u32 pad;
|
||||
LdnNetworkConfig network_config;
|
||||
} in = { *sec_config, *sec_param, tmp_user, 0, tmp_network_config };
|
||||
|
||||
return serviceDispatchIn(&g_ldnSrv, 203, in,
|
||||
.buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In },
|
||||
.buffers = { { addrs, count*sizeof(LdnAddressEntry) } },
|
||||
);
|
||||
}
|
||||
|
||||
Result ldnDestroyNetwork(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 204);
|
||||
}
|
||||
|
||||
Result ldnReject(LdnIpv4Address addr) {
|
||||
return serviceDispatchIn(&g_ldnSrv, 205, addr);
|
||||
}
|
||||
|
||||
Result ldnSetAdvertiseData(const void* buffer, size_t size) {
|
||||
return serviceDispatch(&g_ldnSrv, 206,
|
||||
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In },
|
||||
.buffers = { { buffer, size } },
|
||||
);
|
||||
}
|
||||
|
||||
Result ldnSetStationAcceptPolicy(LdnAcceptPolicy policy) {
|
||||
u8 tmp=policy;
|
||||
return serviceDispatchIn(&g_ldnSrv, 207, tmp);
|
||||
}
|
||||
|
||||
Result ldnAddAcceptFilterEntry(LdnMacAddress addr) {
|
||||
return serviceDispatchIn(&g_ldnSrv, 208, addr);
|
||||
}
|
||||
|
||||
Result ldnClearAcceptFilter(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 209);
|
||||
}
|
||||
|
||||
Result ldnOpenStation(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 300);
|
||||
}
|
||||
|
||||
Result ldnCloseStation(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 301);
|
||||
}
|
||||
|
||||
Result ldnConnect(const LdnSecurityConfig *sec_config, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkInfo *network_info) {
|
||||
LdnUserConfig tmp_user={0};
|
||||
memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname));
|
||||
|
||||
const struct {
|
||||
LdnSecurityConfig sec_config;
|
||||
LdnUserConfig user_config;
|
||||
s32 version;
|
||||
u32 option;
|
||||
} in = { *sec_config, tmp_user, version, option };
|
||||
|
||||
return serviceDispatchIn(&g_ldnSrv, 302, in,
|
||||
.buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_In },
|
||||
.buffers = { { network_info, sizeof(*network_info) } },
|
||||
);
|
||||
}
|
||||
|
||||
Result ldnConnectPrivate(const LdnSecurityConfig *sec_config, const LdnSecurityParameter *sec_param, const LdnUserConfig *user_config, s32 version, u32 option, const LdnNetworkConfig *network_config) {
|
||||
LdnNetworkConfig tmp_network_config;
|
||||
LdnUserConfig tmp_user={0};
|
||||
memcpy(tmp_user.nickname, user_config->nickname, sizeof(tmp_user.nickname));
|
||||
_ldnCopyNetworkConfig(network_config, &tmp_network_config);
|
||||
|
||||
const struct {
|
||||
LdnSecurityConfig sec_config;
|
||||
LdnSecurityParameter sec_param;
|
||||
LdnUserConfig user_config;
|
||||
s32 version;
|
||||
u32 option;
|
||||
u32 pad;
|
||||
LdnNetworkConfig network_config;
|
||||
} in = { *sec_config, *sec_param, tmp_user, version, option, 0, tmp_network_config };
|
||||
|
||||
return serviceDispatchIn(&g_ldnSrv, 303, in);
|
||||
}
|
||||
|
||||
Result ldnDisconnect(void) {
|
||||
return _ldnCmdNoIO(&g_ldnSrv, 304);
|
||||
}
|
||||
|
||||
Result ldnSetOperationMode(LdnOperationMode mode) {
|
||||
if (!serviceIsActive(&g_ldnSrv) || g_ldnServiceType != LdnServiceType_System)
|
||||
return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
|
||||
|
||||
if (hosversionBefore(4,0,0))
|
||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
||||
|
||||
return _ldnCmdInU32NoOut(&g_ldnSrv, mode, 402);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user