LDN handles all local network communication.

ldn:m

This is "nn::ldn::detail::IMonitorServiceCreator".

Cmd Name
0 #CreateMonitorService

CreateMonitorService

Returns an #IMonitorService.

The user-process closes the IMonitorServiceCreator object immediately after using this cmd.

IMonitorService

This is "nn::ldn::detail::IMonitorService".

Cmd Name
0 #GetStateForMonitor
1 #GetNetworkInfoForMonitor
2 #GetIpv4AddressForMonitor
3 #GetDisconnectReasonForMonitor
4 #GetSecurityParameterForMonitor
5 #GetNetworkConfigForMonitor
100 #InitializeMonitor
101 #FinalizeMonitor

GetStateForMonitor

No input, returns an output u32.

sdknso implements this by returning the u32, with 0 being returned on error.

GetNetworkInfoForMonitor

Takes a type-0x1A output buffer containing a #NetworkInfo.

GetIpv4AddressForMonitor

No input, returns an output #Ipv4Address and a #SubnetMask.

GetDisconnectReasonForMonitor

No input, returns an output s16.

This is not exposed by sdknso.

This just returns 0.

GetSecurityParameterForMonitor

No input, returns an output #SecurityParameter.

This is not exposed by sdknso.

GetNetworkConfigForMonitor

No input, returns an output #NetworkConfig.

This is not exposed by sdknso.

InitializeMonitor

No input/output.

This is used immediately after object creation. Official sw will Abort if this fails.

This just returns 0.

FinalizeMonitor

No input/output.

This is used during service exit, prior to closing the object. Official sw will Abort if this fails.

This just returns 0.

ldn:s

This is "nn::ldn::detail::ISystemServiceCreator".

Cmd Name
0 #CreateSystemLocalCommunicationService

CreateSystemLocalCommunicationService

Returns an #ISystemLocalCommunicationService.

The user-process closes the ISystemServiceCreator object immediately after using this cmd. Official sw ignores errors from this cmd.

ISystemLocalCommunicationService

This is "nn::ldn::detail::ISystemLocalCommunicationService".

Cmd Name
0 #GetState
1 #GetNetworkInfo
2 #GetIpv4Address
3 #GetDisconnectReason
4 #GetSecurityParameter
5 #GetNetworkConfig
100 #AttachStateChangeEvent
101 #GetNetworkInfoLatestUpdate
102 #Scan
103 #ScanPrivate
104 [5.0.0+] #SetWirelessControllerRestriction
200 #OpenAccessPoint
201 #CloseAccessPoint
202 #CreateNetwork
203 #CreateNetworkPrivate
204 #DestroyNetwork
205 #Reject
206 #SetAdvertiseData
207 #SetStationAcceptPolicy
208 #AddAcceptFilterEntry
209 #ClearAcceptFilter
300 #OpenStation
301 #CloseStation
302 #Connect
303 #ConnectPrivate
304 #Disconnect
400 #InitializeSystem
401 #FinalizeSystem
402 [4.0.0+] #SetOperationMode
403 [7.0.0+] #InitializeSystem2

GetState

No input, returns an output u32.

sdknso implements this by returning the u32, with 0 being returned on error / when service isn't initialized.

Value Description
0 None
1 Initialized
2 AccessPointOpened
3 AccessPointCreated
4 StationOpened
5 StationConnected
6 Error

GetNetworkInfo

Takes a type-0x1A output buffer containing a #NetworkInfo.

GetIpv4Address

No input, returns an output #Ipv4Address and a #SubnetMask.

GetDisconnectReason

No input, returns an output s16.

sdknso implements this by returning the s16 as a s32, with -1 being returned on error.

Value Description
-1 See above.
0 None
1 User
2 SystemRequest
3 DestroyedByAdmin
4 DestroyedBySystemRequest
5 Admin
6 SignalLost

GetSecurityParameter

No input, returns an output #SecurityParameter.

GetNetworkConfig

No input, returns an output #NetworkConfig.

AttachStateChangeEvent

No input, returns an output Event handle.

sdknso uses EventClearMode=1 with this. sdknso will Abort if this cmd fails.

This is signaled when the data returned by #GetNetworkInfo/#GetNetworkInfoLatestUpdate is updated.

GetNetworkInfoLatestUpdate

Takes a type-0x1A output buffer containing a #NetworkInfo and a type-0xA output buffer containing an array of #NodeLatestUpdate.

The array count must be 8.

Scan

Takes a type-0x22 output buffer containing an array of #NetworkInfo, a s16 channel, a #ScanFilter, returns an output s16 total_out.

sdknso copies the output s16 to a s32, the value passed for the input s16 is from an user-specified s32 (user-apps generally use value 0 for this).

This is the same as #ScanPrivate, except this also has the same channel-override functionality as #CreateNetwork.

State must be 3-5.

The array count must be at least 1. This is clamped to a maximum of 0x18.

ScanPrivate

Takes a type-0x22 output buffer containing an array of #NetworkInfo, a s16 channel, a #ScanFilter, returns an output s16 total_out.

