forked from MeloNX/MeloNX
* Close handle for stateChangeEvent on Finalize * Properly dispose NetworkClient before setting it to null
1105 lines
38 KiB
C#
1105 lines
38 KiB
C#
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, IDisposable
|
|
{
|
|
public INetworkClient NetworkClient { get; private set; }
|
|
|
|
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 readonly KEvent _stateChangeEvent;
|
|
private int _stateChangeEventHandle;
|
|
|
|
private NetworkState _state;
|
|
private DisconnectReason _disconnectReason;
|
|
private ResultCode _nifmResultCode;
|
|
|
|
private AccessPoint _accessPoint;
|
|
private Station _station;
|
|
|
|
public IUserLocalCommunicationService(ServiceCtx context)
|
|
{
|
|
_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 (_nifmResultCode != ResultCode.Success)
|
|
{
|
|
context.ResponseData.Write((int)NetworkState.Error);
|
|
|
|
return ResultCode.Success;
|
|
}
|
|
|
|
// 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);
|
|
|
|
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)
|
|
{
|
|
networkInfo = _accessPoint.NetworkInfo;
|
|
}
|
|
else
|
|
{
|
|
networkInfo = new NetworkInfo();
|
|
|
|
return ResultCode.InvalidState;
|
|
}
|
|
|
|
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 && context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _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)
|
|
{
|
|
return _nifmResultCode;
|
|
}
|
|
|
|
if (disconnectReason - 3 <= DisconnectReason.DisconnectedByUser)
|
|
{
|
|
if (_state == NetworkState.AccessPointCreated)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (_state == NetworkState.Station || _state == NetworkState.StationConnected)
|
|
{
|
|
DisconnectImpl(DisconnectReason.DisconnectedByUser);
|
|
}
|
|
else
|
|
{
|
|
return ResultCode.InvalidState;
|
|
}
|
|
|
|
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(pid)
|
|
public ResultCode InitializeOld(ServiceCtx context)
|
|
{
|
|
return InitializeImpl(context, context.Process.Pid, NifmRequestID);
|
|
}
|
|
|
|
[CommandCmif(401)]
|
|
// Finalize()
|
|
public ResultCode Finalize(ServiceCtx context)
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (_stateChangeEventHandle != 0)
|
|
{
|
|
context.Process.HandleTable.CloseHandle(_stateChangeEventHandle);
|
|
_stateChangeEventHandle = 0;
|
|
}
|
|
|
|
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?.Dispose();
|
|
NetworkClient = null;
|
|
|
|
return ResultCode.Success;
|
|
}
|
|
|
|
[CommandCmif(402)] // 7.0.0+
|
|
// Initialize(u64 ip_addresses, pid)
|
|
public ResultCode Initialize(ServiceCtx context)
|
|
{
|
|
_ = 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 different ResultCode here related to the nifm ResultCode.
|
|
resultCode = ResultCode.DeviceDisabled;
|
|
_nifmResultCode = resultCode;
|
|
}
|
|
}
|
|
}
|
|
|
|
return resultCode;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_station?.Dispose();
|
|
_station = null;
|
|
|
|
_accessPoint?.Dispose();
|
|
_accessPoint = null;
|
|
|
|
NetworkClient?.Dispose();
|
|
NetworkClient = null;
|
|
}
|
|
}
|
|
}
|