aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs1060
1 files changed, 1035 insertions, 25 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
index d390a3e6..6abd2b89 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
@@ -1,88 +1,1098 @@
-using Ryujinx.HLE.HOS.Ipc;
+using LibHac.Ns;
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration.Multiplayer;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
+using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn;
using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
using System;
+using System.IO;
using System.Net;
+using System.Net.NetworkInformation;
+using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
- class IUserLocalCommunicationService : IpcService
+ class IUserLocalCommunicationService : IpcService, IDisposable
{
- // TODO(Ac_K): Determine what the hardcoded unknown value is.
- private const int UnknownValue = 90;
+ public INetworkClient NetworkClient { get; private set; }
- private readonly NetworkInterface _networkInterface;
+ private const int NifmRequestID = 90;
+ private const string DefaultIPAddress = "127.0.0.1";
+ private const string DefaultSubnetMask = "255.255.255.0";
+ private const bool IsDevelopment = false;
- private int _stateChangeEventHandle = 0;
+ private readonly KEvent _stateChangeEvent;
+
+ private NetworkState _state;
+ private DisconnectReason _disconnectReason;
+ private ResultCode _nifmResultCode;
+
+ private AccessPoint _accessPoint;
+ private Station _station;
public IUserLocalCommunicationService(ServiceCtx context)
{
- _networkInterface = new NetworkInterface(context.Device.System);
+ _stateChangeEvent = new KEvent(context.Device.System.KernelContext);
+ _state = NetworkState.None;
+ _disconnectReason = DisconnectReason.None;
+ }
+
+ private ushort CheckDevelopmentChannel(ushort channel)
+ {
+ return (ushort)(!IsDevelopment ? 0 : channel);
+ }
+
+ private SecurityMode CheckDevelopmentSecurityMode(SecurityMode securityMode)
+ {
+ return !IsDevelopment ? SecurityMode.Retail : securityMode;
+ }
+
+ private bool CheckLocalCommunicationIdPermission(ServiceCtx context, ulong localCommunicationIdChecked)
+ {
+ // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
+ ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+
+ foreach (var localCommunicationId in controlProperty.LocalCommunicationId.ItemsRo)
+ {
+ if (localCommunicationId == localCommunicationIdChecked)
+ {
+ return true;
+ }
+ }
+
+ return false;
}
[CommandCmif(0)]
// GetState() -> s32 state
public ResultCode GetState(ServiceCtx context)
{
- if (_networkInterface.NifmState != ResultCode.Success)
+ if (_nifmResultCode != ResultCode.Success)
{
context.ResponseData.Write((int)NetworkState.Error);
return ResultCode.Success;
}
- ResultCode result = _networkInterface.GetState(out NetworkState state);
+ // NOTE: Returns ResultCode.InvalidArgument if _state is null, doesn't occur in our case.
+ context.ResponseData.Write((int)_state);
+
+ return ResultCode.Success;
+ }
+
+ public void SetState()
+ {
+ _stateChangeEvent.WritableEvent.Signal();
+ }
+
+ public void SetState(NetworkState state)
+ {
+ _state = state;
+
+ SetState();
+ }
+
+ [CommandCmif(1)]
+ // GetNetworkInfo() -> buffer<network_info<0x480>, 0x1a>
+ public ResultCode GetNetworkInfo(ServiceCtx context)
+ {
+ ulong bufferPosition = context.Request.RecvListBuff[0].Position;
+
+ MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480);
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo);
- if (result == ResultCode.Success)
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize);
+
+ return ResultCode.Success;
+ }
+
+ private ResultCode GetNetworkInfoImpl(out NetworkInfo networkInfo)
+ {
+ if (_state == NetworkState.StationConnected)
+ {
+ networkInfo = _station.NetworkInfo;
+ }
+ else if (_state == NetworkState.AccessPointCreated)
{
- context.ResponseData.Write((int)state);
+ networkInfo = _accessPoint.NetworkInfo;
+ }
+ else
+ {
+ networkInfo = new NetworkInfo();
+
+ return ResultCode.InvalidState;
}
- return result;
+ return ResultCode.Success;
+ }
+
+ private NodeLatestUpdate[] GetNodeLatestUpdateImpl(int count)
+ {
+ if (_state == NetworkState.StationConnected)
+ {
+ return _station.LatestUpdates.ConsumeLatestUpdate(count);
+ }
+ else if (_state == NetworkState.AccessPointCreated)
+ {
+ return _accessPoint.LatestUpdates.ConsumeLatestUpdate(count);
+ }
+ else
+ {
+ return Array.Empty<NodeLatestUpdate>();
+ }
+ }
+
+ [CommandCmif(2)]
+ // GetIpv4Address() -> (u32 ip_address, u32 subnet_mask)
+ public ResultCode GetIpv4Address(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ // NOTE: Return ResultCode.InvalidArgument if ip_address and subnet_mask are null, doesn't occur in our case.
+
+ if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected)
+ {
+ (_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);
+
+ if (unicastAddress == null)
+ {
+ context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
+ context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
+ }
+ else
+ {
+ Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
+
+ context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
+ context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
+ }
+ }
+ else
+ {
+ return ResultCode.InvalidArgument;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetDisconnectReason() -> u16 disconnect_reason
+ public ResultCode GetDisconnectReason(ServiceCtx context)
+ {
+ // NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
+
+ context.ResponseData.Write((short)_disconnectReason);
+
+ return ResultCode.Success;
+ }
+
+ public void SetDisconnectReason(DisconnectReason reason)
+ {
+ if (_state != NetworkState.Initialized)
+ {
+ _disconnectReason = reason;
+
+ SetState(NetworkState.Initialized);
+ }
+ }
+
+ [CommandCmif(4)]
+ // GetSecurityParameter() -> bytes<0x20, 1> security_parameter
+ public ResultCode GetSecurityParameter(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ SecurityParameter securityParameter = new()
+ {
+ Data = new Array16<byte>(),
+ SessionId = networkInfo.NetworkId.SessionId,
+ };
+
+ context.ResponseData.WriteStruct(securityParameter);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // GetNetworkConfig() -> bytes<0x20, 8> network_config
+ public ResultCode GetNetworkConfig(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ NetworkConfig networkConfig = new()
+ {
+ IntentId = networkInfo.NetworkId.IntentId,
+ Channel = networkInfo.Common.Channel,
+ NodeCountMax = networkInfo.Ldn.NodeCountMax,
+ LocalCommunicationVersion = networkInfo.Ldn.Nodes[0].LocalCommunicationVersion,
+ Reserved2 = new Array10<byte>(),
+ };
+
+ context.ResponseData.WriteStruct(networkConfig);
+
+ return ResultCode.Success;
}
[CommandCmif(100)]
// AttachStateChangeEvent() -> handle<copy>
public ResultCode AttachStateChangeEvent(ServiceCtx context)
{
- if (_stateChangeEventHandle == 0)
+ if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(stateChangeEventHandle);
+
+ // Returns ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(101)]
+ // GetNetworkInfoLatestUpdate() -> (buffer<network_info<0x480>, 0x1a>, buffer<node_latest_update, 0xa>)
+ public ResultCode GetNetworkInfoLatestUpdate(ServiceCtx context)
+ {
+ ulong bufferPosition = context.Request.RecvListBuff[0].Position;
+
+ MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480);
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+ ulong outputSize = context.Request.RecvListBuff[0].Size;
+
+ ulong latestUpdateSize = (ulong)Marshal.SizeOf<NodeLatestUpdate>();
+ int count = (int)(outputSize / latestUpdateSize);
+
+ NodeLatestUpdate[] latestUpdate = GetNodeLatestUpdateImpl(count);
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+ foreach (NodeLatestUpdate node in latestUpdate)
+ {
+ MemoryHelper.Write(context.Memory, outputPosition, node);
+
+ outputPosition += latestUpdateSize;
+ }
+
+ ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo);
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(102)]
+ // Scan(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer<network_info, 0x22>)
+ public ResultCode Scan(ServiceCtx context)
+ {
+ return ScanImpl(context);
+ }
+
+ [CommandCmif(103)]
+ // ScanPrivate(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer<network_info, 0x22>)
+ public ResultCode ScanPrivate(ServiceCtx context)
+ {
+ return ScanImpl(context, true);
+ }
+
+ private ResultCode ScanImpl(ServiceCtx context, bool isPrivate = false)
+ {
+ ushort channel = (ushort)context.RequestData.ReadUInt64();
+ ScanFilter scanFilter = context.RequestData.ReadStruct<ScanFilter>();
+
+ (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(0);
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (!isPrivate)
+ {
+ channel = CheckDevelopmentChannel(channel);
+ }
+
+ ResultCode resultCode = ResultCode.InvalidArgument;
+
+ if (bufferSize != 0)
+ {
+ if (bufferPosition != 0)
+ {
+ ScanFilterFlag scanFilterFlag = scanFilter.Flag;
+
+ if (!scanFilterFlag.HasFlag(ScanFilterFlag.NetworkType) || scanFilter.NetworkType <= NetworkType.All)
+ {
+ if (scanFilterFlag.HasFlag(ScanFilterFlag.Ssid))
+ {
+ if (scanFilter.Ssid.Length <= 31)
+ {
+ return resultCode;
+ }
+ }
+
+ if (!scanFilterFlag.HasFlag(ScanFilterFlag.MacAddress))
+ {
+ if (scanFilterFlag > ScanFilterFlag.All)
+ {
+ return resultCode;
+ }
+
+ if (_state - 3 >= NetworkState.AccessPoint)
+ {
+ resultCode = ResultCode.InvalidState;
+ }
+ else
+ {
+ if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1)
+ {
+ // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
+ ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+
+ scanFilter.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
+ }
+
+ resultCode = ScanInternal(context.Memory, channel, scanFilter, bufferPosition, bufferSize, out ulong counter);
+
+ context.ResponseData.Write(counter);
+ }
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ }
+
+ return resultCode;
+ }
+
+ private ResultCode ScanInternal(IVirtualMemoryManager memory, ushort channel, ScanFilter scanFilter, ulong bufferPosition, ulong bufferSize, out ulong counter)
+ {
+ ulong networkInfoSize = (ulong)Marshal.SizeOf(typeof(NetworkInfo));
+ ulong maxGames = bufferSize / networkInfoSize;
+
+ MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize);
+
+ NetworkInfo[] availableGames = NetworkClient.Scan(channel, scanFilter);
+
+ counter = 0;
+
+ foreach (NetworkInfo networkInfo in availableGames)
+ {
+ MemoryHelper.Write(memory, bufferPosition + (networkInfoSize * counter), networkInfo);
+
+ if (++counter >= maxGames)
+ {
+ break;
+ }
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(104)] // 5.0.0+
+ // SetWirelessControllerRestriction(u32 wireless_controller_restriction)
+ public ResultCode SetWirelessControllerRestriction(ServiceCtx context)
+ {
+ // NOTE: Return ResultCode.InvalidArgument if an internal IPAddress is null, doesn't occur in our case.
+
+ uint wirelessControllerRestriction = context.RequestData.ReadUInt32();
+
+ if (wirelessControllerRestriction > 1)
+ {
+ return ResultCode.InvalidArgument;
+ }
+
+ if (_state != NetworkState.Initialized)
+ {
+ return ResultCode.InvalidState;
+ }
+
+ // NOTE: WirelessControllerRestriction value is used for the btm service in SetWlanMode call.
+ // Since we use our own implementation we can do nothing here.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(200)]
+ // OpenAccessPoint()
+ public ResultCode OpenAccessPoint(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (_state != NetworkState.Initialized)
+ {
+ return ResultCode.InvalidState;
+ }
+
+ CloseStation();
+
+ SetState(NetworkState.AccessPoint);
+
+ _accessPoint = new AccessPoint(this);
+
+ // NOTE: Calls nifm service and return related result codes.
+ // Since we use our own implementation we can return ResultCode.Success.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(201)]
+ // CloseAccessPoint()
+ public ResultCode CloseAccessPoint(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
+ {
+ DestroyNetworkImpl(DisconnectReason.DestroyedByUser);
+ }
+ else
+ {
+ return ResultCode.InvalidState;
+ }
+
+ SetState(NetworkState.Initialized);
+
+ return ResultCode.Success;
+ }
+
+ private void CloseAccessPoint()
+ {
+ _accessPoint?.Dispose();
+ _accessPoint = null;
+ }
+
+ [CommandCmif(202)]
+ // CreateNetwork(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, bytes<0x20, 8> network_config)
+ public ResultCode CreateNetwork(ServiceCtx context)
+ {
+ return CreateNetworkImpl(context);
+ }
+
+ [CommandCmif(203)]
+ // CreateNetworkPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1>, bytes<0x20, 8> network_config, buffer<unknown, 9> address_entry, int count)
+ public ResultCode CreateNetworkPrivate(ServiceCtx context)
+ {
+ return CreateNetworkImpl(context, true);
+ }
+
+ public ResultCode CreateNetworkImpl(ServiceCtx context, bool isPrivate = false)
+ {
+ SecurityConfig securityConfig = context.RequestData.ReadStruct<SecurityConfig>();
+ SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct<SecurityParameter>() : new SecurityParameter();
+
+ UserConfig userConfig = context.RequestData.ReadStruct<UserConfig>();
+
+ context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment?
+ NetworkConfig networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
+
+ if (networkConfig.IntentId.LocalCommunicationId == -1)
+ {
+ // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
+ ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+
+ networkConfig.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
+ }
+
+ bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
+ if (!isLocalCommunicationIdValid)
+ {
+ return ResultCode.InvalidObject;
+ }
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel);
+ securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
+
+ if (networkConfig.NodeCountMax <= 8)
+ {
+ if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0)
+ {
+ if (securityConfig.SecurityMode <= SecurityMode.Retail)
+ {
+ if (securityConfig.Passphrase.Length <= 0x40)
+ {
+ if (_state == NetworkState.AccessPoint)
+ {
+ if (isPrivate)
+ {
+ ulong bufferPosition = context.Request.PtrBuff[0].Position;
+ ulong bufferSize = context.Request.PtrBuff[0].Size;
+
+ byte[] addressListBytes = new byte[bufferSize];
+
+ context.Memory.Read(bufferPosition, addressListBytes);
+
+ AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
+
+ _accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
+ }
+ else
+ {
+ _accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
+ }
+
+ return ResultCode.Success;
+ }
+ else
+ {
+ return ResultCode.InvalidState;
+ }
+ }
+ }
+ }
+ }
+
+ return ResultCode.InvalidArgument;
+ }
+
+ [CommandCmif(204)]
+ // DestroyNetwork()
+ public ResultCode DestroyNetwork(ServiceCtx context)
+ {
+ return DestroyNetworkImpl(DisconnectReason.DestroyedByUser);
+ }
+
+ private ResultCode DestroyNetworkImpl(DisconnectReason disconnectReason)
+ {
+ if (_nifmResultCode != ResultCode.Success)
{
- if (context.Process.HandleTable.GenerateHandle(_networkInterface.StateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success)
+ return _nifmResultCode;
+ }
+
+ if (disconnectReason - 3 <= DisconnectReason.DisconnectedByUser)
+ {
+ if (_state == NetworkState.AccessPointCreated)
{
- throw new InvalidOperationException("Out of handles!");
+ CloseAccessPoint();
+
+ SetState(NetworkState.AccessPoint);
+
+ return ResultCode.Success;
}
+
+ CloseAccessPoint();
+
+ return ResultCode.InvalidState;
+ }
+
+ return ResultCode.InvalidArgument;
+ }
+
+ [CommandCmif(205)]
+ // Reject(u32 node_id)
+ public ResultCode Reject(ServiceCtx context)
+ {
+ uint nodeId = context.RequestData.ReadUInt32();
+
+ return RejectImpl(DisconnectReason.Rejected, nodeId);
+ }
+
+ private ResultCode RejectImpl(DisconnectReason disconnectReason, uint nodeId)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (_state != NetworkState.AccessPointCreated)
+ {
+ return ResultCode.InvalidState; // Must be network host to reject nodes.
+ }
+
+ return NetworkClient.Reject(disconnectReason, nodeId);
+ }
+
+ [CommandCmif(206)]
+ // SetAdvertiseData(buffer<advertise_data, 0x21>)
+ public ResultCode SetAdvertiseData(ServiceCtx context)
+ {
+ (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(0);
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (bufferSize == 0 || bufferSize > 0x180)
+ {
+ return ResultCode.InvalidArgument;
+ }
+
+ if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
+ {
+ byte[] advertiseData = new byte[bufferSize];
+
+ context.Memory.Read(bufferPosition, advertiseData);
+
+ return _accessPoint.SetAdvertiseData(advertiseData);
+ }
+ else
+ {
+ return ResultCode.InvalidState;
+ }
+ }
+
+ [CommandCmif(207)]
+ // SetStationAcceptPolicy(u8 accept_policy)
+ public ResultCode SetStationAcceptPolicy(ServiceCtx context)
+ {
+ AcceptPolicy acceptPolicy = (AcceptPolicy)context.RequestData.ReadByte();
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (acceptPolicy > AcceptPolicy.WhiteList)
+ {
+ return ResultCode.InvalidArgument;
+ }
+
+ if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
+ {
+ return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
+ }
+ else
+ {
+ return ResultCode.InvalidState;
+ }
+ }
+
+ [CommandCmif(208)]
+ // AddAcceptFilterEntry(bytes<6, 1> mac_address)
+ public ResultCode AddAcceptFilterEntry(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ // TODO
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(209)]
+ // ClearAcceptFilter()
+ public ResultCode ClearAcceptFilter(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ // TODO
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(300)]
+ // OpenStation()
+ public ResultCode OpenStation(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (_state != NetworkState.Initialized)
+ {
+ return ResultCode.InvalidState;
+ }
+
+ CloseAccessPoint();
+
+ SetState(NetworkState.Station);
+
+ _station?.Dispose();
+ _station = new Station(this);
+
+ // NOTE: Calls nifm service and returns related result codes.
+ // Since we use our own implementation we can return ResultCode.Success.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(301)]
+ // CloseStation()
+ public ResultCode CloseStation(ServiceCtx context)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
}
- context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
+ if (_state == NetworkState.Station || _state == NetworkState.StationConnected)
+ {
+ DisconnectImpl(DisconnectReason.DisconnectedByUser);
+ }
+ else
+ {
+ return ResultCode.InvalidState;
+ }
- // Return ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
+ SetState(NetworkState.Initialized);
return ResultCode.Success;
}
+ private void CloseStation()
+ {
+ _station?.Dispose();
+ _station = null;
+ }
+
+ [CommandCmif(302)]
+ // Connect(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, buffer<network_info<0x480>, 0x19>)
+ public ResultCode Connect(ServiceCtx context)
+ {
+ return ConnectImpl(context);
+ }
+
+ [CommandCmif(303)]
+ // ConnectPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, bytes<0x20, 8> network_config)
+ public ResultCode ConnectPrivate(ServiceCtx context)
+ {
+ return ConnectImpl(context, true);
+ }
+
+ private ResultCode ConnectImpl(ServiceCtx context, bool isPrivate = false)
+ {
+ SecurityConfig securityConfig = context.RequestData.ReadStruct<SecurityConfig>();
+ SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct<SecurityParameter>() : new SecurityParameter();
+
+ UserConfig userConfig = context.RequestData.ReadStruct<UserConfig>();
+ uint localCommunicationVersion = context.RequestData.ReadUInt32();
+ uint optionUnknown = context.RequestData.ReadUInt32();
+
+ NetworkConfig networkConfig = new();
+ NetworkInfo networkInfo = new();
+
+ if (isPrivate)
+ {
+ context.RequestData.ReadUInt32(); // Padding.
+
+ networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
+ }
+ else
+ {
+ ulong bufferPosition = context.Request.PtrBuff[0].Position;
+ ulong bufferSize = context.Request.PtrBuff[0].Size;
+
+ byte[] networkInfoBytes = new byte[bufferSize];
+
+ context.Memory.Read(bufferPosition, networkInfoBytes);
+
+ networkInfo = MemoryMarshal.Cast<byte, NetworkInfo>(networkInfoBytes)[0];
+ }
+
+ if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1)
+ {
+ // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
+ ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+
+ networkInfo.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
+ }
+
+ bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
+ if (!isLocalCommunicationIdValid)
+ {
+ return ResultCode.InvalidObject;
+ }
+
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
+
+ ResultCode resultCode = ResultCode.InvalidArgument;
+
+ if (securityConfig.SecurityMode - 1 <= SecurityMode.Debug)
+ {
+ if (optionUnknown <= 1 && (localCommunicationVersion >> 15) == 0 && securityConfig.PassphraseSize <= 64)
+ {
+ resultCode = ResultCode.VersionTooLow;
+ if (localCommunicationVersion >= 0)
+ {
+ resultCode = ResultCode.VersionTooHigh;
+ if (localCommunicationVersion <= short.MaxValue)
+ {
+ if (_state != NetworkState.Station)
+ {
+ resultCode = ResultCode.InvalidState;
+ }
+ else
+ {
+ if (isPrivate)
+ {
+ resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
+ }
+ else
+ {
+ resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(304)]
+ // Disconnect()
+ public ResultCode Disconnect(ServiceCtx context)
+ {
+ return DisconnectImpl(DisconnectReason.DisconnectedByUser);
+ }
+
+ private ResultCode DisconnectImpl(DisconnectReason disconnectReason)
+ {
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ if (disconnectReason <= DisconnectReason.DisconnectedBySystem)
+ {
+ if (_state == NetworkState.StationConnected)
+ {
+ SetState(NetworkState.Station);
+
+ CloseStation();
+
+ _disconnectReason = disconnectReason;
+
+ return ResultCode.Success;
+ }
+
+ CloseStation();
+
+ return ResultCode.InvalidState;
+ }
+
+ return ResultCode.InvalidArgument;
+ }
+
[CommandCmif(400)]
- // InitializeOld(u64, pid)
+ // InitializeOld(pid)
public ResultCode InitializeOld(ServiceCtx context)
{
- return _networkInterface.Initialize(UnknownValue, 0, null, null);
+ return InitializeImpl(context, context.Process.Pid, NifmRequestID);
}
[CommandCmif(401)]
// Finalize()
public ResultCode Finalize(ServiceCtx context)
{
- return _networkInterface.Finalize();
+ if (_nifmResultCode != ResultCode.Success)
+ {
+ return _nifmResultCode;
+ }
+
+ // NOTE: Use true when its called in nn::ldn::detail::ISystemLocalCommunicationService
+ ResultCode resultCode = FinalizeImpl(false);
+ if (resultCode == ResultCode.Success)
+ {
+ SetDisconnectReason(DisconnectReason.None);
+ }
+
+ return resultCode;
+ }
+
+ private ResultCode FinalizeImpl(bool isCausedBySystem)
+ {
+ DisconnectReason disconnectReason;
+
+ switch (_state)
+ {
+ case NetworkState.None:
+ return ResultCode.Success;
+ case NetworkState.AccessPoint:
+ {
+ CloseAccessPoint();
+
+ break;
+ }
+ case NetworkState.AccessPointCreated:
+ {
+ if (isCausedBySystem)
+ {
+ disconnectReason = DisconnectReason.DestroyedBySystem;
+ }
+ else
+ {
+ disconnectReason = DisconnectReason.DestroyedByUser;
+ }
+
+ DestroyNetworkImpl(disconnectReason);
+
+ break;
+ }
+ case NetworkState.Station:
+ {
+ CloseStation();
+
+ break;
+ }
+ case NetworkState.StationConnected:
+ {
+ if (isCausedBySystem)
+ {
+ disconnectReason = DisconnectReason.DisconnectedBySystem;
+ }
+ else
+ {
+ disconnectReason = DisconnectReason.DisconnectedByUser;
+ }
+
+ DisconnectImpl(disconnectReason);
+
+ break;
+ }
+ }
+
+ SetState(NetworkState.None);
+
+ NetworkClient?.DisconnectAndStop();
+ NetworkClient = null;
+
+ return ResultCode.Success;
}
[CommandCmif(402)] // 7.0.0+
- // Initialize(u64 ip_addresses, u64, pid)
+ // Initialize(u64 ip_addresses, pid)
public ResultCode Initialize(ServiceCtx context)
{
- // TODO(Ac_K): Determine what addresses are.
- IPAddress unknownAddress1 = new(context.RequestData.ReadUInt32());
- IPAddress unknownAddress2 = new(context.RequestData.ReadUInt32());
+ _ = new IPAddress(context.RequestData.ReadUInt32());
+ _ = new IPAddress(context.RequestData.ReadUInt32());
+
+ // NOTE: It seems the guest can get ip_address and subnet_mask from nifm service and pass it through the initialize.
+ // This calls InitializeImpl() twice: The first time with NIFM_REQUEST_ID, and if it fails, a second time with nifm_request_id = 1.
+
+ return InitializeImpl(context, context.Process.Pid, NifmRequestID);
+ }
+
+ public ResultCode InitializeImpl(ServiceCtx context, ulong pid, int nifmRequestId)
+ {
+ ResultCode resultCode = ResultCode.InvalidArgument;
+
+ if (nifmRequestId <= 255)
+ {
+ if (_state != NetworkState.Initialized)
+ {
+ // NOTE: Service calls nn::ldn::detail::NetworkInterfaceManager::NetworkInterfaceMonitor::Initialize() with nifmRequestId as argument,
+ // then it stores the result code of it in a global variable. Since we use our own implementation, we can just check the connection
+ // and return related error codes.
+ if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
+ {
+ MultiplayerMode mode = context.Device.Configuration.MultiplayerMode;
+ switch (mode)
+ {
+ case MultiplayerMode.Disabled:
+ NetworkClient = new DisabledLdnClient();
+ break;
+ }
+
+ // TODO: Call nn::arp::GetApplicationLaunchProperty here when implemented.
+ NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion.Items.ToArray());
+
+ resultCode = ResultCode.Success;
+
+ _nifmResultCode = resultCode;
+
+ SetState(NetworkState.Initialized);
+ }
+ else
+ {
+ // NOTE: Service returns differents ResultCode here related to the nifm ResultCode.
+ resultCode = ResultCode.DeviceDisabled;
+ _nifmResultCode = resultCode;
+ }
+ }
+ }
+
+ return resultCode;
+ }
+
+ public void Dispose()
+ {
+ if (NetworkClient != null)
+ {
+ _station?.Dispose();
+ _accessPoint?.Dispose();
+
+ NetworkClient.DisconnectAndStop();
+ }
- return _networkInterface.Initialize(UnknownValue, version: 1, unknownAddress1, unknownAddress2);
+ NetworkClient = null;
}
}
}