sdknso copies the output s16 to a s32, the value passed for the input s16 is from an user-specified s32.

See #Scan.

SetWirelessControllerRestriction

Takes an input #WirelessControllerRestriction, no output.

State must be 1.

The input value is written into state.

OpenAccessPoint

No input/output.

State must be 1, this cmd eventually sets the State to value 2.

CloseAccessPoint

No input/output.

State must be 2-3, this cmd eventually sets the State to value 1.

CreateNetwork

Takes an input #SecurityConfig, an #UserConfig, a #NetworkConfig, no output.

This is the same as #CreateNetworkPrivate, except the #AddressEntry params are 0, and the #SecurityParameter is generated from "nn::util::TinyMt::GenerateRandomBytes".

Unlike CreateNetworkPrivate, this overwrites the channel field in the #NetworkConfig. When the cached IsDevelopment value is true, the output from GetLdnChannel will overwrite that field if the s32 setting value is >=0, otherwise the original value is used. Otherwise when the IsDevelopment field is false (retail), the channel is overwritten with value 0.

This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the original value is used.

State must be 2, this cmd eventually sets the State to value 3.

CreateNetworkPrivate

Takes an input #SecurityConfig, a #SecurityParameter, an #UserConfig, a #NetworkConfig, a type-0x9 input buffer containing an array of #AddressEntry, no output.

The buffer/count for #AddressEntry can be 0, in which case the network will be non-Private like #CreateNetwork. The count must be <=8.

See #CreateNetwork.

DestroyNetwork

No input/output.

State must be 3, this cmd eventually sets the State to value 2.

Reject

Takes an input #Ipv4Address, no output.

State must be 3.

SetAdvertiseData

Takes a type-0x21 input buffer, no output.

The input buffer contains arbitrary user data.

The buffer size must be <=0x180. An empty buffer (addr=NULL/size=0) can be used to reset the AdvertiseData size in state to zero.

State must be 2-3.

SetStationAcceptPolicy

Takes an input #AcceptPolicy, no output.

State must be 2-3.

AddAcceptFilterEntry

Takes an input #MacAddress, no output.

There are two sdknso funcs implementing this: one which takes a #MacAddress directly, the other loads the #MacAddress from the input #NodeInfo.

State must be 2-3.

ClearAcceptFilter

No input/output.

State must be 2-3.

OpenStation

No input/output.

State must be 1, this cmd eventually sets the State to value 4.

CloseStation

No input/output.

State must be 4-5, this cmd eventually sets the State to value 1.

Connect

Takes a type-0x19 input buffer containing a #NetworkInfo, a #SecurityConfig, an #UserConfig, a s32 LocalCommunicationVersion, a #ConnectOption, no output.

State must be 4, this cmd eventually sets the State to value 5.

This is identical to #ConnectPrivate (besides the below), except the data internally passed for #SecurityParameter/#NetworkConfig are loaded from the input #NetworkInfo.

