diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs')
-rw-r--r-- | src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs | 1060 |
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; } } } |