diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/ISystemLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/ISystemLocalCommunicationService.cs index 1e6ad72f5..49d12e8c8 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/ISystemLocalCommunicationService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/ISystemLocalCommunicationService.cs @@ -1,4 +1,8 @@ +using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Ldn.Types; namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator { @@ -6,6 +10,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator { private const int NifmRequestId = 90; + private bool _actionFrameEnabled; + + protected override bool ValidateLocalCommunicationId => false; + public ISystemLocalCommunicationService(ServiceCtx context) : base(context) { } // NOTE: This overrides the parent's Initialize method with the same command ID (402) @@ -19,6 +27,123 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator return ResultCode.Success; } + [CommandCmif(500)] // 18.0.0+ + // EnableActionFrame(nn::ldn::ActionFrameSettings) + public ResultCode EnableActionFrame(ServiceCtx context) + { + byte[] settings = context.RequestData.ReadBytes(0x80); + + _actionFrameEnabled = true; + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new + { + localCommunicationId = System.BitConverter.ToUInt64(settings, 0), + securityMode = System.BitConverter.ToUInt16(settings, 0x3c), + passphraseSize = System.BitConverter.ToUInt16(settings, 0x3e), + }); + + return ResultCode.Success; + } + + [CommandCmif(501)] // 18.0.0+ + // DisableActionFrame() + public ResultCode DisableActionFrame(ServiceCtx context) + { + _actionFrameEnabled = false; + + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + + [CommandCmif(502)] // 18.0.0+ + // SendActionFrame(buffer, nn::ldn::MacAddress, nn::ldn::MacAddress, s16 channel, u32 flags) + public ResultCode SendActionFrame(ServiceCtx context) + { + Array6 destination = context.RequestData.ReadStruct>(); + Array6 bssid = context.RequestData.ReadStruct>(); + short channel = context.RequestData.ReadInt16(); + _ = context.RequestData.ReadInt16(); + uint flags = context.RequestData.ReadUInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new + { + enabled = _actionFrameEnabled, + bufferPosition, + bufferSize, + destination, + bssid, + channel, + flags, + }); + + return _actionFrameEnabled && bufferPosition != 0 && bufferSize != 0 + ? ResultCode.Success + : ResultCode.InvalidState; + } + + [CommandCmif(503)] // 18.0.0+ + // RecvActionFrame(u32 flags, buffer) -> nn::ldn::MacAddress, nn::ldn::MacAddress, s16 channel, u32 size, s32 link_level + public ResultCode RecvActionFrame(ServiceCtx context) + { + uint flags = context.RequestData.ReadUInt32(); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + + if (bufferPosition != 0 && bufferSize != 0) + { + MemoryHelper.FillWithZeros(context.Memory, bufferPosition, (int)bufferSize); + } + + context.ResponseData.WriteStruct(new Array6()); + context.ResponseData.WriteStruct(new Array6()); + context.ResponseData.Write((short)0); + context.ResponseData.Write((ushort)0); + context.ResponseData.Write(0u); + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new + { + enabled = _actionFrameEnabled, + bufferSize, + flags, + }); + + return _actionFrameEnabled ? ResultCode.Success : ResultCode.InvalidState; + } + + [CommandCmif(505)] // 18.0.0+ + // SetHomeChannel(s16 channel) + public ResultCode SetHomeChannel(ServiceCtx context) + { + short channel = context.RequestData.ReadInt16(); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { channel }); + + return ResultCode.Success; + } + + [CommandCmif(600)] // 18.0.0+ + // SetTxPower(s32 power) + public ResultCode SetTxPower(ServiceCtx context) + { + int power = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { power }); + + return ResultCode.Success; + } + + [CommandCmif(601)] // 18.0.0+ + // ResetTxPower() + public ResultCode ResetTxPower(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + [CommandCmif(403)] // InitializeWithVersion(s32 version, pid) public ResultCode InitializeWithVersion(ServiceCtx context) diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs index 6af710ecd..2977d05ab 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -49,6 +49,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator private AccessPoint _accessPoint; private Station _station; + protected virtual bool ValidateLocalCommunicationId => true; + private ushort CheckDevelopmentChannel(ushort channel) { return (ushort)(!IsDevelopment ? 0 : channel); @@ -531,7 +533,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator [CommandCmif(200)] // OpenAccessPoint() - public ResultCode OpenAccessPoint(ServiceCtx context) + public virtual ResultCode OpenAccessPoint(ServiceCtx context) { if (_nifmResultCode != ResultCode.Success) { @@ -620,7 +622,9 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator networkConfig.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0]; } - bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId); + bool isLocalCommunicationIdValid = !ValidateLocalCommunicationId || + CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId); + if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId) { Logger.NetLog?.PrintMsg(LogClass.ServiceLdn, "CreateNetworkImpl: Invalid object!"); diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs index 37fa722b7..220ddfb63 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs @@ -473,6 +473,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData) { _timeout.DisableTimeout(); + _apConnected.Reset(); ConfigureAccessPoint(ref request.RyuNetworkConfig); @@ -528,6 +529,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData) { _timeout.DisableTimeout(); + _apConnected.Reset(); ConfigureAccessPoint(ref request.RyuNetworkConfig); @@ -600,6 +602,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu public NetworkError Connect(ConnectRequest request) { _timeout.DisableTimeout(); + _apConnected.Reset(); if (!EnsureConnected()) { @@ -622,6 +625,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu public NetworkError ConnectPrivate(ConnectPrivateRequest request) { _timeout.DisableTimeout(); + _apConnected.Reset(); if (!EnsureConnected()) { diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxy.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxy.cs index 06d46aadc..2590a6d9c 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxy.cs @@ -21,6 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy private readonly uint _subnetMask; private readonly uint _localIp; private readonly uint _broadcast; + private bool _tcpWarningLogged; public LdnProxy(ProxyConfig config, IProxyClient client, RyuLdnProtocol protocol) { @@ -43,9 +44,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy public bool Supported(AddressFamily domain, SocketType type, ProtocolType protocol) { - if (protocol == ProtocolType.Tcp) + if (protocol == ProtocolType.Tcp && !_tcpWarningLogged) { - Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Tcp proxy networking is untested. Please report this game so that it can be tested."); + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LDN proxy TCP networking is experimental."); + _tcpWarningLogged = true; } return domain == AddressFamily.InterNetwork && (protocol == ProtocolType.Tcp || protocol == ProtocolType.Udp); @@ -115,53 +117,168 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy } } + private void ForConnectionResponseSockets(ProxyInfo info, Action action) + { + lock (_sockets) + { + foreach (LdnProxySocket socket in _sockets) + { + if (socket.ProtocolType != info.Protocol || + socket.LocalEndPoint is not IPEndPoint localEndpoint || + localEndpoint.Port != info.DestPort || + socket.RemoteEndPoint is not IPEndPoint remoteEndpoint || + remoteEndpoint.Port != info.SourcePort) + { + continue; + } + + action(socket); + } + } + } + + private void ForDataSockets(ProxyInfo info, Action action) + { + lock (_sockets) + { + foreach (LdnProxySocket socket in _sockets) + { + if (socket.ProtocolType != info.Protocol || + socket.LocalEndPoint is not IPEndPoint localEndpoint || + localEndpoint.Port != info.DestPort || + !EndpointMatches(localEndpoint, info.DestIpV4)) + { + continue; + } + + if (info.Protocol == ProtocolType.Tcp && + (!socket.Connected || + socket.RemoteEndPoint is not IPEndPoint remoteEndpoint || + remoteEndpoint.Port != info.SourcePort || + !EndpointMatches(remoteEndpoint, info.SourceIpV4))) + { + continue; + } + + action(socket); + } + } + } + public void HandleConnectionRequest(LdnHeader header, ProxyConnectRequest request) { + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy connect received: {FormatInfo(request.Info)}"); + + bool routed = false; + ForRoutedSockets(request.Info, (socket) => { + routed = true; socket.HandleConnectRequest(request); }); + + if (!routed) + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"ProxyConnect had no listening socket: {FormatInfo(request.Info)}"); + } } public void HandleConnectionResponse(LdnHeader header, ProxyConnectResponse response) { - ForRoutedSockets(response.Info, (socket) => + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy connect reply received: {FormatInfo(response.Info)}"); + + bool routed = false; + + ForConnectionResponseSockets(response.Info, (socket) => { + routed = true; socket.HandleConnectResponse(response); }); + + if (!routed) + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"ProxyConnectReply had no connecting socket: {FormatInfo(response.Info)}"); + } } public void HandleData(LdnHeader header, ProxyDataHeader proxyHeader, byte[] data) { + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy data received: {data.Length} bytes, {FormatInfo(proxyHeader.Info)}"); + ProxyDataPacket packet = new() { Header = proxyHeader, Data = data }; - ForRoutedSockets(proxyHeader.Info, (socket) => + bool routed = false; + + ForDataSockets(proxyHeader.Info, (socket) => { + routed = true; socket.IncomingData(packet); }); + + if (!routed) + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"ProxyData had no receiving socket: {FormatInfo(proxyHeader.Info)}"); + } } public void HandleDisconnect(LdnHeader header, ProxyDisconnectMessage disconnect) { - ForRoutedSockets(disconnect.Info, (socket) => + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy disconnect received: {FormatInfo(disconnect.Info)}"); + + bool routed = false; + + ForDataSockets(disconnect.Info, (socket) => { + routed = true; socket.HandleDisconnect(disconnect); }); + + if (!routed) + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"ProxyDisconnect had no connected socket: {FormatInfo(disconnect.Info)}"); + } } private uint GetIpV4(IPEndPoint endpoint) { + ArgumentNullException.ThrowIfNull(endpoint); + if (endpoint.AddressFamily != AddressFamily.InterNetwork) { throw new NotSupportedException(); } + if (endpoint.Address.Equals(IPAddress.Any)) + { + return _localIp; + } + byte[] address = endpoint.Address.GetAddressBytes(); Array.Reverse(address); return BitConverter.ToUInt32(address); } + private bool EndpointMatches(IPEndPoint endpoint, uint ipv4) + { + return endpoint.Address.Equals(IPAddress.Any) || + endpoint.Address.Equals(IPAddress.IPv6Any) || + GetIpV4(endpoint) == ipv4; + } + + private static string FormatInfo(ProxyInfo info) + { + return $"{FormatIp(info.SourceIpV4)}:{info.SourcePort} -> {FormatIp(info.DestIpV4)}:{info.DestPort} ({info.Protocol})"; + } + + private static string FormatIp(uint ipv4) + { + byte[] address = BitConverter.GetBytes(ipv4); + Array.Reverse(address); + + return new IPAddress(address).ToString(); + } + private ProxyInfo MakeInfo(IPEndPoint localEp, IPEndPoint remoteEP, ProtocolType type) { return new ProxyInfo @@ -185,6 +302,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy Info = MakeInfo(localEp, remoteEp, type) }; + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy connect sent: {FormatInfo(request.Info)}"); + _parent.SendAsync(_protocol.Encode(PacketId.ProxyConnect, request)); } @@ -197,6 +316,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy Info = MakeInfo(localEp, remoteEp, type) }; + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy connect reply sent: {FormatInfo(request.Info)}"); + _parent.SendAsync(_protocol.Encode(PacketId.ProxyConnectReply, request)); } @@ -210,6 +331,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy DisconnectReason = 0 // TODO }; + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy disconnect sent: {FormatInfo(request.Info)}"); + _parent.SendAsync(_protocol.Encode(PacketId.ProxyDisconnect, request)); } @@ -224,6 +347,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy DataLength = (uint)buffer.Length }; + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy data sent: {buffer.Length} bytes, {FormatInfo(request.Info)}"); + _parent.SendAsync(_protocol.Encode(PacketId.ProxyData, request, buffer.ToArray())); return buffer.Length; diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxySocket.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxySocket.cs index 4cdb8e8b8..70534eed4 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxySocket.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/Proxy/LdnProxySocket.cs @@ -1,6 +1,7 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Types; using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy; +using Ryujinx.Common.Logging; using System; using System.Collections.Generic; using System.Net; @@ -41,6 +42,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy private bool _readShutdown; // private bool _writeShutdown; private bool _closed; + private long _bytesReceived; + private long _bytesSent; + private int _receiveWouldBlockCount; + private bool _closeSummaryLogged; + private string _sentPreview; + private string _receivedPreview; private readonly Dictionary _socketOptions = new() { @@ -118,7 +125,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy } } public bool Writable => Connected || ProtocolType == ProtocolType.Udp; - public bool Error => false; + public bool Error + { + get + { + lock (_errors) + { + return _errors.Count > 0; + } + } + } public LdnProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, LdnProxy proxy) { @@ -152,13 +168,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy return localEp; } - public LdnProxySocket AsAccepted(IPEndPoint remoteEp) + public LdnProxySocket AsAccepted(IPEndPoint localEp, IPEndPoint remoteEp) { Connected = true; + LocalEndPoint = localEp; RemoteEndPoint = remoteEp; - IPEndPoint localEp = EnsureLocalEndpoint(true); - _proxy.SignalConnected(localEp, remoteEp, ProtocolType); return this; @@ -190,6 +205,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy { _receiveQueue.Enqueue(packet); } + + _bytesReceived += packet.Data.Length; + _receivedPreview ??= FormatPayloadPreview(packet.Data); + + _receiveEvent.Set(); } } @@ -200,6 +220,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy throw new InvalidOperationException(); } + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket accept waiting on {LocalEndPoint}"); + // Accept a pending request to this socket. lock (_connectRequests) @@ -228,12 +250,15 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy // Is this request made for us? IPEndPoint endpoint = GetEndpoint(request.Info.DestIpV4, request.Info.DestPort); - if (Equals(endpoint, LocalEndPoint)) + if (MatchesLocalEndpoint(endpoint)) { // Yes - let's accept. IPEndPoint remoteEndpoint = GetEndpoint(request.Info.SourceIpV4, request.Info.SourcePort); - LdnProxySocket socket = new LdnProxySocket(AddressFamily, SocketType, ProtocolType, _proxy).AsAccepted(remoteEndpoint); + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket accepted {remoteEndpoint} on {LocalEndPoint}"); + + LdnProxySocket socket = new LdnProxySocket(AddressFamily, SocketType, ProtocolType, _proxy) + .AsAccepted((IPEndPoint)LocalEndPoint, remoteEndpoint); lock (_listenSockets) { @@ -247,6 +272,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy } } + private bool MatchesLocalEndpoint(IPEndPoint endpoint) + { + if (LocalEndPoint is not IPEndPoint localEndpoint || endpoint.Port != localEndpoint.Port) + { + return false; + } + + return localEndpoint.Address.Equals(IPAddress.Any) || + localEndpoint.Address.Equals(IPAddress.IPv6Any) || + endpoint.Address.Equals(localEndpoint.Address); + } + public void Bind(EndPoint localEP) { ArgumentNullException.ThrowIfNull(localEP); @@ -269,6 +306,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy public void Close() { + LogCloseSummary("close"); + _closed = true; _proxy.UnregisterSocket(this); @@ -291,7 +330,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy public void Connect(EndPoint remoteEP) { - if (_isListening || !IsBound) + if (_isListening) { throw new InvalidOperationException(); } @@ -303,6 +342,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy IPEndPoint localEp = EnsureLocalEndpoint(true); + RemoteEndPoint = (IPEndPoint)remoteEP; _connecting = true; _proxy.RequestConnection(localEp, (IPEndPoint)remoteEP, ProtocolType); @@ -331,29 +371,44 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy _connecting = false; - if (_connectResponse.Info.SourceIpV4 != 0) + _connectResponse = obj; + + if (obj.Info.SourceIpV4 != 0) { IPEndPoint remoteEp = GetEndpoint(obj.Info.SourceIpV4, obj.Info.SourcePort); RemoteEndPoint = remoteEp; Connected = true; + + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket connected to {RemoteEndPoint} from {LocalEndPoint}"); } else { // Connection failed SignalError(WsaError.WSAECONNREFUSED); + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket connection refused from {LocalEndPoint}"); } + + _connectEvent.Set(); } public void Disconnect(bool reuseSocket) { if (Connected) { + IPEndPoint localEndpoint = LocalEndPoint as IPEndPoint; + IPEndPoint remoteEndpoint = RemoteEndPoint as IPEndPoint; + + LogCloseSummary("local disconnect"); + ConnectionEnded(); // The other side needs to be notified that connection ended. - _proxy.EndConnection(LocalEndPoint as IPEndPoint, RemoteEndPoint as IPEndPoint, ProtocolType); + if (localEndpoint != null && remoteEndpoint != null) + { + _proxy.EndConnection(localEndpoint, remoteEndpoint, ProtocolType); + } } } @@ -375,6 +430,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy if (_socketOptions.TryGetValue(optionName, out int result)) { + if (optionName == SocketOptionName.Error) + { + lock (_errors) + { + result = _errors.Count > 0 ? _errors.Dequeue() : 0; + } + } + byte[] data = BitConverter.GetBytes(result); Array.Copy(data, 0, optionValue, 0, Math.Min(data.Length, optionValue.Length)); } @@ -401,12 +464,22 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy _connectRequests.Enqueue(obj); } - _connectEvent.Set(); + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket queued connect request on {LocalEndPoint}"); + + _acceptEvent.Set(); } public void HandleDisconnect(ProxyDisconnectMessage message) { - Disconnect(false); + _readShutdown = true; + + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LDN proxy socket disconnected by peer on {LocalEndPoint}"); + LogCloseSummary("peer disconnect"); + ConnectionEnded(); + + _receiveEvent.Set(); + _acceptEvent.Set(); + _connectEvent.Set(); } public int Receive(Span buffer) @@ -477,7 +550,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy } else { - throw new SocketException((int)WsaError.WSAETIMEDOUT); + _receiveWouldBlockCount++; + throw new SocketException((int)WsaError.WSAEWOULDBLOCK); } } } @@ -532,7 +606,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy } else { - socketError = SocketError.TimedOut; + _receiveWouldBlockCount++; + socketError = SocketError.WouldBlock; return -1; } } @@ -700,7 +775,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy throw new NotSupportedException(); } - return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType); + int sent = _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType); + _bytesSent += sent; + _sentPreview ??= FormatPayloadPreview(buffer); + + return sent; } public int SendTo(ReadOnlySpan buffer, SocketFlags flags, out SocketError socketError, EndPoint remoteEP) @@ -722,7 +801,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy socketError = SocketError.Success; - return _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType); + int sent = _proxy.SendTo(buffer, flags, localEp, (IPEndPoint)remoteEP, ProtocolType); + _bytesSent += sent; + _sentPreview ??= FormatPayloadPreview(buffer); + + return sent; } public bool Poll(int microSeconds, SelectMode mode) @@ -774,9 +857,11 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy case SocketShutdown.Both: _readShutdown = true; // _writeShutdown = true; + _receiveEvent.Set(); break; case SocketShutdown.Receive: _readShutdown = true; + _receiveEvent.Set(); break; case SocketShutdown.Send: // _writeShutdown = true; @@ -786,12 +871,41 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy public void ProxyDestroyed() { - // Do nothing, for now. Will likely be more useful with TCP. + _closed = true; + _readShutdown = true; + ConnectionEnded(); + + _receiveEvent.Set(); + _acceptEvent.Set(); + _connectEvent.Set(); + } + + private void LogCloseSummary(string reason) + { + if (_closeSummaryLogged || (_bytesReceived == 0 && _bytesSent == 0 && _receiveWouldBlockCount == 0)) + { + return; + } + + _closeSummaryLogged = true; + + Logger.Info?.PrintMsg( + LogClass.ServiceLdn, + $"LDN proxy socket closed ({reason}): local={LocalEndPoint}, remote={RemoteEndPoint}, sent={_bytesSent} bytes, received={_bytesReceived} bytes, receiveWouldBlock={_receiveWouldBlockCount}, sentPreview={_sentPreview ?? "none"}, receivedPreview={_receivedPreview ?? "none"}"); + } + + private static string FormatPayloadPreview(ReadOnlySpan data) + { + const int PreviewLength = 32; + + ReadOnlySpan preview = data[..Math.Min(data.Length, PreviewLength)]; + + return Convert.ToHexString(preview); } public void Dispose() { - + LogCloseSummary("dispose"); } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs index 3f9fad4fb..550f518a6 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs @@ -1,4 +1,5 @@ using Ryujinx.Common; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Services.Mii.Types; using System; @@ -34,7 +35,11 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService { SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); - context.ResponseData.Write(GetCount(flag)); + uint count = GetCount(flag); + + context.ResponseData.Write(count); + + Logger.NetLog?.PrintMsg(LogClass.ServiceMii, $"GetCount: flag={flag}, count={count}"); return ResultCode.Success; } @@ -57,6 +62,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService WriteSpanToBuffer(context, outputBuffer, elementsSpan); + Logger.NetLog?.PrintMsg(LogClass.ServiceMii, $"Get: flag={flag}, count={count}, result={result}"); + return result; } @@ -78,6 +85,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService WriteSpanToBuffer(context, outputBuffer, elementsSpan); + Logger.NetLog?.PrintMsg(LogClass.ServiceMii, $"Get1: flag={flag}, count={count}, result={result}"); + return result; } @@ -141,6 +150,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService WriteSpanToBuffer(context, outputBuffer, elementsSpan); + Logger.NetLog?.PrintMsg(LogClass.ServiceMii, $"Get2: flag={flag}, count={count}, result={result}"); + return result; } @@ -162,6 +173,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService WriteSpanToBuffer(context, outputBuffer, elementsSpan); + Logger.NetLog?.PrintMsg(LogClass.ServiceMii, $"Get3: flag={flag}, count={count}, result={result}"); + return result; } diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs index 0624fcf91..665e554fd 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs @@ -172,6 +172,12 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); } } + catch (InvalidOperationException exception) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket connection failed: {exception.Message}"); + + return LinuxError.EINVAL; + } } public void Disconnect() @@ -449,6 +455,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); } + else if (level == SocketOptionLevel.Socket && (option == BsdSocketOption.SoRcvTimeo || option == BsdSocketOption.SoSndTimeo)) + { + Socket.SetSocketOption(level, optionName, ConvertTimeValToMilliseconds(optionValue)); + } else { Socket.SetSocketOption(level, optionName, value); @@ -467,6 +477,36 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl } } + private static int ConvertTimeValToMilliseconds(ReadOnlySpan optionValue) + { + long seconds; + long microseconds; + + if (optionValue.Length >= 16) + { + seconds = MemoryMarshal.Read(optionValue); + microseconds = MemoryMarshal.Read(optionValue[8..]); + } + else if (optionValue.Length >= 8) + { + seconds = MemoryMarshal.Read(optionValue); + microseconds = MemoryMarshal.Read(optionValue[4..]); + } + else + { + return optionValue.Length >= 4 ? MemoryMarshal.Read(optionValue) : MemoryMarshal.Read(optionValue); + } + + if (seconds <= 0 && microseconds <= 0) + { + return 0; + } + + long milliseconds = (seconds * 1000) + ((microseconds + 999) / 1000); + + return (int)Math.Clamp(milliseconds, 1, int.MaxValue); + } + public LinuxError Read(out int readSize, Span buffer) { return Receive(out readSize, buffer, BsdSocketFlags.None);