This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the used value is: original_field == 0 ? {u16 #NetworkInfo+0x60} : original_field.

u32 LocalCommunicationVersion>>15 must be 0.

ConnectPrivate

Takes a #SecurityConfig, #SecurityParameter, an #UserConfig, a s32 LocalCommunicationVersion, a #ConnectOption, a #NetworkConfig, no output.

See #Connect.

This overwrites the u16 field at #SecurityConfig+0. When the cached IsDevelopment value is false (retail), value 1 is used, otherwise the original value is used.

Disconnect

No input/output.

State must be 5, this cmd eventually sets the State to value 4.

InitializeSystem

Takes an input PID and an u64 pid_placeholder.

This is used immediately after object creation.

With [7.0.0+] #InitializeSystem2 is used instead.

FinalizeSystem

No input/output.

This is used during service exit, prior to closing the object. Official sw will Abort if this fails.

If State is set for it, this will run the equivalent of #CloseAccessPoint/#CloseStation when needed.

State must be non-zero, this cmd eventually sets the State to value 0.

SetOperationMode

Takes an input #OperationMode, no output.

State must be 1.

The input value is written into state.

InitializeSystem2

Takes an input PID, an u32, and an u64 pid_placeholder.

Official sw uses hard-coded value 0x1 for the u32.

The input u32 is ignored, the impl for this cmd is identical to #InitializeSystem.

Internally this calls a func with params: (..., PID, &{u16 value 0x38}). On success, this then calls another func (which sets two state fields to the input) with params: (state, 0) (these state fields are used during #Authentication to check which service is being used).

The first func uses various nifm funcs. The input value is used to determine the value for nn::nifm::RequestParameters: essentially, value 0x4 is used for ldn:u, and value 0x8 is used for ldn:s. The input value is also copied into state.

State must be 0, this cmd eventually sets the State to value 1.

ldn:u

This is "nn::ldn::detail::IUserServiceCreator".

Cmd Name
0 #CreateUserLocalCommunicationService

CreateUserLocalCommunicationService

Returns an #IUserLocalCommunicationService.

The user-process closes the IUserServiceCreator object immediately after using this cmd. Official sw ignores errors from this cmd.

IUserLocalCommunicationService

This is "nn::ldn::detail::IUserLocalCommunicationService".

This is identical to #ISystemLocalCommunicationService, except for the System-only cmd(s), and #Initialize/#Initialize2 differ.

Cmd Name
0 #GetState
1 #GetNetworkInfo
2 #GetIpv4Address
3 #GetDisconnectReason
4 #GetSecurityParameter
5 #GetNetworkConfig
100 #AttachStateChangeEvent
101 #GetNetworkInfoLatestUpdate
102 #Scan
103 #ScanPrivate
104 [5.0.0+] #SetWirelessControllerRestriction
200 #OpenAccessPoint
201 #CloseAccessPoint
202 #CreateNetwork
203 #CreateNetworkPrivate
204 #DestroyNetwork
205 #Reject
206 #SetAdvertiseData
207 #SetStationAcceptPolicy
208 #AddAcceptFilterEntry
209 #ClearAcceptFilter
300 #OpenStation
301 #CloseStation
302 #Connect
303 #ConnectPrivate
304 #Disconnect
400 #Initialize
401 #Finalize
402 [7.0.0+] #Initialize2

Initialize

Takes an input PID and an u64 pid_placeholder.

This is used immediately after object creation.

With [7.0.0+] #Initialize2 is used instead.

This is identical to #InitializeSystem except different params are used for the funcs called internally.

Finalize

No input/output.

This is used during service exit, prior to closing the object. Official sw will Abort if this fails.

This is identical to #FinalizeSystem.

Initialize2

Takes an input PID, an u32, and an u64 pid_placeholder.

Official sw uses hard-coded value 0x1 for the u32.

The input u32 is ignored, the impl for this cmd is identical to #Initialize.

This is identical to #InitializeSystem2 except different params are used for the funcs called internally: the u16 value is 0x5A, and the value for the second func is 1.

ndd

This is "nn::ndd::IService".

This was added with [5.0.0] and removed with [6.0.0].

Cmd Name
0 EnableAutoCommunication
1 DisableAutoCommunication
2 IsAutoCommunicationEnabled
3 EnablePowerSave
4 DisablePowerSave
5 IsPowerSaveEnabled
6 IsNetworkActive
7 AcquireSendDataUpdateEvent
8 AddSendData
9 ClearSendData
10 GetSendData
11 AcquireReceiveDataEvent
12 GetCurrentReceiveDataCounter
13 GetOldestReceiveDataCounter
14 GetNextReceiveDataCounter
15 GetAvailableReceiveDataCount
16 GetRecentReceiveDataCounter
17 GetReceiveData
18 AddReceiveData
19 ClearReceiveData
20 ClearDataIdFilter
21 AcquireDeviceScanEvent
22 StartDeviceScan
23 CancelDeviceScan
24 GetDeviceScanResult

lp2p:app, lp2p:sys

These are "nn::lp2p::detail::INetworkServiceCreator".

These were added with [9.0.0+].

lp2p:app is used by Mario Kart Live: Home Circuit.

Cmd Name
0 #CreateNetworkService
8 #CreateNetworkServiceMonitor

CreateNetworkService

Takes an input u32, an u64 pid_reserved, a PID, returns an output #INetworkService.

The input u32 must be value 0x1.

CreateNetworkServiceMonitor

Takes an input u64 pid_reserved, a PID, returns an output #INetworkServiceMonitor.

INetworkService

This is "nn::lp2p::detail::INetworkService".

Cmd Name
0 Initialize
[9.0.0-9.0.1] 256 AttachNetworkInterfaceStateChangeEvent
[9.0.0-9.0.1] 264 GetNetworkInterfaceLastError
[9.0.0-9.0.1] 272 GetRole
[9.0.0-9.0.1] 280
[9.0.0-9.0.1] 288 #GetGroupInfo
[9.0.0-9.0.1] 296
[9.0.0-9.0.1] 304 GetAdvertiseData
[9.0.0-9.0.1] 312 #GetIpConfig
[9.0.0-9.0.1] 320
512 #Scan
768 #CreateGroup
776 #DestroyGroup
784 #SetAdvertiseData
1536 #SendToOtherGroup
1544 #RecvFromOtherGroup
1552 #AddAcceptableGroupId
1560 [9.1.0+] Finalize

Initialize

No input/output.

Returns 0.

Unused by official sw.

Scan

Takes a type-0x19 input buffer containing a 0x200-byte struct, a type-0x22 output buffer containing an array of #ScanResult, returns an output s32 total_out.

CreateGroup

Takes a type-0x31 input buffer containing a 0x200-byte struct, no output.

mklive uses the following string with this: "Failed to create a group: %08X".

This is only available if a group wasn't already created.

DestroyGroup

No input/output.

This destroys the previously created group. If no group was previously created, this just returns 0.

SetAdvertiseData

Takes a type-0x21 input buffer, no output.

The buffer size must be <=0x80.

A string in mklive refers to the buffer data as "scan advertise data".

SendToOtherGroup

Takes an input #MacAddress, a #GroupId, a s16 frequency, a s16 channel, an u32 flags, a type-0x21 input buffer, no output.

The buffer size must be <=0x400.

The s16s must be >=1.

Flags is only used for selecting which func to call internally, via bit0.

A string in mklive refers to the buffer data as "Action frame".

This is only available when a group was previously created.

This sends an Action frame to the specified #GroupId, with the specified destination #MacAddress (can be a broadcast address).

The frequency param is the same as the #GroupInfo+0x84 field.

RecvFromOtherGroup

Takes an input u32 flags, a type-0x22 output buffer, returns a #MacAddress, an u16, a s16, an u32 out_size, a s32.

The out_size is the original size used for copying to the output buffer, before it's clamped to the output-buffer size.

Flags is only used for selecting which func to call internally, via bit0.

This is only available when a group was previously created.

This receives an Action frame. This will block until data is available (?).

AddAcceptableGroupId

Takes an input #GroupId, no output.

Finalize

No input/output.

INetworkServiceMonitor

This is "nn::lp2p::detail::INetworkServiceMonitor".

This interface has no commands, until [9.1.0+] which added actual commands.

Cmd Name
0 Initialize
256 AttachNetworkInterfaceStateChangeEvent
264 GetNetworkInterfaceLastError
272 #GetRole
280
281
288 #GetGroupInfo
296
304 #GetAdvertiseData
312 #GetIpConfig
320
328 AttachJoinEvent
336

Initialize

Returns 0.

Unused by official sw.

GetRole

No input, returns an output u8.

Cmd280

Takes a type-0x22 output buffer, returns 2 output u16s.

Validates that the role is value 0x2, then copies data from state into the output buffer. The first output u16 is the size used for the memcpy, the second u16 is the original size from state.

Cmd281

Takes a type-0x22 output buffer, returns 2 output u16s.

This is identical to Cmd280 except this doesn't run the role validation.

GetGroupInfo

Takes a type-0x32 output buffer containing a #GroupInfo.

Validates that the role is non-zero, then copies the struct from state into the output buffer.

Cmd296

Takes a type-0x32 output buffer containing a #GroupInfo and a type-0x31 input buffer containing a 0x200-byte struct.

This runs the same code as #CreateGroup to generate the #GroupInfo for the input struct (which with #CreateGroup would be available with #GetGroupInfo). The input struct is the same as #CreateGroup.

GetAdvertiseData

No input, returns 0x80-bytes of output.

Validates that the role is non-zero, then copies the data from state to output.

GetIpConfig

Takes a type-0x1A output buffer containing a 0x100-byte struct.

Validates that the role is non-zero, then copies the struct from state into the output buffer.

u32 +0x24 is the little-endian IpV4 address, u32 +0x44 is the little-endian IpV4 subnet.

Cmd320

No input, returns an output u32.

Cmd336

Takes a type-0x22 output buffer containing an array of a 0x80-byte struct, returns an output s32 total_out.

Validates that the role is value 0x1. Then any entries from state which are available are copied into the output array buffer, if there's space available.

A string in mklive refers to the array data as "connected members".

lp2p:m

This is "nn::lp2p::detail::IMonitorServiceCreator".

This was added with [9.1.0+].

Cmd Name
0 #CreateMonitorService

CreateMonitorService

Takes a PID, a total of 0x10-bytes of input, and returns an #IMonitorService.

IMonitorService

This is "nn::lp2p::detail::IMonitorService".

Cmd Name
0 Initialize
288 #GetGroupInfo
320

Initialize

Returns 0.

Ipv4Address

This is "nn::ldn::Ipv4Address". This is a 0x4-byte struct with 4-byte alignment.

This is essentially the same as struct in_addr, except this is little-endian.

This is generally "169.254.XXX.{...}", where XXX is random per created network.

SubnetMask

This is "nn::ldn::SubnetMask". This is a 0x4-byte struct with 4-byte alignment.

This is essentially the same as struct in_addr, except this is little-endian.

Ssid

This is "nn::ldn::Ssid".

When converting a Ssid to a string, the loaded chars from the string must be in the range of 0x20-0x7F, otherwise the byte written to the string will be 0.

Offset Size Description
0x0 0x1 Length excluding NUL-terminator, must be 0x1-0x20.
0x1 0x21 SSID string including NUL-terminator, str[{above length}] must be 0.

NetworkInfo

This is "nn::ldn::NetworkInfo". This is a 0x480-byte struct. The data at +0x50 is another struct.

The fields listed as Reserved (besides the fields before +0x10) are cleared during the memset and are not written to again afterwards, with cmds which return NetworkInfo.

Offset Size Description
0x0 0x8 LocalCommunicationId
0x8 0x2 Reserved
0xA 0x2 Arbitrary user data which can be used for filtering with #ScanFilter.
0xC 0x4 Reserved
0x10 0x10 Last 0x10-bytes of #SecurityParameter. NetworkId which is used to generate/overwrite the #Ssid. With #Scan/#ScanPrivate, this is only done after filtering when +0x4B is value 0x2. The converted Ssid is a 0x20-byte lowercase hex string version of the input NetworkId.
0x20 0x6 #MacAddress
0x26 0x22 #Ssid
0x48 0x2 s16 NetworkChannel
0x4A 0x1 s8 LinkLevel
0x4B 0x1 Set to hard-coded value 0x2 with output structs, except with #Scan/#ScanPrivate which can also set value 0x1 in certain cases. 0x1 = normal network without an #ActionFrame, 0x2 = LDN network with a valid #ActionFrame.
0x4C 0x4 Padding
0x50 0x10 First 0x10-bytes of #SecurityParameter.
0x60 0x2 Same as #SecurityConfig+0x0.
0x62 0x1 #AcceptPolicy
0x63 0x1 Only set with #Scan/#ScanPrivate, when +0x4B is value 0x2. See #ActionFrame.
0x64 0x2 Padding
0x66 0x1 Maximum participants, for the #NodeInfo array.
0x67 0x1 ParticipantNum, number of set entries in the #NodeInfo array. If +0x4B is not 0x2, ParticipantNum should be handled as if it's 0.
0x68 0x200(0x40*8) Array of #NodeInfo with 8 entries, starting with the AccessPoint node.
0x268 0x2 Reserved
0x26A 0x2 AdvertiseData size
0x26C 0x180 AdvertiseData
0x3EC 0x8C Reserved
0x478 0x8 [6.0.0+] Random AuthenticationId. Set to the output from es cmd1501 during network creation.

ScanFilter

This is "nn::ldn::ScanFilter". This is a 0x60-byte struct with 8-byte alignment.

sdknso copies the input ScanFilter to a tmp struct on stack, which is then used with the cmd.

Offset Size Description
0x0 0x8 When enabled, this will be overwritten if it's -1. The data written for this is the first LocalCommunicationId for the user-process loaded via arp:r, if loading fails value 0 is written instead. During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter field must match #NetworkInfo+0x0.
0x8 0x2 Padding
0xA 0x2 During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter field must match #NetworkInfo+0xA.
0xC 0x4 Padding
0x10 0x10 During filtering if enabled, u8 #NetworkInfo+0x4B must match 0x2, and this ScanFilter data must match #NetworkInfo+0x10.
0x20 0x4 When enabled, must be <=0x3, and during filtering must match u8 #NetworkInfo+0x4B.
0x24 0x6 #MacAddress. Only copied with #ScanPrivate. During filtering if enabled, this must match #NetworkInfo+0x20.
0x2A 0x22 #Ssid. During filtering if enabled, this must match #NetworkInfo+0x26 (the #Ssid there must be valid for this as well). The strings are compared, without verifying the length field in #Ssid matches.
0x4C 0x10 Cleared to zero for the tmp struct.
0x5C 0x4 Flags. Masked with 0x37 for #Scan, with #ScanPrivate this is masked with 0x3F.

Flags:

Bit Description
0 When set, enables using ScanFilter+0.
1 When set, enables using ScanFilter+0x10.
2 When set, enables using ScanFilter+0x20.
3 When set, enables using the ScanFilter #MacAddress.
4 When set, enables using the ScanFilter #Ssid.
5 When set, enables using ScanFilter+0xA.

NetworkConfig

This is "nn::ldn::NetworkConfig". This is a 0x20-byte struct with 8-byte alignment.

sdknso copies the input NetworkConfig to a tmp struct on stack, which is then used with the cmd (#CreateNetwork, #CreateNetworkPrivate, #ConnectPrivate).

Offset Size Description
0x0 0x8 LocalCommunicationId. Same as #NetworkInfo+0x0. #CreateNetwork/#CreateNetworkPrivate/#Connect/#ConnectPrivate: When -1, this is overwritten with the first LocalCommunicationId for the user-process loaded via arp:r, if loading fails value 0 is written instead. Otherwise when not -1, if control.nacp loading is successful with arp:r, this field must match one of the LocalCommunicationIds from there otherwise an error is thrown.
0x8 0x2 Cleared to zero during the copy.
0xA 0x2 Same as #NetworkInfo+0xA.
0xC 0x4 Cleared to zero during the copy.
0x10 0x2 s16 Channel, can be zero. Same as #NetworkInfo+0x48.
0x12 0x1 s8. Same as #NetworkInfo+0x66. #CreateNetwork/#CreateNetworkPrivate: Must be 0x1-0x8.
0x13 0x1 Cleared to zero during the copy.
0x14 0x2 Same as #NetworkInfo+0x96 (LocalCommunicationVersion from the first #NodeInfo). Must not be negative. #Connect/#ConnectPrivate: This must match the value for the AccessPoint LocalCommunicationVersion.
0x16 0xA Cleared to zero during the copy.

NodeLatestUpdate

This is "nn::ldn::NodeLatestUpdate". This is a 0x8-byte struct.

Offset Size Description
0x0 0x1 The field in state is reset to zero by #GetNetworkInfoLatestUpdate after loading it. Official apps checks whether this is non-zero.
0x1 0x7 Not initialized with #GetNetworkInfoLatestUpdate.

WirelessControllerRestriction

This is "nn::ldn::WirelessControllerRestriction". This is an u32 enum.

This is used to determine the value passed to btm SetWlanMode.

Value Description
0
1 This is the default.

SecurityConfig

This is "nn::ldn::SecurityConfig". This is a 0x44-byte struct with 2-byte alignment.

Offset Size Description
0x0 0x2 Type, a default of value 1 can be used here. Overwritten by #CreateNetwork/#CreateNetworkPrivate and #Connect/#ConnectPrivate. The value used internally by these cmds must be 1-3.
0x2 0x2 Data size. Must be 0x10-0x40.
0x4 0x40 Data, used with key derivation.

Type:

  • 1-2: Broadcast Action frame data is encrypted and is verified with SHA256.
  • 3: Broadcast Action frame data is plaintext and is verified with SHA256.
  • 1: Data frames are encrypted.
  • 2-3: Data frames for normal data-transfer are plaintext - the network is Open.

SecurityParameter

This is "nn::ldn::SecurityParameter". This is a 0x20-byte struct with 1-byte alignment.

Offset Size Description
0x0 0x10 Data, used with the same key derivation as #SecurityConfig.
0x10 0x10 NetworkId, see #NetworkInfo.

UserConfig

This is "nn::ldn::UserConfig". This is a 0x30-byte struct with 1-byte alignment.

sdknso copies the input UserConfig to a tmp struct on stack, which is then used with the cmd.

Offset Size Description
0x0 0x20 NUL-terminated string for the user nickname.
0x20 0x10 Cleared to zero during the copy.

AddressEntry

This is "nn::ldn::AddressEntry". This is a 0xC-byte struct.

Offset Size Description
0x0 0x4 #Ipv4Address
0x4 0x6 #MacAddress
0xA 0x2 Padding

AcceptPolicy

This is "nn::ldn::AcceptPolicy". This is an u8.

Value Description
0 Allow all.
1 Deny all.
2 Blacklist, addresses in the list are not allowed.
3 Whitelist, only addresses in the list are allowed.

MacAddress

This is "nn::ldn::MacAddress". This is a 6-byte struct with 1-byte alignment.

GroupId

This is "nn::lp2p::GroupId". This is a 6-byte struct with 1-byte alignment.

This is a WiFi BSS Id.

NodeInfo

This is "nn::ldn::NodeInfo".

The fields listed as Reserved are cleared during the memset and are not written to again afterwards, with cmds which return #NetworkInfo.

Offset Size Description
0x0 0x4 #Ipv4Address
0x4 0x6 #MacAddress
0xA 0x1 s8 ID / index
0xB 0x1 IsConnected
0xC 0x20 First 0x20-bytes of #UserConfig.
0x2C 0x2 Reserved
0x2E 0x2 s16 LocalCommunicationVersion
0x30 0x10 Reserved

ConnectOption

This is "nn::ldn::ConnectOption". This is an u32 bitmask.

There's two versions of the sdknso funcs for #Connect/#ConnectPrivate: the version where the ConnectOption isn't user-specified uses a default value of 0x1 for it, with the same ShowError code without the bit0 check.

When bit0 here is set after using the above cmds, the sdknso funcs will use ShowError with the returned Result if: (rc & 0x3FE1FF) == 0xE0CB.

This must be <=0x1, besides this validation ConnectOption is ignored by #Connect/#ConnectPrivate.

OperationMode

This is "nn::ldn::OperationMode". This is an u32 enum.

This controls bit1 in the value passed to wlan:lcl cmd0/cmd1: bit1 = OperationMode==1.

Value 1 seems to affect power (?) related fields in the beacon tags?

Value Description
0 This is the default.
1

GroupInfo

This is "nn::lp2p::GroupInfo". This is a 0x200-byte struct.

Offset Size Description
0x18 0x6 "GROUP ID (BSSID)" #MacAddress
0x1E 0x21 "GROUP NAME (SSID)" (string)
0x84 0x2 Wifi frequency: 24 = 2.4GHz, 50 = 5.0GHz.
0x86 0x2 "CHANNEL" s16. 0 = use default, otherwise this must be one of the following depending on the frequency field:
  • 24: 1, 6, 11.
  • 50: 36, 40, 44, 48.

ScanResult

This is "nn::lp2p::ScanResult". This is a 0x300-byte struct.

Offset Size Description
0x18 0x6 #MacAddress?
0x86 0x1
0x200 0x1
0x206 0x2 Size of the following data.
0x208 0x80 Data with the size specified above. Advertise data?

Network protocol

ldn

A beacon and Action frame are broadcasted. The SSID in the beacon is hidden (32-bytes with value 0). For #Scan/#ScanPrivate it doesn't matter if no beacon is available (#NetworkInfo is the same), as long as the Action frame is broadcasted. However, the Station will not send a probe-request during connection if no beacon is available (and therefore not attempt any communication with the AccessPoint). The beacon doesn't have any custom Nintendo data, that data is in the Acton frame.

During connection, the Station first sends a probe-request using the generated SSID from the Action frame. If the probe-response contains the expected data for the #SecurityConfig type, the Station then proceeds to connect to the AccessPoint. The key for data-frames, if enabled, is derived from a buffer containing: {#SecurityParameter+0x0} followed by {#SecurityConfig data with the specified data-size}. The #ActionFrame/data-frame keys are derived with the same func, the only difference is the input passed to this func + the passed constant data. The key derived by ldn is used directly as the static CCMP key for all data-frames (CCMP / MIC is standard).

Then the Station scans for an #ActionFrame for loading the #NetworkInfo.

Once connected, the AccessPoint sends Epigram-vendor Action frame(s) (same data) to the Station, the Station doesn't require these frames: dd1afeedfacedeadbeef010000000a00000000000000000000000000. Then the Station must Authenticate with the AccessPoint, this is custom. The Station sends a frame (a maximum of 3 times in some cases if errors occur, with the same data), and the AccessPoint sends a response. Once Authenticated, the node is added to the #NodeInfo array in #NetworkInfo. If the Station does not successfully Authenticate X-seconds after connecting, the AccessPoint disconnects the Station. If the Station fails to Authenticate, the Station itself will disconnect as well.

After Authentication the Station will scan for another #ActionFrame, with frame-comparision enabled with the above frame (frame must have been updated since the previous scan). The Station locates the index for a #MacAddress matching itself in the #NetworkInfo #NodeInfo array (the entry for the AccessPoint is skipped), throwing an error if not found. After validating the LocalCommunicationVersion, it proceeds to handle ARP setup below.

This does not use DHCP, each node on the network has to manually setup ARP (without sending ARP network requests) with the #NodeInfo array in #NetworkInfo.

At this point standard sockets can be used over Data frames.

EthFrame

The custom Ethernet frames have the following structure:

  • "Type: IEEE 802a OUI Extended Ethertype (0x88b7)"
  • "IEEE802a OUI Extended Ethertype":
    • "Organization Code: 00:22:aa (Nintendo Co., Ltd.)"
    • "Protocol ID: {...}"
  • The first byte of Data is value 0, then the ProtocolID-specific data follows, see below.
    • ProtocolID 0x0103 frames are sent by the AccessPoint to the Station. This is 0x20-bytes of zeros, except for the first byte which is 0x3. This is sent by the AccessPoint prior to destroying the network.

Authentication

Offset Size Description
0x0 0x1 #AuthVersion
0x1 0x1 Low u8 for the size.
0x2 0x1 Status. 0 = success, non-zero = error.
0x3 0x1 [2.0.0+] bool flag. The AccessPoint verifies that this is not set. Always set to 1 by the AccessPoint in the response. The Station only uses this when the #AuthVersion is >=2.
0x4 0x1 [6.0.0+] High u8 for the size.
0x5 0x3 Unused, zeros.
0x8 0x20 #NetworkInfo+0, must match the corresponding data in #NetworkInfo when the receiving node verifies this. With the

AccessPoint->Station frame, the Station verifies that this matches the data previously sent to the AccessPoint.

0x28 0x10 #NetworkInfo+0x50, must match the corresponding data in #NetworkInfo when the receiving node verifies this. With the

AccessPoint->Station frame, the Station verifies that this matches the data previously sent to the AccessPoint.

0x38 0x10 See below.
0x48 Frame-specific data, with the above size. The total frame size - {offset of the start of this data in the frame} must match the above size.

The Station sets the above size to 0x40 ([6.0.0+] if #NetworkInfo+0x13 is <3). [6.0.0+] The Authentication challenge is only used/enabled if that value is >=3, and #IUserLocalCommunicationService is being used.

The AccessPoint sets the above size to 0x40 ([6.0.0+] 0x0 if the +0x0 #AuthVersion is <3). [6.0.0+] The AccessPoint will only use/enable the Authentication challenge when the +0x0 #AuthVersion is >=3, and #IUserLocalCommunicationService is being used. This data will not be included in the frame if the status field indicates error.

[6.0.0+] Support for the Authentication challenge with es cmds 1501-1504 was added.

Station->AccessPoint frame, relative to +0x0 above (frame size depends on whether +0xAC is enabled):

The AccessPoint will not respond to frames where the source mac-address is unrecognized.

Offset Size Description
0x38 0x10 The Station sets this to random data. Unused by the AccessPoint, except for copying into the response.
0x48 0x20 #UserConfig+0. Copied into state by the AccessPoint.
0x68 0x2 Big-endian LocalCommunicationVersion. Byte-swapped by the AccessPoint then copied into state.
0x6A 0x1E Zeros, unused by the AccessPoint.
0x88 0x24 [6.0.0+] Zeros, unused by the AccessPoint.
0xAC 0x300 [6.0.0+] Authentication challenge data. If enabled, the total frame size must be >= {end offset of this data in the frame}. The frame data does not include this if it's not enabled.

AccessPoint->Station response frame, relative to +0x0 above (frame size depends on whether +0x48/+0xCC are enabled):

Offset Size Description
0x38 0x10 +0x38 from the data originally sent by the Station. The Station verifies that this matches the previously sent data.
0x48 0x40 Zeros. [6.0.0+] Only included in the frame if it's enabled (+0x0 #AuthVersion >= 3). Unused by the Station.
0x88 0x44 [6.0.0+] Only included in the frame if it's enabled (+0x0 #AuthVersion >= 3). Unused by the Station.
0xCC 0x100 [6.0.0+] If enabled, Authentication challenge response data. Not included in the frame if it's not enabled.
AuthVersion

Must be 0x1-0xF.

Value SystemVersion
1 [1.0.0+]
2 [2.0.0+]
3 [6.0.0+]

ActionFrame

The Action frames have the following structure:

  • "Fixed parameters":
    • "Category code: Vendor Specific (127)"
    • "OUI: 00:22:aa (Nintendo Co., Ltd.)"
  • The Data starts with the following header:
Offset Size Description
0x0 0x2 04 00 in sent frames.
0x2 0x2 Protocol ID, must be 0x0101.
0x4 0x2 Must be 0.
0x6 0x2 Zeros, unused.

Then the actual data follows:

Offset Size Description
0x0 0x20 #NetworkInfo+0x0. The u64/u16 are big-endian. Outside of #Scan/#ScanPrivate, this must match the previously loaded data for this.
0x20 0x1 #AuthVersion. Copied to #NetworkInfo+0x63. When comparing with a previous frame is enabled, this must match the value from the previous frame.
0x21 0x1 Encryption type: 1 = plaintext, 2 = encrypted, {frames with other values are ignored by #Scan/#ScanPrivate}. Must match the type which is currently being used: with #Scan/#ScanPrivate this is determined via this field, otherwise #SecurityConfig is used to determine this.
0x22 0x2 Big-endian u16 size for the data starting at +0x48, must be <=0x500, and must match {total frame size relative to +0x0 above} + 0x48.
0x24 0x4 Big-endian u32 Counter. The initial value is randomly-generated. This is incremented each time the below content is updated (including initial creation). Also used by the Station to determine whether the frame changed compared to a previous one. When comparing against a previous frame, new_counter-prev_counter must be <= 0xFF, and the counters must not match.
0x28 0x20 SHA256 hash over the entire frame starting at +0x0, with the above size + 0x48. During hashing, this hash is cleared, with the new hash overwriting the original in memory (the original is copied to stack for comparing).

When encryption is enabled, the encrypted data is +0x28 with size {remaining frame size}. This is encrypted with AES-128-CTR. The key is derived from the raw 0x20-bytes at +0x0. The CTR is {raw Counter above without byte-swap}, with the rest cleared to zeros.

The content data at +0x48 follows, which has the size specified above (which must be >=0x500), where all fields are big-endian:

Offset Size Description
0x0 0x10 #NetworkInfo+0x50
0x10 0x2 #NetworkInfo+0x60
0x12 0x1 #NetworkInfo+0x62
0x13 0x3 Unused
0x16 0x1 s8 #NetworkInfo+0x66, clamped to range 1-8.
0x17 0x1 s8 #NetworkInfo+0x67, clamped to range 1-8.
0x18 0x1C0(0x38*8) Array of the below node struct.
0x1D8 0x2 Unused
0x1DA 0x2 #NetworkInfo+0x26A
0x1DC 0x180 #NetworkInfo+0x26C
0x35C 0x19C Unused
0x4F8 0x8 [6.0.0+] #NetworkInfo+0x478

The data here is copied into #NetworkInfo.

Node data used in the above array (all fields big-endian), which are copied into the #NetworkInfo #NodeInfo array:

Offset Size Description
0x0 0x4 #Ipv4Address
0x4 0x6 #MacAddress
0xA 0x1 bool IsConnected
0xB 0x1 Unused
0xC 0x20 First 0x20-bytes of #UserConfig.
0x2C 0x2 s16 LocalCommunicationVersion
0x2E 0xA Unused

lp2p

This is used for communicating with accessories over local wifi - in particular, Mario Kart Live: Home Circuit uses this.

A beacon is broadcasted. The SSID in the beacon is hidden (all-zero with the same length as the original SSID). The beacon contains two custom Nintendo tags with OUI 00:22:aa.

Action frames are only sent when done so by #SendToOtherGroup.

Communication uses standard sockets and the above Action frames. Switch consoles presumably only use the Action frames to communicate with each other?

The DHCP server thread is started by the "nn.lp2p.StateMachine" thread eventually during group creation.