diff options
| author | Ac_K <Acoustik666@gmail.com> | 2019-09-19 02:45:11 +0200 |
|---|---|---|
| committer | jduncanator <1518948+jduncanator@users.noreply.github.com> | 2019-09-19 10:45:11 +1000 |
| commit | a0720b5681852f3d786d77bd3793b0359dea321c (patch) | |
| tree | 9d8f61e540d1d1d827999902dad95e5c0c1e076e /Ryujinx.HLE/HOS/Services/Sockets | |
| parent | 4af3101b22e6957d6aa48a2768566d658699f4ed (diff) | |
Refactoring HOS folder structure (#771)
* Refactoring HOS folder structure
Refactoring HOS folder structure:
- Added some subfolders when needed (Following structure decided in private).
- Added some `Types` folders when needed.
- Little cleanup here and there.
- Add services placeholders for every HOS services (close #766 and #753).
* Remove Types namespaces
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Sockets')
14 files changed, 2101 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs new file mode 100644 index 00000000..3a02e06c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -0,0 +1,1180 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Utilities; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsd:s", true)] + [Service("bsd:u", false)] + class IClient : IpcService + { + private static Dictionary<WsaError, LinuxError> _errorMap = new Dictionary<WsaError, LinuxError> + { + // WSAEINTR + {WsaError.WSAEINTR, LinuxError.EINTR}, + // WSAEWOULDBLOCK + {WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK}, + // WSAEINPROGRESS + {WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS}, + // WSAEALREADY + {WsaError.WSAEALREADY, LinuxError.EALREADY}, + // WSAENOTSOCK + {WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK}, + // WSAEDESTADDRREQ + {WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ}, + // WSAEMSGSIZE + {WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE}, + // WSAEPROTOTYPE + {WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE}, + // WSAENOPROTOOPT + {WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT}, + // WSAEPROTONOSUPPORT + {WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT}, + // WSAESOCKTNOSUPPORT + {WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT}, + // WSAEOPNOTSUPP + {WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP}, + // WSAEPFNOSUPPORT + {WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT}, + // WSAEAFNOSUPPORT + {WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT}, + // WSAEADDRINUSE + {WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE}, + // WSAEADDRNOTAVAIL + {WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL}, + // WSAENETDOWN + {WsaError.WSAENETDOWN, LinuxError.ENETDOWN}, + // WSAENETUNREACH + {WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH}, + // WSAENETRESET + {WsaError.WSAENETRESET, LinuxError.ENETRESET}, + // WSAECONNABORTED + {WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED}, + // WSAECONNRESET + {WsaError.WSAECONNRESET, LinuxError.ECONNRESET}, + // WSAENOBUFS + {WsaError.WSAENOBUFS, LinuxError.ENOBUFS}, + // WSAEISCONN + {WsaError.WSAEISCONN, LinuxError.EISCONN}, + // WSAENOTCONN + {WsaError.WSAENOTCONN, LinuxError.ENOTCONN}, + // WSAESHUTDOWN + {WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN}, + // WSAETOOMANYREFS + {WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS}, + // WSAETIMEDOUT + {WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT}, + // WSAECONNREFUSED + {WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED}, + // WSAELOOP + {WsaError.WSAELOOP, LinuxError.ELOOP}, + // WSAENAMETOOLONG + {WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG}, + // WSAEHOSTDOWN + {WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN}, + // WSAEHOSTUNREACH + {WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH}, + // WSAENOTEMPTY + {WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY}, + // WSAEUSERS + {WsaError.WSAEUSERS, LinuxError.EUSERS}, + // WSAEDQUOT + {WsaError.WSAEDQUOT, LinuxError.EDQUOT}, + // WSAESTALE + {WsaError.WSAESTALE, LinuxError.ESTALE}, + // WSAEREMOTE + {WsaError.WSAEREMOTE, LinuxError.EREMOTE}, + // WSAEINVAL + {WsaError.WSAEINVAL, LinuxError.EINVAL}, + // WSAEFAULT + {WsaError.WSAEFAULT, LinuxError.EFAULT}, + // NOERROR + {0, 0} + }; + + private bool _isPrivileged; + + private List<BsdSocket> _sockets = new List<BsdSocket>(); + + public IClient(ServiceCtx context, bool isPrivileged) + { + _isPrivileged = isPrivileged; + } + + private LinuxError ConvertError(WsaError errorCode) + { + if (!_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + errno = (LinuxError)errorCode; + } + + return errno; + } + + private ResultCode WriteWinSock2Error(ServiceCtx context, WsaError errorCode) + { + return WriteBsdResult(context, -1, ConvertError(errorCode)); + } + + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = 0) + { + if (errorCode != LinuxError.SUCCESS) + { + result = -1; + } + + context.ResponseData.Write(result); + context.ResponseData.Write((int)errorCode); + + return ResultCode.Success; + } + + private BsdSocket RetrieveSocket(int socketFd) + { + if (socketFd >= 0 && _sockets.Count > socketFd) + { + return _sockets[socketFd]; + } + + return null; + } + + private LinuxError SetResultErrno(Socket socket, int result) + { + return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private AddressFamily ConvertFromBsd(int domain) + { + if (domain == 2) + { + return AddressFamily.InterNetwork; + } + + // FIXME: AF_ROUTE ignored, is that really needed? + return AddressFamily.Unknown; + } + + private ResultCode SocketInternal(ServiceCtx context, bool exempt) + { + AddressFamily domain = (AddressFamily)context.RequestData.ReadInt32(); + SocketType type = (SocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + + if (domain == AddressFamily.Unknown) + { + return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((type == SocketType.Seqpacket || type == SocketType.Raw) && !_isPrivileged) + { + if (domain != AddressFamily.InterNetwork || type != SocketType.Raw || protocol != ProtocolType.Icmp) + { + return WriteBsdResult(context, -1, LinuxError.ENOENT); + } + } + + BsdSocket newBsdSocket = new BsdSocket + { + Family = (int)domain, + Type = (int)type, + Protocol = (int)protocol, + Handle = new Socket(domain, type, protocol) + }; + + _sockets.Add(newBsdSocket); + + if (exempt) + { + newBsdSocket.Handle.Disconnect(true); + } + + return WriteBsdResult(context, _sockets.Count - 1); + } + + private IPEndPoint ParseSockAddr(ServiceCtx context, long bufferPosition, long bufferSize) + { + int size = context.Memory.ReadByte(bufferPosition); + int family = context.Memory.ReadByte(bufferPosition + 1); + int port = EndianSwap.Swap16(context.Memory.ReadUInt16(bufferPosition + 2)); + + byte[] rawIp = context.Memory.ReadBytes(bufferPosition + 4, 4); + + return new IPEndPoint(new IPAddress(rawIp), port); + } + + private void WriteSockAddr(ServiceCtx context, long bufferPosition, IPEndPoint endPoint) + { + context.Memory.WriteByte(bufferPosition, 0); + context.Memory.WriteByte(bufferPosition + 1, (byte)endPoint.AddressFamily); + context.Memory.WriteUInt16(bufferPosition + 2, EndianSwap.Swap16((ushort)endPoint.Port)); + context.Memory.WriteBytes(bufferPosition + 4, endPoint.Address.GetAddressBytes()); + } + + private void WriteSockAddr(ServiceCtx context, long bufferPosition, BsdSocket socket, bool isRemote) + { + IPEndPoint endPoint = (isRemote ? socket.Handle.RemoteEndPoint : socket.Handle.LocalEndPoint) as IPEndPoint; + + WriteSockAddr(context, bufferPosition, endPoint); + } + + [Command(0)] + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject<copy, transfer_memory>, pid) -> u32 bsd_errno + public ResultCode RegisterClient(ServiceCtx context) + { + /* + typedef struct { + u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). + u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). + } BsdBufferConfig; + */ + + // bsd_error + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(1)] + // StartMonitoring(u64, pid) + public ResultCode StartMonitoring(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceBsd, new { unknown0 }); + + return ResultCode.Success; + } + + [Command(2)] + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode Socket(ServiceCtx context) + { + return SocketInternal(context, false); + } + + [Command(3)] + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode SocketExempt(ServiceCtx context) + { + return SocketInternal(context, true); + } + + [Command(4)] + // Open(u32 flags, array<unknown, 0x21> path) -> (i32 ret, u32 bsd_errno) + public ResultCode Open(ServiceCtx context) + { + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + int flags = context.RequestData.ReadInt32(); + + byte[] rawPath = context.Memory.ReadBytes(bufferPosition, bufferSize); + string path = Encoding.ASCII.GetString(rawPath); + + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd, new { path, flags }); + + return ResultCode.Success; + } + + [Command(5)] + // Select(u32 nfds, nn::socket::timeout timeout, buffer<nn::socket::fd_set, 0x21, 0> readfds_in, buffer<nn::socket::fd_set, 0x21, 0> writefds_in, buffer<nn::socket::fd_set, 0x21, 0> errorfds_in) -> (i32 ret, u32 bsd_errno, buffer<nn::socket::fd_set, 0x22, 0> readfds_out, buffer<nn::socket::fd_set, 0x22, 0> writefds_out, buffer<nn::socket::fd_set, 0x22, 0> errorfds_out) + public ResultCode Select(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(6)] + // Poll(u32 nfds, u32 timeout, buffer<unknown, 0x21, 0> fds) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>) + public ResultCode Poll(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(); + + + if (timeout < -1 || fdsCount < 0 || (fdsCount * 8) > bufferSize) + { + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + + PollEvent[] events = new PollEvent[fdsCount]; + + for (int i = 0; i < fdsCount; i++) + { + int socketFd = context.Memory.ReadInt32(bufferPosition + i * 8); + + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket == null) + { + return WriteBsdResult(context, -1, LinuxError.EBADF);} + + PollEvent.EventTypeMask inputEvents = (PollEvent.EventTypeMask)context.Memory.ReadInt16(bufferPosition + i * 8 + 4); + PollEvent.EventTypeMask outputEvents = (PollEvent.EventTypeMask)context.Memory.ReadInt16(bufferPosition + i * 8 + 6); + + events[i] = new PollEvent(socketFd, socket, inputEvents, outputEvents); + } + + List<Socket> readEvents = new List<Socket>(); + List<Socket> writeEvents = new List<Socket>(); + List<Socket> errorEvents = new List<Socket>(); + + foreach (PollEvent Event in events) + { + bool isValidEvent = false; + + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + readEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.UrgentInput) != 0) + { + readEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Output) != 0) + { + writeEvents.Add(Event.Socket.Handle); + errorEvents.Add(Event.Socket.Handle); + + isValidEvent = true; + } + + if ((Event.InputEvents & PollEvent.EventTypeMask.Error) != 0) + { + errorEvents.Add(Event.Socket.Handle); + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Poll input event type: {Event.InputEvents}"); + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + } + + try + { + System.Net.Sockets.Socket.Select(readEvents, writeEvents, errorEvents, timeout); + } + catch (SocketException exception) + { + return WriteWinSock2Error(context, (WsaError)exception.ErrorCode); + } + + for (int i = 0; i < fdsCount; i++) + { + PollEvent Event = events[i]; + context.Memory.WriteInt32(bufferPosition + i * 8, Event.SocketFd); + context.Memory.WriteInt16(bufferPosition + i * 8 + 4, (short)Event.InputEvents); + + PollEvent.EventTypeMask outputEvents = 0; + + Socket socket = Event.Socket.Handle; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEvent.EventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEvent.EventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((Event.InputEvents & PollEvent.EventTypeMask.Input) != 0) + { + outputEvents |= PollEvent.EventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEvent.EventTypeMask.Output; + } + + context.Memory.WriteInt16(bufferPosition + i * 8 + 6, (short)outputEvents); + } + + return WriteBsdResult(context, readEvents.Count + writeEvents.Count + errorEvents.Count, LinuxError.SUCCESS); + } + + [Command(7)] + // Sysctl(buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>) + public ResultCode Sysctl(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [Command(8)] + // Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array<i8, 0x22> message) + public ResultCode Recv(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && (socketFlags & SocketFlags.OutOfBand) == 0 + && (socketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {socketFlags}"); + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] receivedBuffer = new byte[receiveLength]; + + try + { + result = socket.Handle.Receive(receivedBuffer, socketFlags); + errno = SetResultErrno(socket.Handle, result); + + context.Memory.WriteBytes(receivePosition, receivedBuffer); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(9)] + // RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<i8, 0x22, 0> message, buffer<nn::socket::sockaddr_in, 0x22, 0x10>) + public ResultCode RecvFrom(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + (long sockAddrOutPosition, long sockAddrOutSize) = context.Request.GetBufferType0x22(1); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && (socketFlags & SocketFlags.OutOfBand) == 0 + && (socketFlags & SocketFlags.Peek) == 0) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Recv flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] receivedBuffer = new byte[receiveLength]; + EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); + + try + { + result = socket.Handle.ReceiveFrom(receivedBuffer, receivedBuffer.Length, socketFlags, ref endPoint); + errno = SetResultErrno(socket.Handle, result); + + context.Memory.WriteBytes(receivePosition, receivedBuffer); + WriteSockAddr(context, sockAddrOutPosition, (IPEndPoint)endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(10)] + // Send(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public ResultCode Send(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && socketFlags != SocketFlags.OutOfBand + && socketFlags != SocketFlags.Peek && socketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + + try + { + result = socket.Handle.Send(sendBuffer, socketFlags); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + } + + return WriteBsdResult(context, result, errno); + } + + [Command(11)] + // SendTo(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno) + public ResultCode SendTo(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketFlags socketFlags = (SocketFlags)context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21(1); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + if (socketFlags != SocketFlags.None && socketFlags != SocketFlags.OutOfBand + && socketFlags != SocketFlags.Peek && socketFlags != SocketFlags.DontRoute) + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Send flags: {socketFlags}"); + + return WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + } + + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + EndPoint endPoint = ParseSockAddr(context, bufferPosition, bufferSize); + + try + { + result = socket.Handle.SendTo(sendBuffer, sendBuffer.Length, socketFlags, endPoint); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + } + + return WriteBsdResult(context, result, errno); + } + + [Command(12)] + // Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode Accept(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + Socket newSocket = null; + + try + { + newSocket = socket.Handle.Accept(); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + + if (newSocket == null && errno == LinuxError.SUCCESS) + { + errno = LinuxError.EWOULDBLOCK; + } + else if (errno == LinuxError.SUCCESS) + { + BsdSocket newBsdSocket = new BsdSocket + { + Family = (int)newSocket.AddressFamily, + Type = (int)newSocket.SocketType, + Protocol = (int)newSocket.ProtocolType, + Handle = newSocket + }; + + _sockets.Add(newBsdSocket); + + WriteSockAddr(context, bufferPos, newBsdSocket, true); + + WriteBsdResult(context, _sockets.Count - 1, errno); + + context.ResponseData.Write(0x10); + + return ResultCode.Success; + } + } + + return WriteBsdResult(context, -1, errno); + } + + [Command(13)] + // Bind(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10> addr) -> (i32 ret, u32 bsd_errno) + public ResultCode Bind(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + try + { + IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); + + socket.Handle.Bind(endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(14)] + // Connect(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno) + public ResultCode Connect(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + try + { + IPEndPoint endPoint = ParseSockAddr(context, bufferPos, bufferSize); + + socket.Handle.Connect(endPoint); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(15)] + // GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode GetPeerName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, true); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(0x10); + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(16)] + // GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode GetSockName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, false); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(0x10); + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(17)] + // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>) + public ResultCode GetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int level = context.RequestData.ReadInt32(); + int optionName = context.RequestData.ReadInt32(); + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.ENOPROTOOPT; + + if (level == 0xFFFF) + { + errno = HandleGetSocketOption(context, socket, (SocketOptionName)optionName, bufferPosition, bufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported GetSockOpt Level: {(SocketOptionLevel)level}"); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(18)] + // Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public ResultCode Listen(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int backlog = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + try + { + socket.Handle.Listen(backlog); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(19)] + // Ioctl(u32 fd, u32 request, u32 bufcount, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>) + public ResultCode Ioctl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdIoctl cmd = (BsdIoctl)context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + switch (cmd) + { + case BsdIoctl.AtMark: + errno = LinuxError.SUCCESS; + + (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); + + // FIXME: OOB not implemented. + context.Memory.WriteInt32(bufferPosition, 0); + break; + + default: + errno = LinuxError.EOPNOTSUPP; + + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}"); + break; + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(20)] + // Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno) + public ResultCode Fcntl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int cmd = context.RequestData.ReadInt32(); + int arg = context.RequestData.ReadInt32(); + + int result = 0; + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + if (cmd == 0x3) + { + result = !socket.Handle.Blocking ? 0x800 : 0; + } + else if (cmd == 0x4 && arg == 0x800) + { + socket.Handle.Blocking = false; + result = 0; + } + else + { + errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(context, result, errno); + } + + private LinuxError HandleGetSocketOption(ServiceCtx context, BsdSocket socket, SocketOptionName optionName, long optionValuePosition, long optionValueSize) + { + try + { + byte[] optionValue = new byte[optionValueSize]; + + switch (optionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.Linger: + socket.Handle.GetSocketOption(SocketOptionLevel.Socket, optionName, optionValue); + context.Memory.WriteBytes(optionValuePosition, optionValue); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + socket.Handle.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, optionValue); + context.Memory.WriteBytes(optionValuePosition, optionValue); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException exception) + { + return ConvertError((WsaError)exception.ErrorCode); + } + } + + private LinuxError HandleSetSocketOption(ServiceCtx context, BsdSocket socket, SocketOptionName optionName, long optionValuePosition, long optionValueSize) + { + try + { + switch (optionName) + { + case SocketOptionName.Broadcast: + case SocketOptionName.DontLinger: + case SocketOptionName.Debug: + case SocketOptionName.Error: + case SocketOptionName.KeepAlive: + case SocketOptionName.OutOfBandInline: + case SocketOptionName.ReceiveBuffer: + case SocketOptionName.ReceiveTimeout: + case SocketOptionName.SendBuffer: + case SocketOptionName.SendTimeout: + case SocketOptionName.Type: + case SocketOptionName.ReuseAddress: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, optionName, context.Memory.ReadInt32(optionValuePosition)); + + return LinuxError.SUCCESS; + + case (SocketOptionName)0x200: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, context.Memory.ReadInt32(optionValuePosition)); + + return LinuxError.SUCCESS; + + case SocketOptionName.Linger: + socket.Handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, + new LingerOption(context.Memory.ReadInt32(optionValuePosition) != 0, context.Memory.ReadInt32(optionValuePosition + 4))); + + return LinuxError.SUCCESS; + + default: + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt OptionName: {optionName}"); + + return LinuxError.EOPNOTSUPP; + } + } + catch (SocketException exception) + { + return ConvertError((WsaError)exception.ErrorCode); + } + } + + [Command(21)] + // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0> option_value) -> (i32 ret, u32 bsd_errno) + public ResultCode SetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int level = context.RequestData.ReadInt32(); + int optionName = context.RequestData.ReadInt32(); + + (long bufferPos, long bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.ENOPROTOOPT; + + if (level == 0xFFFF) + { + errno = HandleSetSocketOption(context, socket, (SocketOptionName)optionName, bufferPos, bufferSize); + } + else + { + Logger.PrintWarning(LogClass.ServiceBsd, $"Unsupported SetSockOpt Level: {(SocketOptionLevel)level}"); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(22)] + // Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode Shutdown(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = LinuxError.SUCCESS; + + try + { + socket.Handle.Shutdown((SocketShutdown)how); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(23)] + // ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode ShutdownAllSockets(ServiceCtx context) + { + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = LinuxError.SUCCESS; + + foreach (BsdSocket socket in _sockets) + { + if (socket != null) + { + try + { + socket.Handle.Shutdown((SocketShutdown)how); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + break; + } + } + } + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(24)] + // Write(u32 socket, buffer<i8, 0x21, 0> message) -> (i32 ret, u32 bsd_errno) + public ResultCode Write(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + byte[] sendBuffer = context.Memory.ReadBytes(sendPosition, sendSize); + + try + { + result = socket.Handle.Send(sendBuffer); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(25)] + // Read(u32 socket) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message) + public ResultCode Read(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (long receivePosition, long receiveLength) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + byte[] receivedBuffer = new byte[receiveLength]; + + try + { + result = socket.Handle.Receive(receivedBuffer); + errno = SetResultErrno(socket.Handle, result); + } + catch (SocketException exception) + { + errno = ConvertError((WsaError)exception.ErrorCode); + } + } + + return WriteBsdResult(context, result, errno); + } + + [Command(26)] + // Close(u32 socket) -> (i32 ret, u32 bsd_errno) + public ResultCode Close(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + BsdSocket socket = RetrieveSocket(socketFd); + + if (socket != null) + { + socket.Handle.Close(); + + _sockets[socketFd] = null; + + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, 0, errno); + } + + [Command(27)] + // DuplicateSocket(u32 socket, u64 reserved) -> (i32 ret, u32 bsd_errno) + public ResultCode DuplicateSocket(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + ulong reserved = context.RequestData.ReadUInt64(); + + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; + + if (_isPrivileged) + { + errno = LinuxError.EBADF; + + BsdSocket oldSocket = RetrieveSocket(socketFd); + + if (oldSocket != null) + { + _sockets.Add(oldSocket); + newSockFd = _sockets.Count - 1; + } + } + + return WriteBsdResult(context, newSockFd, errno); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs new file mode 100644 index 00000000..798fc015 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsdcfg")] + class ServerInterface : IpcService + { + public ServerInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs new file mode 100644 index 00000000..421a255c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + enum BsdIoctl + { + AtMark = 0x40047307 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs new file mode 100644 index 00000000..2d5bf429 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocket.cs @@ -0,0 +1,13 @@ +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class BsdSocket + { + public int Family; + public int Type; + public int Protocol; + + public Socket Handle; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs new file mode 100644 index 00000000..ff47a4c7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class PollEvent + { + public enum EventTypeMask + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20 + } + + public int SocketFd { get; private set; } + public BsdSocket Socket { get; private set; } + public EventTypeMask InputEvents { get; private set; } + public EventTypeMask OutputEvents { get; private set; } + + public PollEvent(int socketFd, BsdSocket socket, EventTypeMask inputEvents, EventTypeMask outputEvents) + { + SocketFd = socketFd; + Socket = socket; + InputEvents = inputEvents; + OutputEvents = outputEvents; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs new file mode 100644 index 00000000..f5877697 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:c")] + class IEthInterface : IpcService + { + public IEthInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs new file mode 100644 index 00000000..9832e448 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:i")] + class IEthInterfaceGroup : IpcService + { + public IEthInterfaceGroup(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs new file mode 100644 index 00000000..277bc374 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs @@ -0,0 +1,268 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + [Service("nsd:a")] // Max sessions: 5 + [Service("nsd:u")] // Max sessions: 20 + class IManager : IpcService + { + private NsdSettings _nsdSettings; + private FqdnResolver _fqdnResolver; + + private bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode); + + _nsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode + }; + + _fqdnResolver = new FqdnResolver(_nsdSettings); + + _isInitialized = true; + } + + [Command(10)] + // GetSettingName() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetSettingName(context, out string settingName); + + if (result == ResultCode.Success) + { + byte[] settingNameBuffer = Encoding.UTF8.GetBytes(settingName + '\0'); + + context.Memory.WriteBytes(outputPosition, settingNameBuffer); + } + + return result; + } + + [Command(11)] + // GetEnvironmentIdentifier() -> buffer<unknown<8>, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(context, out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier + '\0'); + + context.Memory.WriteBytes(outputPosition, identifierBuffer); + } + + return result; + } + + [Command(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [Command(13)] + // DeleteSettings(u32) + public ResultCode DeleteSettings(ServiceCtx context) + { + uint unknown = context.RequestData.ReadUInt32(); + + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + if (unknown > 1) + { + return ResultCode.InvalidArgument; + } + + if (unknown == 1) + { + NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier); + + if ((string)environmentIdentifier == _nsdSettings.Environment) + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + else + { + // TODO: Mount the savedata file and return ResultCode. + } + } + else + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + + return ResultCode.Success; + } + + [Command(14)] + // ImportSettings(u32, buffer<unknown, 5>) -> buffer<unknown, 6> + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(15)] + // Unknown(bytes<1>) + public ResultCode Unknown(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(20)] + // Resolve(buffer<unknown<0x100>, 0x15>) -> buffer<unknown<0x100>, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + return result; + } + + [Command(21)] + // ResolveEx(buffer<unknown<0x100>, 0x15>) -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [Command(30)] + // GetNasServiceSetting(buffer<unknown<0x10>, 0x15>) -> buffer<unknown<0x108>, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(31)] + // GetNasServiceSettingEx(buffer<unknown<0x10>, 0x15>) -> (u32, buffer<unknown<0x108>, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(40)] + // GetNasRequestFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(41)] + // GetNasRequestFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(42)] + // GetNasApiFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(43)] + // GetNasApiFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(50)] + // GetCurrentSetting() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(60)] + // ReadSaveDataFromFsForTest() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Call nn::nsd::detail::fs::ReadSaveDataWithOffset() at offset 0 to write the + // whole savedata inside the buffer. + + Logger.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [Command(61)] + // WriteSaveDataToFsForTest(buffer<unknown<0x12bf0>, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(63)] + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs new file mode 100644 index 00000000..aaab7b3b --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs @@ -0,0 +1,131 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager +{ + class FqdnResolver + { + private const string _dummyAddress = "unknown.dummy.nintendo.net"; + + private NsdSettings _nsdSettings; + + public FqdnResolver(NsdSettings nsdSettings) + { + _nsdSettings = nsdSettings; + } + + public ResultCode GetSettingName(ServiceCtx context, out string settingName) + { + if (_nsdSettings.TestMode) + { + settingName = ""; + + return ResultCode.NotImplemented; + } + else + { + settingName = ""; + + if (true) // TODO: Determine field (struct + 0x2C) + { + settingName = _nsdSettings.Environment; + + return ResultCode.Success; + } + + return ResultCode.NullOutputObject; + } + } + + public ResultCode GetEnvironmentIdentifier(ServiceCtx context, out string identifier) + { + if (_nsdSettings.TestMode) + { + identifier = "rre"; + + return ResultCode.NotImplemented; + } + else + { + identifier = _nsdSettings.Environment; + } + + return ResultCode.Success; + } + + public ResultCode Resolve(ServiceCtx context, string address, out string resolvedAddress) + { + if (address != "api.sect.srv.nintendo.net" || address != "conntest.nintendowifi.net") + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", _nsdSettings.Environment); + + resolvedAddress = ""; + + if (_nsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!_nsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + switch (address) + { + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com": // lp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "accounts.nintendo.com": // lp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + /* + // TODO: Determine fields of the struct. + case "": // + 0xEB8 || + 0x2BE8 + resolvedAddress = ""; // + 0xEB8 + 0x300 || + 0x2BE8 + 0x300 + break; + */ + default: + resolvedAddress = address; + break; + } + } + else + { + resolvedAddress = address; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + (long inputPosition, long inputSize) = context.Request.GetBufferType0x21(); + + byte[] addressBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + string address = Encoding.UTF8.GetString(addressBuffer); + + resultCode = Resolve(context, address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = _dummyAddress; + } + + if (_nsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs new file mode 100644 index 00000000..0e636f9a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + enum ResultCode + { + ModuleId = 141, + ErrorCodeShift = 9, + + Success = 0, + + NotImplemented = ( 1 << ErrorCodeShift) | ModuleId, + InvalidObject1 = ( 3 << ErrorCodeShift) | ModuleId, + InvalidObject2 = ( 4 << ErrorCodeShift) | ModuleId, + NullOutputObject = ( 5 << ErrorCodeShift) | ModuleId, + SettingsNotLoaded = ( 6 << ErrorCodeShift) | ModuleId, + InvalidArgument = ( 8 << ErrorCodeShift) | ModuleId, + SettingsNotInitialized = ( 10 << ErrorCodeShift) | ModuleId, + ServiceNotInitialized = (400 << ErrorCodeShift) | ModuleId, + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs new file mode 100644 index 00000000..2b31cb1d --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + class NsdSettings + { + public bool Initialized; + public bool TestMode; + public string Environment = "lp1"; // or "dd1" if devkit. + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs new file mode 100644 index 00000000..1cf2aa1c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -0,0 +1,387 @@ +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + [Service("sfdnsres")] + class IResolver : IpcService + { + public IResolver(ServiceCtx context) { } + + private long SerializeHostEnt(ServiceCtx context, IPHostEntry hostEntry, List<IPAddress> addresses = null) + { + long originalBufferPosition = context.Request.ReceiveBuff[0].Position; + long bufferPosition = originalBufferPosition; + long bufferSize = context.Request.ReceiveBuff[0].Size; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.WriteBytes(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += hostName.Length; + + // h_aliases list size + context.Memory.WriteInt32(bufferPosition, IPAddress.HostToNetworkOrder(hostEntry.Aliases.Length)); + bufferPosition += 4; + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.WriteBytes(bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += alias.Length + 1; + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.WriteInt16(bufferPosition, IPAddress.HostToNetworkOrder((short)2)); + bufferPosition += 2; + + // h_length but it's a short + context.Memory.WriteInt16(bufferPosition, IPAddress.HostToNetworkOrder((short)4)); + bufferPosition += 2; + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.WriteInt32(bufferPosition, addresses != null ? IPAddress.HostToNetworkOrder(addresses.Count) : 0); + bufferPosition += 4; + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.WriteInt32(bufferPosition, IPAddress.HostToNetworkOrder(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += 4; + } + } + + return bufferPosition - originalBufferPosition; + } + + private string GetGaiStringErrorFromErrorCode(GaiError errorCode) + { + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + switch (errorCode) + { + case GaiError.AddressFamily: + return "Address family for hostname not supported"; + case GaiError.Again: + return "Temporary failure in name resolution"; + case GaiError.BadFlags: + return "Invalid value for ai_flags"; + case GaiError.Fail: + return "Non-recoverable failure in name resolution"; + case GaiError.Family: + return "ai_family not supported"; + case GaiError.Memory: + return "Memory allocation failure"; + case GaiError.NoData: + return "No address associated with hostname"; + case GaiError.NoName: + return "hostname nor servname provided, or not known"; + case GaiError.Service: + return "servname not supported for ai_socktype"; + case GaiError.SocketType: + return "ai_socktype not supported"; + case GaiError.System: + return "System error returned in errno"; + case GaiError.BadHints: + return "Invalid value for hints"; + case GaiError.Protocol: + return "Resolved protocol is unknown"; + case GaiError.Overflow: + return "Argument buffer overflow"; + case GaiError.Max: + return "Unknown error"; + default: + return "Success"; + } + } + + private string GetHostStringErrorFromErrorCode(NetDbError errorCode) + { + if (errorCode <= NetDbError.Internal) + { + return "Resolver internal error"; + } + + switch (errorCode) + { + case NetDbError.Success: + return "Resolver Error 0 (no error)"; + case NetDbError.HostNotFound: + return "Unknown host"; + case NetDbError.TryAgain: + return "Host name lookup failure"; + case NetDbError.NoRecovery: + return "Unknown server error"; + case NetDbError.NoData: + return "No address associated with name"; + default: + return "Unknown resolver error"; + } + } + + private List<IPAddress> GetIpv4Addresses(IPHostEntry hostEntry) + { + List<IPAddress> result = new List<IPAddress>(); + foreach (IPAddress ip in hostEntry.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + result.Add(ip); + } + return result; + } + + [Command(0)] + // SetDnsAddressesPrivate(u32, buffer<unknown, 5, 0>) + public ResultCode SetDnsAddressesPrivate(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + long bufferPosition = context.Request.SendBuff[0].Position; + long bufferSize = context.Request.SendBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.NotAllocated; + } + + [Command(1)] + // GetDnsAddressPrivate(u32) -> buffer<unknown, 6, 0> + public ResultCode GetDnsAddressesPrivate(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake completeness. + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.NotAllocated; + } + + [Command(2)] + // GetHostByName(u8, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByName(ServiceCtx context) + { + byte[] rawName = context.Memory.ReadBytes(context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size); + string name = Encoding.ASCII.GetString(rawName).TrimEnd('\0'); + + // TODO: use params + bool enableNsdResolve = context.RequestData.ReadInt32() == 1; + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Overflow; + long serializedSize = 0; + + if (name.Length <= 255) + { + try + { + hostEntry = Dns.GetHostEntry(name); + } + catch (SocketException exception) + { + netDbErrorCode = NetDbError.Internal; + + if (exception.ErrorCode == 11001) + { + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else if (exception.ErrorCode == 11002) + { + netDbErrorCode = NetDbError.TryAgain; + } + else if (exception.ErrorCode == 11003) + { + netDbErrorCode = NetDbError.NoRecovery; + } + else if (exception.ErrorCode == 11004) + { + netDbErrorCode = NetDbError.NoData; + } + else if (exception.ErrorCode == 10060) + { + errno = GaiError.Again; + } + } + } + else + { + netDbErrorCode = NetDbError.HostNotFound; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + + List<IPAddress> addresses = GetIpv4Addresses(hostEntry); + + if (addresses.Count == 0) + { + errno = GaiError.NoData; + netDbErrorCode = NetDbError.NoAddress; + } + else + { + serializedSize = SerializeHostEnt(context, hostEntry, addresses); + } + } + + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + + return ResultCode.Success; + } + + [Command(3)] + // GetHostByAddr(u32, u32, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByAddress(ServiceCtx context) + { + byte[] rawIp = context.Memory.ReadBytes(context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size); + + // TODO: use params + uint socketLength = context.RequestData.ReadUInt32(); + uint type = context.RequestData.ReadUInt32(); + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + long serializedSize = 0; + + if (rawIp.Length == 4) + { + try + { + IPAddress address = new IPAddress(rawIp); + + hostEntry = Dns.GetHostEntry(address); + } + catch (SocketException exception) + { + netDbErrorCode = NetDbError.Internal; + if (exception.ErrorCode == 11001) + { + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else if (exception.ErrorCode == 11002) + { + netDbErrorCode = NetDbError.TryAgain; + } + else if (exception.ErrorCode == 11003) + { + netDbErrorCode = NetDbError.NoRecovery; + } + else if (exception.ErrorCode == 11004) + { + netDbErrorCode = NetDbError.NoData; + } + else if (exception.ErrorCode == 10060) + { + errno = GaiError.Again; + } + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEnt(context, hostEntry, GetIpv4Addresses(hostEntry)); + } + + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + + return ResultCode.Success; + } + + [Command(4)] + // GetHostStringError(u32) -> buffer<unknown, 6, 0> + public ResultCode GetHostStringError(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + string errorString = GetHostStringErrorFromErrorCode(errorCode); + + if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + { + resultCode = 0; + context.Memory.WriteBytes(context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); + } + + return resultCode; + } + + [Command(5)] + // GetGaiStringError(u32) -> buffer<unknown, 6, 0> + public ResultCode GetGaiStringError(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + string errorString = GetGaiStringErrorFromErrorCode(errorCode); + + if (errorString.Length + 1 <= context.Request.ReceiveBuff[0].Size) + { + resultCode = 0; + context.Memory.WriteBytes(context.Request.ReceiveBuff[0].Position, Encoding.ASCII.GetBytes(errorString + '\0')); + } + + return resultCode; + } + + [Command(8)] + // RequestCancelHandle(u64, pid) -> u32 + public ResultCode RequestCancelHandle(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.Success; + } + + [Command(9)] + // CancelSocketCall(u32, u64, pid) + public ResultCode CancelSocketCall(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + ulong unknown1 = context.RequestData.ReadUInt64(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0, unknown1 }); + + return ResultCode.Success; + } + + [Command(11)] + // ClearDnsAddresses(u32) + public ResultCode ClearDnsAddresses(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + + Logger.PrintStub(LogClass.ServiceSfdnsres, new { unknown0 }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs new file mode 100644 index 00000000..f9f28b44 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum GaiError + { + Success, + AddressFamily, + Again, + BadFlags, + Fail, + Family, + Memory, + NoData, + NoName, + Service, + SocketType, + System, + BadHints, + Protocol, + Overflow, + Max + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs new file mode 100644 index 00000000..3c04c049 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum NetDbError + { + Internal = -1, + Success, + HostNotFound, + TryAgain, + NoRecovery, + NoData, + NoAddress = NoData + } +}
\ No newline at end of file |
