diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Sockets | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Sockets')
44 files changed, 5135 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs new file mode 100644 index 00000000..a93f176a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs @@ -0,0 +1,184 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class BsdContext + { + private static ConcurrentDictionary<ulong, BsdContext> _registry = new ConcurrentDictionary<ulong, BsdContext>(); + + private readonly object _lock = new object(); + + private List<IFileDescriptor> _fds; + + private BsdContext() + { + _fds = new List<IFileDescriptor>(); + } + + public ISocket RetrieveSocket(int socketFd) + { + IFileDescriptor file = RetrieveFileDescriptor(socketFd); + + if (file is ISocket socket) + { + return socket; + } + + return null; + } + + public IFileDescriptor RetrieveFileDescriptor(int fd) + { + lock (_lock) + { + if (fd >= 0 && _fds.Count > fd) + { + return _fds[fd]; + } + } + + return null; + } + + public List<IFileDescriptor> RetrieveFileDescriptorsFromMask(ReadOnlySpan<byte> mask) + { + List<IFileDescriptor> fds = new(); + + for (int i = 0; i < mask.Length; i++) + { + byte current = mask[i]; + + while (current != 0) + { + int bit = BitOperations.TrailingZeroCount(current); + current &= (byte)~(1 << bit); + int fd = i * 8 + bit; + + fds.Add(RetrieveFileDescriptor(fd)); + } + } + + return fds; + } + + public int RegisterFileDescriptor(IFileDescriptor file) + { + lock (_lock) + { + for (int fd = 0; fd < _fds.Count; fd++) + { + if (_fds[fd] == null) + { + _fds[fd] = file; + + return fd; + } + } + + _fds.Add(file); + + return _fds.Count - 1; + } + } + + public void BuildMask(List<IFileDescriptor> fds, Span<byte> mask) + { + foreach (IFileDescriptor descriptor in fds) + { + int fd = _fds.IndexOf(descriptor); + + mask[fd >> 3] |= (byte)(1 << (fd & 7)); + } + } + + public int DuplicateFileDescriptor(int fd) + { + IFileDescriptor oldFile = RetrieveFileDescriptor(fd); + + if (oldFile != null) + { + lock (_lock) + { + oldFile.Refcount++; + + return RegisterFileDescriptor(oldFile); + } + } + + return -1; + } + + public bool CloseFileDescriptor(int fd) + { + IFileDescriptor file = RetrieveFileDescriptor(fd); + + if (file != null) + { + file.Refcount--; + + if (file.Refcount <= 0) + { + file.Dispose(); + } + + lock (_lock) + { + _fds[fd] = null; + } + + return true; + } + + return false; + } + + public LinuxError ShutdownAllSockets(BsdSocketShutdownFlags how) + { + lock (_lock) + { + foreach (IFileDescriptor file in _fds) + { + if (file is ISocket socket) + { + LinuxError errno = socket.Shutdown(how); + + if (errno != LinuxError.SUCCESS) + { + return errno; + } + } + } + } + + return LinuxError.SUCCESS; + } + + public static BsdContext GetOrRegister(ulong processId) + { + BsdContext context = GetContext(processId); + + if (context == null) + { + context = new BsdContext(); + + _registry.TryAdd(processId, context); + } + + return context; + } + + public static BsdContext GetContext(ulong processId) + { + if (!_registry.TryGetValue(processId, out BsdContext processContext)) + { + return null; + } + + return processContext; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs new file mode 100644 index 00000000..b63864c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -0,0 +1,1121 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsd:s", true)] + [Service("bsd:u", false)] + class IClient : IpcService + { + private static readonly List<IPollManager> _pollManagers = new List<IPollManager> + { + EventFileDescriptorPollManager.Instance, + ManagedSocketPollManager.Instance + }; + + private BsdContext _context; + private bool _isPrivileged; + + public IClient(ServiceCtx context, bool isPrivileged) : base(context.Device.System.BsdServer) + { + _isPrivileged = isPrivileged; + } + + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = LinuxError.SUCCESS) + { + if (errorCode != LinuxError.SUCCESS) + { + result = -1; + } + + context.ResponseData.Write(result); + context.ResponseData.Write((int)errorCode); + + return ResultCode.Success; + } + + private static AddressFamily ConvertBsdAddressFamily(BsdAddressFamily family) + { + switch (family) + { + case BsdAddressFamily.Unspecified: + return AddressFamily.Unspecified; + case BsdAddressFamily.InterNetwork: + return AddressFamily.InterNetwork; + case BsdAddressFamily.InterNetworkV6: + return AddressFamily.InterNetworkV6; + case BsdAddressFamily.Unknown: + return AddressFamily.Unknown; + default: + throw new NotImplementedException(family.ToString()); + } + } + + private LinuxError SetResultErrno(IFileDescriptor socket, int result) + { + return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private ResultCode SocketInternal(ServiceCtx context, bool exempt) + { + BsdAddressFamily domain = (BsdAddressFamily)context.RequestData.ReadInt32(); + BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + + BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift); + type &= BsdSocketType.TypeMask; + + if (domain == BsdAddressFamily.Unknown) + { + return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((type == BsdSocketType.Seqpacket || type == BsdSocketType.Raw) && !_isPrivileged) + { + if (domain != BsdAddressFamily.InterNetwork || type != BsdSocketType.Raw || protocol != ProtocolType.Icmp) + { + return WriteBsdResult(context, -1, LinuxError.ENOENT); + } + } + + AddressFamily netDomain = ConvertBsdAddressFamily(domain); + + if (protocol == ProtocolType.IP) + { + if (type == BsdSocketType.Stream) + { + protocol = ProtocolType.Tcp; + } + else if (type == BsdSocketType.Dgram) + { + protocol = ProtocolType.Udp; + } + } + + ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol); + newBsdSocket.Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newBsdSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + if (exempt) + { + newBsdSocket.Disconnect(); + } + + return WriteBsdResult(context, newSockFd, errno); + } + + private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket socket, bool isRemote) + { + IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint; + + context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + + [CommandCmif(0)] + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject<copy, transfer_memory>, pid) -> u32 bsd_errno + public ResultCode RegisterClient(ServiceCtx context) + { + _context = BsdContext.GetOrRegister(context.Request.HandleDesc.PId); + + /* + 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.Stub?.PrintStub(LogClass.ServiceBsd); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // StartMonitoring(u64, pid) + public ResultCode StartMonitoring(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode Socket(ServiceCtx context) + { + return SocketInternal(context, false); + } + + [CommandCmif(3)] + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode SocketExempt(ServiceCtx context) + { + return SocketInternal(context, true); + } + + [CommandCmif(4)] + // Open(u32 flags, array<unknown, 0x21> path) -> (i32 ret, u32 bsd_errno) + public ResultCode Open(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + int flags = context.RequestData.ReadInt32(); + + byte[] rawPath = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, rawPath); + + string path = Encoding.ASCII.GetString(rawPath); + + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { path, flags }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Select(u32 nfds, nn::socket::timeval 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) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (ulong readFdsInBufferPosition, ulong readFdsInBufferSize) = context.Request.GetBufferType0x21(0); + (ulong writeFdsInBufferPosition, ulong writeFdsInBufferSize) = context.Request.GetBufferType0x21(1); + (ulong errorFdsInBufferPosition, ulong errorFdsInBufferSize) = context.Request.GetBufferType0x21(2); + + (ulong readFdsOutBufferPosition, ulong readFdsOutBufferSize) = context.Request.GetBufferType0x22(0); + (ulong writeFdsOutBufferPosition, ulong writeFdsOutBufferSize) = context.Request.GetBufferType0x22(1); + (ulong errorFdsOutBufferPosition, ulong errorFdsOutBufferSize) = context.Request.GetBufferType0x22(2); + + List<IFileDescriptor> readFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(readFdsInBufferPosition, (int)readFdsInBufferSize)); + List<IFileDescriptor> writeFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(writeFdsInBufferPosition, (int)writeFdsInBufferSize)); + List<IFileDescriptor> errorFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(errorFdsInBufferPosition, (int)errorFdsInBufferSize)); + + int actualFdsCount = readFds.Count + writeFds.Count + errorFds.Count; + + if (fdsCount == 0 || actualFdsCount == 0) + { + WriteBsdResult(context, 0); + + return ResultCode.Success; + } + + PollEvent[] events = new PollEvent[actualFdsCount]; + + int index = 0; + + foreach (IFileDescriptor fd in readFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Input }, fd); + + index++; + } + + foreach (IFileDescriptor fd in writeFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Output }, fd); + + index++; + } + + foreach (IFileDescriptor fd in errorFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Error }, fd); + + index++; + } + + List<PollEvent>[] eventsByPollManager = new List<PollEvent>[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List<PollEvent>(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + } + } + } + + int updatedCount = 0; + + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Count > 0) + { + _pollManagers[i].Select(eventsByPollManager[i], timeout, out int updatedPollCount); + updatedCount += updatedPollCount; + } + } + + readFds.Clear(); + writeFds.Clear(); + errorFds.Clear(); + + foreach (PollEvent pollEvent in events) + { + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Contains(pollEvent)) + { + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Input)) + { + readFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorFds.Add(pollEvent.FileDescriptor); + } + } + } + } + + using var readFdsOut = context.Memory.GetWritableRegion(readFdsOutBufferPosition, (int)readFdsOutBufferSize); + using var writeFdsOut = context.Memory.GetWritableRegion(writeFdsOutBufferPosition, (int)writeFdsOutBufferSize); + using var errorFdsOut = context.Memory.GetWritableRegion(errorFdsOutBufferPosition, (int)errorFdsOutBufferSize); + + _context.BuildMask(readFds, readFdsOut.Memory.Span); + _context.BuildMask(writeFds, writeFdsOut.Memory.Span); + _context.BuildMask(errorFds, errorFdsOut.Memory.Span); + + WriteBsdResult(context, updatedCount); + + return ResultCode.Success; + } + + [CommandCmif(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(); + + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + + if (timeout < -1 || fdsCount < 0 || (ulong)(fdsCount * 8) > inputBufferSize) + { + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + + PollEvent[] events = new PollEvent[fdsCount]; + + for (int i = 0; i < fdsCount; i++) + { + PollEventData pollEventData = context.Memory.Read<PollEventData>(inputBufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>())); + + IFileDescriptor fileDescriptor = _context.RetrieveFileDescriptor(pollEventData.SocketFd); + + if (fileDescriptor == null) + { + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + + events[i] = new PollEvent(pollEventData, fileDescriptor); + } + + List<PollEvent> discoveredEvents = new List<PollEvent>(); + List<PollEvent>[] eventsByPollManager = new List<PollEvent>[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List<PollEvent>(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + discoveredEvents.Add(evnt); + } + } + } + + foreach (PollEvent evnt in events) + { + if (!discoveredEvents.Contains(evnt)) + { + Logger.Error?.Print(LogClass.ServiceBsd, $"Poll operation is not supported for {evnt.FileDescriptor.GetType().Name}!"); + + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + } + + int updateCount = 0; + + LinuxError errno = LinuxError.SUCCESS; + + if (fdsCount != 0) + { + bool IsUnexpectedLinuxError(LinuxError error) + { + return error != LinuxError.SUCCESS && error != LinuxError.ETIMEDOUT; + } + + // Hybrid approach + long budgetLeftMilliseconds; + + if (timeout == -1) + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + uint.MaxValue; + } + else + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + timeout; + } + + do + { + for (int i = 0; i < eventsByPollManager.Length; i++) + { + if (eventsByPollManager[i].Count == 0) + { + continue; + } + + errno = _pollManagers[i].Poll(eventsByPollManager[i], 0, out updateCount); + + if (IsUnexpectedLinuxError(errno)) + { + break; + } + + if (updateCount > 0) + { + break; + } + } + + if (updateCount > 0) + { + break; + } + + // If we are here, that mean nothing was available, sleep for 50ms + context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + } + while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); + } + else if (timeout == -1) + { + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProcess. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + context.Device.System.KernelContext.Syscall.SleepThread(timeout); + } + + // TODO: Spanify + for (int i = 0; i < fdsCount; i++) + { + context.Memory.Write(outputBufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>()), events[i].Data); + } + + // In case of non blocking call timeout should not be returned. + if (timeout == 0 && errno == LinuxError.ETIMEDOUT) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, updateCount, errno); + } + + [CommandCmif(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.Stub?.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [CommandCmif(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(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Receive(out result, receiveRegion.Memory.Span, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(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(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(0); + (ulong sockAddrOutPosition, ulong sockAddrOutSize) = context.Request.GetBufferType0x22(1); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.ReceiveFrom(out result, receiveRegion.Memory.Span, receiveRegion.Memory.Span.Length, socketFlags, out IPEndPoint endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + + if (sockAddrOutSize != 0 && sockAddrOutSize >= (ulong) Unsafe.SizeOf<BsdSockAddr>()) + { + context.Memory.Write(sockAddrOutPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + else + { + errno = LinuxError.ENOMEM; + } + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(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(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Send(out result, sendBuffer, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(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(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(0); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(1); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.SendTo(out result, sendBuffer, sendBuffer.Length, socketFlags, endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(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(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Accept(out ISocket newSocket); + + if (newSocket == null && errno == LinuxError.SUCCESS) + { + errno = LinuxError.EWOULDBLOCK; + } + else if (errno == LinuxError.SUCCESS) + { + int newSockFd = _context.RegisterFileDescriptor(newSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + else + { + WriteSockAddr(context, bufferPos, newSocket, true); + } + + WriteBsdResult(context, newSockFd, errno); + + context.ResponseData.Write(0x10); + + return ResultCode.Success; + } + } + + return WriteBsdResult(context, -1, errno); + } + + [CommandCmif(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(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.Bind(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.Connect(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + if (socket != null) + { + errno = LinuxError.ENOTCONN; + + if (socket.RemoteEndPoint != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPosition, socket, true); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>()); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, false); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>()); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + WritableRegion optionValue = context.Memory.GetWritableRegion(bufferPosition, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.GetSocketOption(option, level, optionValue.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + optionValue.Dispose(); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Listen(backlog); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + switch (cmd) + { + case BsdIoctl.AtMark: + errno = LinuxError.SUCCESS; + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + + // FIXME: OOB not implemented. + context.Memory.Write(bufferPosition, 0); + break; + + default: + errno = LinuxError.EOPNOTSUPP; + + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}"); + break; + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + if (cmd == 0x3) + { + result = !socket.Blocking ? 0x800 : 0; + } + else if (cmd == 0x4 && arg == 0x800) + { + socket.Blocking = false; + result = 0; + } + else + { + errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(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(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> optionValue = context.Memory.GetSpan(bufferPos, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.SetSocketOption(option, level, optionValue); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = socket.Shutdown((BsdSocketShutdownFlags)how); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(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 = _context.ShutdownAllSockets((BsdSocketShutdownFlags)how); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(24)] + // Write(u32 fd, buffer<i8, 0x21, 0> message) -> (i32 ret, u32 bsd_errno) + public ResultCode Write(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Write(out result, sendBuffer); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(25)] + // Read(u32 fd) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message) + public ResultCode Read(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Read(out result, receiveRegion.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(26)] + // Close(u32 fd) -> (i32 ret, u32 bsd_errno) + public ResultCode Close(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + + if (_context.CloseFileDescriptor(fd)) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(27)] + // DuplicateSocket(u32 fd, u64 reserved) -> (i32 ret, u32 bsd_errno) + public ResultCode DuplicateSocket(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + ulong reserved = context.RequestData.ReadUInt64(); + + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; + + if (_isPrivileged) + { + errno = LinuxError.SUCCESS; + + newSockFd = _context.DuplicateFileDescriptor(fd); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + } + + return WriteBsdResult(context, newSockFd, errno); + } + + + [CommandCmif(29)] // 7.0.0+ + // RecvMMsg(u32 fd, u32 vlen, u32 flags, u32 reserved, nn::socket::TimeVal timeout) -> (i32 ret, u32 bsd_errno, buffer<bytes, 6> message); + public ResultCode RecvMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + uint reserved = context.RequestData.ReadUInt32(); + TimeVal timeout = context.RequestData.ReadStruct<TimeVal>(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.RecvMMsg(out result, message, socketFlags, timeout); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(30)] // 7.0.0+ + // SendMMsg(u32 fd, u32 vlen, u32 flags) -> (i32 ret, u32 bsd_errno, buffer<bytes, 6> message); + public ResultCode SendMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.SendMMsg(out result, message, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(31)] // 7.0.0+ + // EventFd(nn::socket::EventFdFlags flags, u64 initval) -> (i32 ret, u32 bsd_errno) + public ResultCode EventFd(ServiceCtx context) + { + EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + ulong initialValue = context.RequestData.ReadUInt64(); + + EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newEventFile); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + return WriteBsdResult(context, newSockFd, errno); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs new file mode 100644 index 00000000..9d4f81ce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface IFileDescriptor : IDisposable + { + bool Blocking { get; set; } + int Refcount { get; set; } + + LinuxError Read(out int readSize, Span<byte> buffer); + + LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs new file mode 100644 index 00000000..05874868 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs @@ -0,0 +1,53 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface ISocket : IDisposable, IFileDescriptor + { + IPEndPoint RemoteEndPoint { get; } + IPEndPoint LocalEndPoint { get; } + + AddressFamily AddressFamily { get; } + + SocketType SocketType { get; } + + ProtocolType ProtocolType { get; } + + IntPtr Handle { get; } + + LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags); + + LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint); + + LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags); + + LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint); + + LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout); + + LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags); + + LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue); + + LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue); + + bool Poll(int microSeconds, SelectMode mode); + + LinuxError Bind(IPEndPoint localEndPoint); + + LinuxError Connect(IPEndPoint remoteEndPoint); + + LinuxError Listen(int backlog); + + LinuxError Accept(out ISocket newSocket); + + void Disconnect(); + + LinuxError Shutdown(BsdSocketShutdownFlags how); + + void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs new file mode 100644 index 00000000..6514d485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs @@ -0,0 +1,153 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptor : IFileDescriptor + { + private ulong _value; + private readonly EventFdFlags _flags; + + private object _lock = new object(); + + public bool Blocking { get => !_flags.HasFlag(EventFdFlags.NonBlocking); set => throw new NotSupportedException(); } + + public ManualResetEvent WriteEvent { get; } + public ManualResetEvent ReadEvent { get; } + + public EventFileDescriptor(ulong value, EventFdFlags flags) + { + // FIXME: We should support blocking operations. + // Right now they can't be supported because it would cause the + // service to lock up as we only have one thread processing requests. + flags |= EventFdFlags.NonBlocking; + + _value = value; + _flags = flags; + + WriteEvent = new ManualResetEvent(false); + ReadEvent = new ManualResetEvent(false); + UpdateEventStates(); + } + + public int Refcount { get; set; } + + public void Dispose() + { + WriteEvent.Dispose(); + ReadEvent.Dispose(); + } + + private void ResetEventStates() + { + WriteEvent.Reset(); + ReadEvent.Reset(); + } + + private void UpdateEventStates() + { + if (_value > 0) + { + ReadEvent.Set(); + } + + if (_value != uint.MaxValue - 1) + { + WriteEvent.Set(); + } + } + + public LinuxError Read(out int readSize, Span<byte> buffer) + { + if (buffer.Length < sizeof(ulong)) + { + readSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0]; + + if (_value == 0) + { + if (Blocking) + { + while (_value == 0) + { + Monitor.Wait(_lock); + } + } + else + { + readSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + readSize = sizeof(ulong); + + if (_flags.HasFlag(EventFdFlags.Semaphore)) + { + --_value; + + count = 1; + } + else + { + count = _value; + + _value = 0; + } + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + + public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer) + { + if (!MemoryMarshal.TryRead(buffer, out ulong count) || count == ulong.MaxValue) + { + writeSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + if (_value > _value + count) + { + if (Blocking) + { + Monitor.Wait(_lock); + } + else + { + writeSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + writeSize = sizeof(ulong); + + _value += count; + Monitor.Pulse(_lock); + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs new file mode 100644 index 00000000..e0ab68c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs @@ -0,0 +1,122 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptorPollManager : IPollManager + { + private static EventFileDescriptorPollManager _instance; + + public static EventFileDescriptorPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new EventFileDescriptorPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is EventFileDescriptor; + } + + public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount) + { + updatedCount = 0; + + List<ManualResetEvent> waiters = new List<ManualResetEvent>(); + + for (int i = 0; i < events.Count; i++) + { + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + bool isValidEvent = false; + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || + evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + waiters.Add(socket.ReadEvent); + + isValidEvent = true; + } + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + waiters.Add(socket.WriteEvent); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + + return LinuxError.EINVAL; + } + } + + int index = WaitHandle.WaitAny(waiters.ToArray(), timeoutMilliseconds); + + if (index != WaitHandle.WaitTimeout) + { + for (int i = 0; i < events.Count; i++) + { + PollEventTypeMask outputEvents = 0; + + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + if (socket.ReadEvent.WaitOne(0)) + { + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + outputEvents |= PollEventTypeMask.Input; + } + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + outputEvents |= PollEventTypeMask.UrgentInput; + } + } + + if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + && socket.WriteEvent.WaitOne(0)) + { + outputEvents |= PollEventTypeMask.Output; + } + + + if (outputEvents != 0) + { + evnt.Data.OutputEvents = outputEvents; + + updatedCount++; + } + } + } + else + { + return LinuxError.ETIMEDOUT; + } + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount) + { + // TODO: Implement Select for event file descriptors + updatedCount = 0; + + return LinuxError.EOPNOTSUPP; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs new file mode 100644 index 00000000..75efc49a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs @@ -0,0 +1,530 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocket : ISocket + { + public int Refcount { get; set; } + + public AddressFamily AddressFamily => Socket.AddressFamily; + + public SocketType SocketType => Socket.SocketType; + + public ProtocolType ProtocolType => Socket.ProtocolType; + + public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } + + public IntPtr Handle => Socket.Handle; + + public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; + + public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint; + + public Socket Socket { get; } + + public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + Socket = new Socket(addressFamily, socketType, protocolType); + Refcount = 1; + } + + private ManagedSocket(Socket socket) + { + Socket = socket; + Refcount = 1; + } + + private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) + { + SocketFlags socketFlags = SocketFlags.None; + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) + { + socketFlags |= SocketFlags.OutOfBand; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek)) + { + socketFlags |= SocketFlags.Peek; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute)) + { + socketFlags |= SocketFlags.DontRoute; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc)) + { + socketFlags |= SocketFlags.Truncated; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc)) + { + socketFlags |= SocketFlags.ControlDataTruncated; + } + + bsdSocketFlags &= ~(BsdSocketFlags.Oob | + BsdSocketFlags.Peek | + BsdSocketFlags.DontRoute | + BsdSocketFlags.DontWait | + BsdSocketFlags.Trunc | + BsdSocketFlags.CTrunc); + + if (bsdSocketFlags != BsdSocketFlags.None) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}"); + } + + return socketFlags; + } + + public LinuxError Accept(out ISocket newSocket) + { + try + { + newSocket = new ManagedSocket(Socket.Accept()); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + newSocket = null; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Bind(IPEndPoint localEndPoint) + { + try + { + Socket.Bind(localEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public void Close() + { + Socket.Close(); + } + + public LinuxError Connect(IPEndPoint remoteEndPoint) + { + try + { + Socket.Connect(remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK) + { + return LinuxError.EINPROGRESS; + } + else + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } + + public void Disconnect() + { + Socket.Disconnect(true); + } + + public void Dispose() + { + Socket.Close(); + Socket.Dispose(); + } + + public LinuxError Listen(int backlog) + { + try + { + Socket.Listen(backlog); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public bool Poll(int microSeconds, SelectMode mode) + { + return Socket.Poll(microSeconds, mode); + } + + public LinuxError Shutdown(BsdSocketShutdownFlags how) + { + try + { + Socket.Shutdown((SocketShutdown)how); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags) + { + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags)); + + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint) + { + remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + EndPoint temp = new IPEndPoint(IPAddress.Any, 0); + + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + if (!Socket.IsBound) + { + receiveSize = -1; + + return LinuxError.EOPNOTSUPP; + } + + receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp); + + remoteEndPoint = (IPEndPoint)temp; + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags) + { + try + { + sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags)); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint) + { + try + { + sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + byte[] tempOptionValue = new byte[optionValue.Length]; + + Socket.GetSocketOption(level, optionName, tempOptionValue); + + tempOptionValue.AsSpan().CopyTo(optionValue); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue); + + if (level == SocketOptionLevel.Socket && option == BsdSocketOption.SoLinger) + { + int value2 = 0; + + if (optionValue.Length >= 8) + { + value2 = MemoryMarshal.Read<int>(optionValue[4..]); + } + + Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); + } + else + { + Socket.SetSocketOption(level, optionName, value); + } + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Read(out int readSize, Span<byte> buffer) + { + return Receive(out readSize, buffer, BsdSocketFlags.None); + } + + public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer) + { + return Send(out writeSize, buffer, BsdSocketFlags.None); + } + + private bool CanSupportMMsgHdr(BsdMMsgHdr message) + { + for (int i = 0; i < message.Messages.Length; i++) + { + if (message.Messages[i].Name != null || + message.Messages[i].Control != null) + { + return false; + } + } + + return true; + } + + private static IList<ArraySegment<byte>> ConvertMessagesToBuffer(BsdMMsgHdr message) + { + int segmentCount = 0; + int index = 0; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + segmentCount += msgHeader.Iov.Length; + } + + ArraySegment<byte>[] buffers = new ArraySegment<byte>[segmentCount]; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + foreach (byte[] iov in msgHeader.Iov) + { + buffers[index++] = new ArraySegment<byte>(iov); + } + + // Clear the length + msgHeader.Length = 0; + } + + return buffers; + } + + private static void UpdateMessages(out int vlen, BsdMMsgHdr message, int transferedSize) + { + int bytesLeft = transferedSize; + int index = 0; + + while (bytesLeft > 0) + { + // First ensure we haven't finished all buffers + if (index >= message.Messages.Length) + { + break; + } + + BsdMsgHdr msgHeader = message.Messages[index]; + + int possiblyTransferedBytes = 0; + + foreach (byte[] iov in msgHeader.Iov) + { + possiblyTransferedBytes += iov.Length; + } + + int storedBytes; + + if (bytesLeft > possiblyTransferedBytes) + { + storedBytes = possiblyTransferedBytes; + index++; + } + else + { + storedBytes = bytesLeft; + } + + msgHeader.Length = (uint)storedBytes; + bytesLeft -= storedBytes; + } + + Debug.Assert(bytesLeft == 0); + + vlen = index + 1; + } + + // TODO: Find a way to support passing the timeout somehow without changing the socket ReceiveTimeout. + public LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (receiveSize > 0) + { + UpdateMessages(out vlen, message, receiveSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int sendSize = Socket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (sendSize > 0) + { + UpdateMessages(out vlen, message, sendSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs new file mode 100644 index 00000000..1b305dfb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs @@ -0,0 +1,177 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocketPollManager : IPollManager + { + private static ManagedSocketPollManager _instance; + + public static ManagedSocketPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new ManagedSocketPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is ManagedSocket; + } + + public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount) + { + List<Socket> readEvents = new List<Socket>(); + List<Socket> writeEvents = new List<Socket>(); + List<Socket> errorEvents = new List<Socket>(); + + updatedCount = 0; + + foreach (PollEvent evnt in events) + { + ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor; + + bool isValidEvent = evnt.Data.InputEvents == 0; + + errorEvents.Add(socket.Socket); + + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0) + { + writeEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + return LinuxError.EINVAL; + } + } + + try + { + int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000; + + Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + foreach (PollEvent evnt in events) + { + Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket; + + PollEventTypeMask outputEvents = evnt.Data.OutputEvents & ~evnt.Data.InputEvents; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + outputEvents |= PollEventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Output; + } + + evnt.Data.OutputEvents = outputEvents; + } + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount) + { + List<Socket> readEvents = new(); + List<Socket> writeEvents = new(); + List<Socket> errorEvents = new(); + + updatedCount = 0; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + readEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorEvents.Add(socket.Socket); + } + } + + Socket.Select(readEvents, writeEvents, errorEvents, timeout); + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (readEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Input; + } + + if (writeEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Output; + } + + if (errorEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Error; + } + } + + return LinuxError.SUCCESS; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs new file mode 100644 index 00000000..0f24a57f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs @@ -0,0 +1,134 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum WsaError + { + /* + * All Windows Sockets error constants are biased by WSABASEERR from + * the "normal" + */ + WSABASEERR = 10000, + + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR + 4), + WSAEBADF = (WSABASEERR + 9), + WSAEACCES = (WSABASEERR + 13), + WSAEFAULT = (WSABASEERR + 14), + WSAEINVAL = (WSABASEERR + 22), + WSAEMFILE = (WSABASEERR + 24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR + 35), + WSAEINPROGRESS = (WSABASEERR + 36), + WSAEALREADY = (WSABASEERR + 37), + WSAENOTSOCK = (WSABASEERR + 38), + WSAEDESTADDRREQ = (WSABASEERR + 39), + WSAEMSGSIZE = (WSABASEERR + 40), + WSAEPROTOTYPE = (WSABASEERR + 41), + WSAENOPROTOOPT = (WSABASEERR + 42), + WSAEPROTONOSUPPORT = (WSABASEERR + 43), + WSAESOCKTNOSUPPORT = (WSABASEERR + 44), + WSAEOPNOTSUPP = (WSABASEERR + 45), + WSAEPFNOSUPPORT = (WSABASEERR + 46), + WSAEAFNOSUPPORT = (WSABASEERR + 47), + WSAEADDRINUSE = (WSABASEERR + 48), + WSAEADDRNOTAVAIL = (WSABASEERR + 49), + WSAENETDOWN = (WSABASEERR + 50), + WSAENETUNREACH = (WSABASEERR + 51), + WSAENETRESET = (WSABASEERR + 52), + WSAECONNABORTED = (WSABASEERR + 53), + WSAECONNRESET = (WSABASEERR + 54), + WSAENOBUFS = (WSABASEERR + 55), + WSAEISCONN = (WSABASEERR + 56), + WSAENOTCONN = (WSABASEERR + 57), + WSAESHUTDOWN = (WSABASEERR + 58), + WSAETOOMANYREFS = (WSABASEERR + 59), + WSAETIMEDOUT = (WSABASEERR + 60), + WSAECONNREFUSED = (WSABASEERR + 61), + WSAELOOP = (WSABASEERR + 62), + WSAENAMETOOLONG = (WSABASEERR + 63), + WSAEHOSTDOWN = (WSABASEERR + 64), + WSAEHOSTUNREACH = (WSABASEERR + 65), + WSAENOTEMPTY = (WSABASEERR + 66), + WSAEPROCLIM = (WSABASEERR + 67), + WSAEUSERS = (WSABASEERR + 68), + WSAEDQUOT = (WSABASEERR + 69), + WSAESTALE = (WSABASEERR + 70), + WSAEREMOTE = (WSABASEERR + 71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR + 91), + WSAVERNOTSUPPORTED = (WSABASEERR + 92), + WSANOTINITIALISED = (WSABASEERR + 93), + WSAEDISCON = (WSABASEERR + 101), + WSAENOMORE = (WSABASEERR + 102), + WSAECANCELLED = (WSABASEERR + 103), + WSAEINVALIDPROCTABLE = (WSABASEERR + 104), + WSAEINVALIDPROVIDER = (WSABASEERR + 105), + WSAEPROVIDERFAILEDINIT = (WSABASEERR + 106), + WSASYSCALLFAILURE = (WSABASEERR + 107), + WSASERVICE_NOT_FOUND = (WSABASEERR + 108), + WSATYPE_NOT_FOUND = (WSABASEERR + 109), + WSA_E_NO_MORE = (WSABASEERR + 110), + WSA_E_CANCELLED = (WSABASEERR + 111), + WSAEREFUSED = (WSABASEERR + 112), + + /* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR + 1001), + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR + 1002), + + /* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR + 1003), + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR + 1004), + + /* + * Define QOS related error return codes + * + */ + WSA_QOS_RECEIVERS = (WSABASEERR + 1005), + /* at least one Reserve has arrived */ + WSA_QOS_SENDERS = (WSABASEERR + 1006), + /* at least one Path has arrived */ + WSA_QOS_NO_SENDERS = (WSABASEERR + 1007), + /* there are no senders */ + WSA_QOS_NO_RECEIVERS = (WSABASEERR + 1008), + /* there are no receivers */ + WSA_QOS_REQUEST_CONFIRMED = (WSABASEERR + 1009), + /* Reserve has been confirmed */ + WSA_QOS_ADMISSION_FAILURE = (WSABASEERR + 1010), + /* error due to lack of resources */ + WSA_QOS_POLICY_FAILURE = (WSABASEERR + 1011), + /* rejected for administrative reasons - bad credentials */ + WSA_QOS_BAD_STYLE = (WSABASEERR + 1012), + /* unknown or conflicting style */ + WSA_QOS_BAD_OBJECT = (WSABASEERR + 1013), + /* problem with some part of the filterspec or providerspecific + * buffer in general */ + WSA_QOS_TRAFFIC_CTRL_ERROR = (WSABASEERR + 1014), + /* problem with some part of the flowspec */ + WSA_QOS_GENERIC_ERROR = (WSABASEERR + 1015) + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs new file mode 100644 index 00000000..5668d30b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -0,0 +1,225 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + static class WinSockHelper + { + private static readonly Dictionary<WsaError, LinuxError> _errorMap = new() + { + // 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 static readonly Dictionary<int, LinuxError> _errorMapMacOs = new() + { + { 35, LinuxError.EAGAIN }, + { 11, LinuxError.EDEADLOCK }, + { 91, LinuxError.ENOMSG }, + { 90, LinuxError.EIDRM }, + { 77, LinuxError.ENOLCK }, + { 70, LinuxError.ESTALE }, + { 36, LinuxError.EINPROGRESS }, + { 37, LinuxError.EALREADY }, + { 38, LinuxError.ENOTSOCK }, + { 39, LinuxError.EDESTADDRREQ }, + { 40, LinuxError.EMSGSIZE }, + { 41, LinuxError.EPROTOTYPE }, + { 42, LinuxError.ENOPROTOOPT }, + { 43, LinuxError.EPROTONOSUPPORT }, + { 44, LinuxError.ESOCKTNOSUPPORT }, + { 45, LinuxError.EOPNOTSUPP }, + { 46, LinuxError.EPFNOSUPPORT }, + { 47, LinuxError.EAFNOSUPPORT }, + { 48, LinuxError.EADDRINUSE }, + { 49, LinuxError.EADDRNOTAVAIL }, + { 50, LinuxError.ENETDOWN }, + { 51, LinuxError.ENETUNREACH }, + { 52, LinuxError.ENETRESET }, + { 53, LinuxError.ECONNABORTED }, + { 54, LinuxError.ECONNRESET }, + { 55, LinuxError.ENOBUFS }, + { 56, LinuxError.EISCONN }, + { 57, LinuxError.ENOTCONN }, + { 58, LinuxError.ESHUTDOWN }, + { 60, LinuxError.ETIMEDOUT }, + { 61, LinuxError.ECONNREFUSED }, + { 64, LinuxError.EHOSTDOWN }, + { 65, LinuxError.EHOSTUNREACH }, + { 68, LinuxError.EUSERS }, + { 62, LinuxError.ELOOP }, + { 63, LinuxError.ENAMETOOLONG }, + { 66, LinuxError.ENOTEMPTY }, + { 69, LinuxError.EDQUOT }, + { 71, LinuxError.EREMOTE }, + { 78, LinuxError.ENOSYS }, + { 59, LinuxError.ETOOMANYREFS }, + { 92, LinuxError.EILSEQ }, + { 89, LinuxError.ECANCELED }, + { 84, LinuxError.EOVERFLOW } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _soSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, SocketOptionName.Debug }, + { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, + { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, + { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, + { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, + { BsdSocketOption.SoLinger, SocketOptionName.Linger }, + { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, + { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, + { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, + { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, + { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, + { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, + { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, + { BsdSocketOption.SoError, SocketOptionName.Error }, + { BsdSocketOption.SoType, SocketOptionName.Type } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _ipSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, + { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, + { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, + { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, + { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, + { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, + { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, + { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, + { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, + { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, + { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _tcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, + { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, + { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, + { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount } + }; + + public static LinuxError ConvertError(WsaError errorCode) + { + if (OperatingSystem.IsMacOS()) + { + if (_errorMapMacOs.TryGetValue((int)errorCode, out LinuxError errno)) + { + return errno; + } + } + else + { + if (_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + return errno; + } + } + + return (LinuxError)errorCode; + } + + public static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) + { + var table = level switch + { + SocketOptionLevel.Socket => _soSocketOptionMap, + SocketOptionLevel.IP => _ipSocketOptionMap, + SocketOptionLevel.Tcp => _tcpSocketOptionMap, + _ => null + }; + + if (table == null) + { + name = default; + return false; + } + + return table.TryGetValue(option, out name); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs new file mode 100644 index 00000000..798fc015 --- /dev/null +++ b/src/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/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs new file mode 100644 index 00000000..37461bb2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdAddressFamily : uint + { + Unspecified, + InterNetwork = 2, + InterNetworkV6 = 28, + + Unknown = uint.MaxValue + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs new file mode 100644 index 00000000..1dfa5a5f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdIoctl + { + AtMark = 0x40047307 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs new file mode 100644 index 00000000..f97b8f5b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMMsgHdr + { + public BsdMsgHdr[] Messages { get; } + + private BsdMMsgHdr(BsdMsgHdr[] messages) + { + Messages = messages; + } + + public static LinuxError Serialize(Span<byte> rawData, BsdMMsgHdr message) + { + rawData[0] = 0x8; + rawData = rawData[1..]; + + for (int index = 0; index < message.Messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Serialize(ref rawData, message.Messages[index]); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMMsgHdr message, ReadOnlySpan<byte> rawData, int vlen) + { + message = null; + + BsdMsgHdr[] messages = new BsdMsgHdr[vlen]; + + // Skip "header" byte (Nintendo also ignore it) + rawData = rawData[1..]; + + for (int index = 0; index < messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Deserialize(out messages[index], ref rawData); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + message = new BsdMMsgHdr(messages); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs new file mode 100644 index 00000000..07c97182 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs @@ -0,0 +1,212 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMsgHdr + { + public byte[] Name { get; } + public byte[][] Iov { get; } + public byte[] Control { get; } + public BsdSocketFlags Flags { get; } + public uint Length; + + private BsdMsgHdr(byte[] name, byte[][] iov, byte[] control, BsdSocketFlags flags, uint length) + { + Name = name; + Iov = iov; + Control = control; + Flags = flags; + Length = length; + } + + public static LinuxError Serialize(ref Span<byte> rawData, BsdMsgHdr message) + { + int msgNameLength = message.Name == null ? 0 : message.Name.Length; + int iovCount = message.Iov == null ? 0 : message.Iov.Length; + int controlLength = message.Control == null ? 0 : message.Control.Length; + BsdSocketFlags flags = message.Flags; + + if (!MemoryMarshal.TryWrite(rawData, ref msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + message.Name.CopyTo(rawData); + rawData = rawData[msgNameLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, ref iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + for (int index = 0; index < iovCount; index++) + { + ulong iovLength = (ulong)message.Iov[index].Length; + + if (!MemoryMarshal.TryWrite(rawData, ref iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + message.Iov[index].CopyTo(rawData); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryWrite(rawData, ref controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + message.Control.CopyTo(rawData); + rawData = rawData[controlLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, ref flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryWrite(rawData, ref message.Length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMsgHdr message, ref ReadOnlySpan<byte> rawData) + { + byte[] name = null; + byte[][] iov = null; + byte[] control = null; + + message = null; + + if (!MemoryMarshal.TryRead(rawData, out uint msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + name = rawData[..(int)msgNameLength].ToArray(); + rawData = rawData[(int)msgNameLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out uint iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + iov = new byte[iovCount][]; + + for (int index = 0; index < iov.Length; index++) + { + if (!MemoryMarshal.TryRead(rawData, out ulong iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + iov[index] = rawData[..(int)iovLength].ToArray(); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryRead(rawData, out uint controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + control = rawData[..(int)controlLength].ToArray(); + rawData = rawData[(int)controlLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out BsdSocketFlags flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryRead(rawData, out uint length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + message = new BsdMsgHdr(name, iov, control, flags, length); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs new file mode 100644 index 00000000..67c11e54 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct BsdSockAddr + { + public byte Length; + public byte Family; + public ushort Port; + public Array4<byte> Address; + private Array8<byte> _reserved; + + public IPEndPoint ToIPEndPoint() + { + IPAddress address = new IPAddress(Address.AsSpan()); + int port = (ushort)IPAddress.NetworkToHostOrder((short)Port); + + return new IPEndPoint(address, port); + } + + public static BsdSockAddr FromIPEndPoint(IPEndPoint endpoint) + { + BsdSockAddr result = new BsdSockAddr + { + Length = 0, + Family = (byte)endpoint.AddressFamily, + Port = (ushort)IPAddress.HostToNetworkOrder((short)endpoint.Port) + }; + + endpoint.Address.GetAddressBytes().AsSpan().CopyTo(result.Address.AsSpan()); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs new file mode 100644 index 00000000..be5991ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum BsdSocketCreationFlags + { + None = 0, + CloseOnExecution = 1, + NonBlocking = 2, + + FlagsShift = 28 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs new file mode 100644 index 00000000..4408c89a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketFlags + { + None = 0, + Oob = 0x1, + Peek = 0x2, + DontRoute = 0x4, + Eor = 0x8, + Trunc = 0x10, + CTrunc = 0x20, + WaitAll = 0x40, + DontWait = 0x80, + Eof = 0x100, + Notification = 0x2000, + Nbio = 0x4000, + Compat = 0x8000, + SoCallbck = 0x10000, + NoSignal = 0x20000, + CMsgCloExec = 0x40000 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs new file mode 100644 index 00000000..4d0d1dcf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs @@ -0,0 +1,119 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketOption + { + SoDebug = 0x1, + SoAcceptConn = 0x2, + SoReuseAddr = 0x4, + SoKeepAlive = 0x8, + SoDontRoute = 0x10, + SoBroadcast = 0x20, + SoUseLoopBack = 0x40, + SoLinger = 0x80, + SoOobInline = 0x100, + SoReusePort = 0x200, + SoTimestamp = 0x400, + SoNoSigpipe = 0x800, + SoAcceptFilter = 0x1000, + SoBinTime = 0x2000, + SoNoOffload = 0x4000, + SoNoDdp = 0x8000, + SoReusePortLb = 0x10000, + SoRError = 0x20000, + + SoSndBuf = 0x1001, + SoRcvBuf = 0x1002, + SoSndLoWat = 0x1003, + SoRcvLoWat = 0x1004, + SoSndTimeo = 0x1005, + SoRcvTimeo = 0x1006, + SoError = 0x1007, + SoType = 0x1008, + SoLabel = 0x1009, + SoPeerLabel = 0x1010, + SoListenQLimit = 0x1011, + SoListenQLen = 0x1012, + SoListenIncQLen = 0x1013, + SoSetFib = 0x1014, + SoUserCookie = 0x1015, + SoProtocol = 0x1016, + SoTsClock = 0x1017, + SoMaxPacingRate = 0x1018, + SoDomain = 0x1019, + + IpOptions = 1, + IpHdrIncl = 2, + IpTos = 3, + IpTtl = 4, + IpRecvOpts = 5, + IpRecvRetOpts = 6, + IpRecvDstAddr = 7, + IpRetOpts = 8, + IpMulticastIf = 9, + IpMulticastTtl = 10, + IpMulticastLoop = 11, + IpAddMembership = 12, + IpDropMembership = 13, + IpMulticastVif = 14, + IpRsvpOn = 15, + IpRsvpOff = 16, + IpRsvpVifOn = 17, + IpRsvpVifOff = 18, + IpPortRange = 19, + IpRecvIf = 20, + IpIpsecPolicy = 21, + IpOnesBcast = 23, + IpBindany = 24, + IpBindMulti = 25, + IpRssListenBucket = 26, + IpOrigDstAddr = 27, + + IpFwTableAdd = 40, + IpFwTableDel = 41, + IpFwTableFlush = 42, + IpFwTableGetSize = 43, + IpFwTableList = 44, + + IpFw3 = 48, + IpDummyNet3 = 49, + + IpFwAdd = 50, + IpFwDel = 51, + IpFwFlush = 52, + IpFwZero = 53, + IpFwGet = 54, + IpFwResetLog = 55, + + IpFwNatCfg = 56, + IpFwNatDel = 57, + IpFwNatGetConfig = 58, + IpFwNatGetLog = 59, + + IpDummyNetConfigure = 60, + IpDummyNetDel = 61, + IpDummyNetFlush = 62, + IpDummyNetGet = 64, + + IpRecvTtl = 65, + IpMinTtl = 66, + IpDontFrag = 67, + IpRecvTos = 68, + + IpAddSourceMembership = 70, + IpDropSourceMembership = 71, + IpBlockSource = 72, + IpUnblockSource = 73, + + TcpNoDelay = 1, + TcpMaxSeg = 2, + TcpNoPush = 4, + TcpNoOpt = 8, + TcpMd5Sig = 16, + TcpInfo = 32, + TcpCongestion = 64, + TcpKeepInit = 128, + TcpKeepIdle = 256, + TcpKeepIntvl = 512, + TcpKeepCnt = 1024 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs new file mode 100644 index 00000000..13230ac3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketShutdownFlags + { + Receive, + Send, + ReceiveAndSend + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs new file mode 100644 index 00000000..b54c7886 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketType + { + Stream = 1, + Dgram, + Raw, + Rdm, + Seqpacket, + + TypeMask = 0xFFFFFFF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs new file mode 100644 index 00000000..e01d8226 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum EventFdFlags : uint + { + None = 0, + Semaphore = 1 << 0, + NonBlocking = 1 << 2 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs new file mode 100644 index 00000000..d3663878 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + interface IPollManager + { + bool IsCompatible(PollEvent evnt); + + LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount); + + LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs new file mode 100644 index 00000000..96602830 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs @@ -0,0 +1,155 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum LinuxError + { + SUCCESS = 0, + EPERM = 1 /* Operation not permitted */, + ENOENT = 2 /* No such file or directory */, + ESRCH = 3 /* No such process */, + EINTR = 4 /* Interrupted system call */, + EIO = 5 /* I/O error */, + ENXIO = 6 /* No such device or address */, + E2BIG = 7 /* Argument list too long */, + ENOEXEC = 8 /* Exec format error */, + EBADF = 9 /* Bad file number */, + ECHILD = 10 /* No child processes */, + EAGAIN = 11 /* Try again */, + ENOMEM = 12 /* Out of memory */, + EACCES = 13 /* Permission denied */, + EFAULT = 14 /* Bad address */, + ENOTBLK = 15 /* Block device required */, + EBUSY = 16 /* Device or resource busy */, + EEXIST = 17 /* File exists */, + EXDEV = 18 /* Cross-device link */, + ENODEV = 19 /* No such device */, + ENOTDIR = 20 /* Not a directory */, + EISDIR = 21 /* Is a directory */, + EINVAL = 22 /* Invalid argument */, + ENFILE = 23 /* File table overflow */, + EMFILE = 24 /* Too many open files */, + ENOTTY = 25 /* Not a typewriter */, + ETXTBSY = 26 /* Text file busy */, + EFBIG = 27 /* File too large */, + ENOSPC = 28 /* No space left on device */, + ESPIPE = 29 /* Illegal seek */, + EROFS = 30 /* Read-only file system */, + EMLINK = 31 /* Too many links */, + EPIPE = 32 /* Broken pipe */, + EDOM = 33 /* Math argument out of domain of func */, + ERANGE = 34 /* Math result not representable */, + EDEADLK = 35 /* Resource deadlock would occur */, + ENAMETOOLONG = 36 /* File name too long */, + ENOLCK = 37 /* No record locks available */, + + /* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ + ENOSYS = 38 /* Invalid system call number */, + ENOTEMPTY = 39 /* Directory not empty */, + ELOOP = 40 /* Too many symbolic links encountered */, + EWOULDBLOCK = EAGAIN /* Operation would block */, + ENOMSG = 42 /* No message of desired type */, + EIDRM = 43 /* Identifier removed */, + ECHRNG = 44 /* Channel number out of range */, + EL2NSYNC = 45 /* Level 2 not synchronized */, + EL3HLT = 46 /* Level 3 halted */, + EL3RST = 47 /* Level 3 reset */, + ELNRNG = 48 /* Link number out of range */, + EUNATCH = 49 /* Protocol driver not attached */, + ENOCSI = 50 /* No CSI structure available */, + EL2HLT = 51 /* Level 2 halted */, + EBADE = 52 /* Invalid exchange */, + EBADR = 53 /* Invalid request descriptor */, + EXFULL = 54 /* Exchange full */, + ENOANO = 55 /* No anode */, + EBADRQC = 56 /* Invalid request code */, + EBADSLT = 57 /* Invalid slot */, + EDEADLOCK = EDEADLK, + EBFONT = 59 /* Bad font file format */, + ENOSTR = 60 /* Device not a stream */, + ENODATA = 61 /* No data available */, + ETIME = 62 /* Timer expired */, + ENOSR = 63 /* Out of streams resources */, + ENONET = 64 /* Machine is not on the network */, + ENOPKG = 65 /* Package not installed */, + EREMOTE = 66 /* Object is remote */, + ENOLINK = 67 /* Link has been severed */, + EADV = 68 /* Advertise error */, + ESRMNT = 69 /* Srmount error */, + ECOMM = 70 /* Communication error on send */, + EPROTO = 71 /* Protocol error */, + EMULTIHOP = 72 /* Multihop attempted */, + EDOTDOT = 73 /* RFS specific error */, + EBADMSG = 74 /* Not a data message */, + EOVERFLOW = 75 /* Value too large for defined data type */, + ENOTUNIQ = 76 /* Name not unique on network */, + EBADFD = 77 /* File descriptor in bad state */, + EREMCHG = 78 /* Remote address changed */, + ELIBACC = 79 /* Can not access a needed shared library */, + ELIBBAD = 80 /* Accessing a corrupted shared library */, + ELIBSCN = 81 /* .lib section in a.out corrupted */, + ELIBMAX = 82 /* Attempting to link in too many shared libraries */, + ELIBEXEC = 83 /* Cannot exec a shared library directly */, + EILSEQ = 84 /* Illegal byte sequence */, + ERESTART = 85 /* Interrupted system call should be restarted */, + ESTRPIPE = 86 /* Streams pipe error */, + EUSERS = 87 /* Too many users */, + ENOTSOCK = 88 /* Socket operation on non-socket */, + EDESTADDRREQ = 89 /* Destination address required */, + EMSGSIZE = 90 /* Message too long */, + EPROTOTYPE = 91 /* Protocol wrong type for socket */, + ENOPROTOOPT = 92 /* Protocol not available */, + EPROTONOSUPPORT = 93 /* Protocol not supported */, + ESOCKTNOSUPPORT = 94 /* Socket type not supported */, + EOPNOTSUPP = 95 /* Operation not supported on transport endpoint */, + EPFNOSUPPORT = 96 /* Protocol family not supported */, + EAFNOSUPPORT = 97 /* Address family not supported by protocol */, + EADDRINUSE = 98 /* Address already in use */, + EADDRNOTAVAIL = 99 /* Cannot assign requested address */, + ENETDOWN = 100 /* Network is down */, + ENETUNREACH = 101 /* Network is unreachable */, + ENETRESET = 102 /* Network dropped connection because of reset */, + ECONNABORTED = 103 /* Software caused connection abort */, + ECONNRESET = 104 /* Connection reset by peer */, + ENOBUFS = 105 /* No buffer space available */, + EISCONN = 106 /* Transport endpoint is already connected */, + ENOTCONN = 107 /* Transport endpoint is not connected */, + ESHUTDOWN = 108 /* Cannot send after transport endpoint shutdown */, + ETOOMANYREFS = 109 /* Too many references: cannot splice */, + ETIMEDOUT = 110 /* Connection timed out */, + ECONNREFUSED = 111 /* Connection refused */, + EHOSTDOWN = 112 /* Host is down */, + EHOSTUNREACH = 113 /* No route to host */, + EALREADY = 114 /* Operation already in progress */, + EINPROGRESS = 115 /* Operation now in progress */, + ESTALE = 116 /* Stale file handle */, + EUCLEAN = 117 /* Structure needs cleaning */, + ENOTNAM = 118 /* Not a XENIX named type file */, + ENAVAIL = 119 /* No XENIX semaphores available */, + EISNAM = 120 /* Is a named type file */, + EREMOTEIO = 121 /* Remote I/O error */, + EDQUOT = 122 /* Quota exceeded */, + ENOMEDIUM = 123 /* No medium found */, + EMEDIUMTYPE = 124 /* Wrong medium type */, + ECANCELED = 125 /* Operation Canceled */, + ENOKEY = 126 /* Required key not available */, + EKEYEXPIRED = 127 /* Key has expired */, + EKEYREVOKED = 128 /* Key has been revoked */, + EKEYREJECTED = 129 /* Key was rejected by service */, + + /* for robust mutexes */ + EOWNERDEAD = 130 /* Owner died */, + ENOTRECOVERABLE = 131 /* State not recoverable */, + + ERFKILL = 132 /* Operation not possible due to RF-kill */, + + EHWPOISON = 133 /* Memory page has hardware error */ + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs new file mode 100644 index 00000000..8b77a6c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class PollEvent + { + public PollEventData Data; + public IFileDescriptor FileDescriptor { get; } + + public PollEvent(PollEventData data, IFileDescriptor fileDescriptor) + { + Data = data; + FileDescriptor = fileDescriptor; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs new file mode 100644 index 00000000..546b738e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + struct PollEventData + { +#pragma warning disable CS0649 + public int SocketFd; + public PollEventTypeMask InputEvents; +#pragma warning restore CS0649 + public PollEventTypeMask OutputEvents; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs new file mode 100644 index 00000000..f434fa03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum PollEventTypeMask : ushort + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs new file mode 100644 index 00000000..690a63ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + public struct TimeVal + { + public ulong TvSec; + public ulong TvUsec; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs new file mode 100644 index 00000000..f5877697 --- /dev/null +++ b/src/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/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs new file mode 100644 index 00000000..9832e448 --- /dev/null +++ b/src/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/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs new file mode 100644 index 00000000..0b7adff4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs @@ -0,0 +1,402 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types; +using System; +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 + { + public static readonly NsdSettings NsdSettings; + private readonly FqdnResolver _fqdnResolver; + + private bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + _fqdnResolver = new FqdnResolver(); + + _isInitialized = true; + } + + static IManager() + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + if (!NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode)) + { + // return ResultCode.InvalidSettingsValue; + } + + if (!NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier)) + { + // return ResultCode.InvalidSettingsValue; + } + + NsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode, + Environment = (string)environmentIdentifier + }; + } + + [CommandCmif(5)] // 11.0.0+ + // GetSettingUrl() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetSettingUrl(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(10)] + // GetSettingName() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] + // GetEnvironmentIdentifier() -> buffer<bytes<8> environment_identifier, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier); + + context.Memory.Write(outputPosition, identifierBuffer); + } + + return result; + } + + [CommandCmif(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [CommandCmif(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; + } + + [CommandCmif(14)] + // ImportSettings(u32, buffer<unknown, 5>) -> buffer<unknown, 6> + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(15)] // 4.0.0+ + // SetChangeEnvironmentIdentifierDisabled(bytes<1>) + public ResultCode SetChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + byte disabled = context.RequestData.ReadByte(); + + // TODO: When sys:set service calls will be implemented + /* + if (((nn::settings::detail::GetServiceDiscoveryControlSettings() ^ disabled) & 1) != 0 ) + { + nn::settings::detail::SetServiceDiscoveryControlSettings(disabled & 1); + } + */ + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // Resolve(buffer<unknown<0x100>, 0x15>) -> buffer<unknown<0x100>, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out _, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + return result; + } + + [CommandCmif(21)] + // ResolveEx(buffer<unknown<0x100>, 0x15>) -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [CommandCmif(30)] + // GetNasServiceSetting(buffer<unknown<0x10>, 0x15>) -> buffer<unknown<0x108>, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(31)] + // GetNasServiceSettingEx(buffer<unknown<0x10>, 0x15>) -> (u32, buffer<unknown<0x108>, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(40)] + // GetNasRequestFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(41)] + // GetNasRequestFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(42)] + // GetNasApiFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(43)] + // GetNasApiFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(50)] + // GetCurrentSetting() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(51)] // 9.0.0+ + // WriteTestParameter(buffer<?>) + public ResultCode WriteTestParameter(ServiceCtx context) + { + // TODO: Write test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(52)] // 9.0.0+ + // ReadTestParameter() -> buffer<?> + public ResultCode ReadTestParameter(ServiceCtx context) + { + // TODO: Read test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(60)] + // ReadSaveDataFromFsForTest() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Read the savedata 0x80000000000000B0 (nsdsave:/file) and write it inside the buffer. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // WriteSaveDataToFsForTest(buffer<unknown<0x12bf0>, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Write the buffer inside the savedata 0x80000000000000B0 (nsdsave:/file). + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Delete the savedata 0x80000000000000B0. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(63)] // 4.0.0+ + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + // TODO: When sys:set service calls will be implemented use nn::settings::detail::GetServiceDiscoveryControlSettings() + + bool disabled = false; + + context.ResponseData.Write(disabled); + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 10.0.0+ + // GetApplicationServerEnvironmentType() -> bytes<1> + public ResultCode GetApplicationServerEnvironmentType(ServiceCtx context) + { + // TODO: Mount the savedata 0x80000000000000B0 (nsdsave:/test_parameter) and returns the environment type stored inside if the mount succeed. + // Returns ResultCode.NullOutputObject if failed. + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result != ResultCode.Success) + { + return result; + } + + byte environmentType = identifier.AsSpan(0, 2) switch + { + "lp" => (byte)ApplicationServerEnvironmentType.Lp, + "sd" => (byte)ApplicationServerEnvironmentType.Sd, + "sp" => (byte)ApplicationServerEnvironmentType.Sp, + "dp" => (byte)ApplicationServerEnvironmentType.Dp, + _ => (byte)ApplicationServerEnvironmentType.None + }; + + context.ResponseData.Write(environmentType); + + return ResultCode.Success; + } + + [CommandCmif(101)] // 10.0.0+ + // SetApplicationServerEnvironmentType(bytes<1>) + public ResultCode SetApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(102)] // 10.0.0+ + // DeleteApplicationServerEnvironmentType() + public ResultCode DeleteApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs new file mode 100644 index 00000000..4096e431 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs @@ -0,0 +1,97 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager +{ + class FqdnResolver + { + private const string _dummyAddress = "unknown.dummy.nintendo.net"; + + public ResultCode GetEnvironmentIdentifier(out string identifier) + { + if (IManager.NsdSettings.TestMode) + { + identifier = "err"; + + return ResultCode.InvalidSettingsValue; + } + else + { + identifier = IManager.NsdSettings.Environment; + } + + return ResultCode.Success; + } + + public static ResultCode Resolve(string address, out string resolvedAddress) + { + if (address == "api.sect.srv.nintendo.net" || + address == "ctest.cdn.nintendo.net" || + address == "ctest.cdn.n.nintendoswitch.cn" || + address == "unknown.dummy.nintendo.net") + { + resolvedAddress = address; + } + else + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", IManager.NsdSettings.Environment); + + resolvedAddress = ""; + + if (IManager.NsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!IManager.NsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + resolvedAddress = address switch + { + "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // dp1 environment + "api.accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // dp1 environment + "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // lp1 environment + "accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // lp1 environment + /* + // TODO: Determine fields of the struct. + this + 0xEB8 => this + 0xEB8 + 0x300 + this + 0x2BE8 => this + 0x2BE8 + 0x300 + */ + _ => address, + }; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] addressBuffer = new byte[inputSize]; + + context.Memory.Read(inputPosition, addressBuffer); + + string address = Encoding.UTF8.GetString(addressBuffer).TrimEnd('\0'); + + resultCode = Resolve(address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = _dummyAddress; + } + + if (IManager.NsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs new file mode 100644 index 00000000..993fbe8a --- /dev/null +++ b/src/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, + + InvalidSettingsValue = ( 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/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs new file mode 100644 index 00000000..150bdab4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types +{ + enum ApplicationServerEnvironmentType : byte + { + None, + Lp, + Sd, + Sp, + Dp + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs new file mode 100644 index 00000000..0a72fa87 --- /dev/null +++ b/src/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; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs new file mode 100644 index 00000000..64c3acbb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -0,0 +1,686 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types; +using Ryujinx.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + [Service("sfdnsres")] + class IResolver : IpcService + { + public IResolver(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + } + + [CommandCmif(0)] + // SetDnsAddressesPrivateRequest(u32, buffer<unknown, 5, 0>) + public ResultCode SetDnsAddressesPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(1)] + // GetDnsAddressPrivateRequest(u32) -> buffer<unknown, 6, 0> + public ResultCode GetDnsAddressPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(2)] + // GetHostByNameRequest(u8, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByNameRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByNameRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(3)] + // GetHostByAddrRequest(u32, u32, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByAddrRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByAddrRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(4)] + // GetHostStringErrorRequest(u32) -> buffer<unknown, 6, 0> + public ResultCode GetHostStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + + string errorString = errorCode switch + { + NetDbError.Success => "Resolver Error 0 (no error)", + NetDbError.HostNotFound => "Unknown host", + NetDbError.TryAgain => "Host name lookup failure", + NetDbError.NoRecovery => "Unknown server error", + NetDbError.NoData => "No address associated with name", + _ => (errorCode <= NetDbError.Internal) ? "Resolver internal error" : "Unknown resolver error" + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(5)] + // GetGaiStringErrorRequest(u32) -> buffer<byte, 6, 0> + public ResultCode GetGaiStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + string errorString = errorCode switch + { + GaiError.AddressFamily => "Address family for hostname not supported", + GaiError.Again => "Temporary failure in name resolution", + GaiError.BadFlags => "Invalid value for ai_flags", + GaiError.Fail => "Non-recoverable failure in name resolution", + GaiError.Family => "ai_family not supported", + GaiError.Memory => "Memory allocation failure", + GaiError.NoData => "No address associated with hostname", + GaiError.NoName => "hostname nor servname provided, or not known", + GaiError.Service => "servname not supported for ai_socktype", + GaiError.SocketType => "ai_socktype not supported", + GaiError.System => "System error returned in errno", + GaiError.BadHints => "Invalid value for hints", + GaiError.Protocol => "Resolved protocol is unknown", + GaiError.Overflow => "Argument buffer overflow", + GaiError.Max => "Unknown error", + _ => "Success" + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(6)] + // GetAddrInfoRequest(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints) -> (i32 ret, u32 bsd_errno, u32 packed_addrinfo_size, buffer<packed_addrinfo, 6, 0> response) + public ResultCode GetAddrInfoRequest(ServiceCtx context) + { + ulong responseBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong responseBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetAddrInfoRequestImpl(context, responseBufferPosition, responseBufferSize, false, 0, 0); + } + + [CommandCmif(8)] + // GetCancelHandleRequest(u64, pid) -> u32 + public ResultCode GetCancelHandleRequest(ServiceCtx context) + { + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + uint cancelHandleRequest = 0; + + context.ResponseData.Write(cancelHandleRequest); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // CancelRequest(u32, u64, pid) + public ResultCode CancelRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 5.0.0+ + // GetHostByNameRequestWithOptions(u8, u32, u64, pid, buffer<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>) + public ResultCode GetHostByNameRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByNameRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(11)] // 5.0.0+ + // GetHostByAddrRequestWithOptions(u32, u32, u32, u64, pid, buffer<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>) + public ResultCode GetHostByAddrRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByAddrRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(12)] // 5.0.0+ + // GetAddrInfoRequestWithOptions(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints, buffer<unknown, 21, 0>) -> (i32 ret, u32 bsd_errno, u32 unknown, u32 packed_addrinfo_size, buffer<packed_addrinfo, 22, 0> response) + public ResultCode GetAddrInfoRequestWithOptions(ServiceCtx context) + { + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetAddrInfoRequestImpl(context, outputBufferPosition, outputBufferSize, true, optionsBufferPosition, optionsBufferSize); + } + + [CommandCmif(14)] // 5.0.0+ + // ResolverSetOptionRequest(buffer<unknown, 5, 0>, u64 unknown, u64 pid_placeholder, pid) -> (i32 ret, u32 bsd_errno) + public ResultCode ResolverSetOptionRequest(ServiceCtx context) + { + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + ulong unknown = context.RequestData.ReadUInt64(); + + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + // TODO: Parse and use options. + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown }); + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Success; + + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + + return ResultCode.Success; + } + + // Atmosphère extension for dns_mitm + [CommandCmif(65000)] + // AtmosphereReloadHostsFile() + public ResultCode AtmosphereReloadHostsFile(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + + return ResultCode.Success; + } + + private static ResultCode GetHostByNameRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + string host = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // TODO: Use params. + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Overflow; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.HostNotFound; + } + + if (hostEntry != null) + { + IEnumerable<IPAddress> addresses = GetIpv4Addresses(hostEntry); + + if (!addresses.Any()) + { + errno = GaiError.NoData; + netDbErrorCode = NetDbError.NoAddress; + } + else + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, addresses); + } + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static ResultCode GetHostByAddrRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked."); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + byte[] rawIp = new byte[inputBufferSize]; + + context.Memory.Read(inputBufferPosition, rawIp); + + // TODO: Use params. + uint socketLength = context.RequestData.ReadUInt32(); + uint type = context.RequestData.ReadUInt32(); + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (rawIp.Length == 4) + { + try + { + IPAddress address = new IPAddress(rawIp); + + hostEntry = Dns.GetHostEntry(address); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, GetIpv4Addresses(hostEntry)); + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static int SerializeHostEntries(ServiceCtx context, ulong outputBufferPosition, ulong outputBufferSize, IPHostEntry hostEntry, IEnumerable<IPAddress> addresses = null) + { + ulong originalBufferPosition = outputBufferPosition; + ulong bufferPosition = originalBufferPosition; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += (ulong)hostName.Length; + + // h_aliases list size + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(hostEntry.Aliases.Length)); + bufferPosition += sizeof(int); + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += (ulong)(alias.Length + 1); + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)AddressFamily.InterNetwork)); + bufferPosition += sizeof(short); + + // h_length but it's a short + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)4)); + bufferPosition += sizeof(short); + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.Write(bufferPosition, addresses != null ? BinaryPrimitives.ReverseEndianness(addresses.Count()) : 0); + bufferPosition += sizeof(int); + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += sizeof(int); + } + } + + return (int)(bufferPosition - originalBufferPosition); + } + + private static ResultCode GetAddrInfoRequestImpl( + ServiceCtx context, + ulong responseBufferPosition, + ulong responseBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; + uint cancelHandle = context.RequestData.ReadUInt32(); + + string host = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[0].Position, (long)context.Request.SendBuff[0].Size); + string service = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[1].Position, (long)context.Request.SendBuff[1].Size); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // NOTE: We ignore hints for now. + List<AddrInfoSerialized> hints = DeserializeAddrInfos(context.Memory, context.Request.SendBuff[2].Position, context.Request.SendBuff[2].Size); + + if (withOptions) + { + // TODO: Find unknown, Parse and use options. + uint unknown = context.RequestData.ReadUInt32(); + } + + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + int.TryParse(service, out int port); + + errno = GaiError.Success; + serializedSize = SerializeAddrInfos(context, responseBufferPosition, responseBufferSize, hostEntry, port); + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static List<AddrInfoSerialized> DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size) + { + List<AddrInfoSerialized> result = new(); + + ReadOnlySpan<byte> data = memory.GetSpan(address, (int)size); + + while (!data.IsEmpty) + { + AddrInfoSerialized info = AddrInfoSerialized.Read(data, out data); + + if (info == null) + { + break; + } + + result.Add(info); + } + + return result; + } + + private static int SerializeAddrInfos(ServiceCtx context, ulong responseBufferPosition, ulong responseBufferSize, IPHostEntry hostEntry, int port) + { + ulong originalBufferPosition = responseBufferPosition; + ulong bufferPosition = originalBufferPosition; + + byte[] hostName = Encoding.ASCII.GetBytes(hostEntry.HostName + '\0'); + + using (WritableRegion region = context.Memory.GetWritableRegion(responseBufferPosition, (int)responseBufferSize)) + { + Span<byte> data = region.Memory.Span; + + for (int i = 0; i < hostEntry.AddressList.Length; i++) + { + IPAddress ip = hostEntry.AddressList[i]; + + if (ip.AddressFamily != AddressFamily.InterNetwork) + { + continue; + } + + // NOTE: 0 = Any + AddrInfoSerializedHeader header = new(ip, 0); + AddrInfo4 addr = new(ip, (short)port); + AddrInfoSerialized info = new(header, addr, null, hostEntry.HostName); + + data = info.Write(data); + } + + uint sentinel = 0; + MemoryMarshal.Write(data, ref sentinel); + data = data[sizeof(uint)..]; + + return region.Memory.Span.Length - data.Length; + } + } + + private static void WriteResponse( + ServiceCtx context, + bool withOptions, + int serializedSize, + GaiError errno, + NetDbError netDbErrorCode) + { + if (withOptions) + { + context.ResponseData.Write(serializedSize); + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write(0); + } + else + { + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + } + } + + private static IEnumerable<IPAddress> GetIpv4Addresses(IPHostEntry hostEntry) + { + return hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork); + } + + private static NetDbError ConvertSocketErrorCodeToNetDbError(int errorCode) + { + return errorCode switch + { + 11001 => NetDbError.HostNotFound, + 11002 => NetDbError.TryAgain, + 11003 => NetDbError.NoRecovery, + 11004 => NetDbError.NoData, + _ => NetDbError.Internal + }; + } + + private static GaiError ConvertSocketErrorCodeToGaiError(int errorCode, GaiError errno) + { + return errorCode switch + { + 11001 => GaiError.NoData, + 10060 => GaiError.Again, + _ => errno + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs new file mode 100644 index 00000000..776a6f7c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs @@ -0,0 +1,44 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + static partial class DnsBlacklist + { + const RegexOptions RegexOpts = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + [GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost1(); + [GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost2(); + [GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost3(); + [GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost4(); + [GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost5(); + [GeneratedRegex(@"^accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost6(); + + private static readonly Regex[] BlockedHosts = { + BlockedHost1(), + BlockedHost2(), + BlockedHost3(), + BlockedHost4(), + BlockedHost5(), + BlockedHost6() + }; + + public static bool IsHostBlocked(string host) + { + foreach (Regex regex in BlockedHosts) + { + if (regex.IsMatch(host)) + { + return true; + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs new file mode 100644 index 00000000..8eece5ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + class DnsMitmResolver + { + private const string HostsFilePath = "/atmosphere/hosts/default.txt"; + + private static DnsMitmResolver _instance; + public static DnsMitmResolver Instance => _instance ??= new DnsMitmResolver(); + + private readonly Dictionary<string, IPAddress> _mitmHostEntries = new(); + + public void ReloadEntries(ServiceCtx context) + { + string sdPath = context.Device.Configuration.VirtualFileSystem.GetSdCardPath(); + string filePath = context.Device.Configuration.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath); + + _mitmHostEntries.Clear(); + + if (File.Exists(filePath)) + { + using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using StreamReader reader = new(fileStream); + + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + + if (line == null) + { + break; + } + + // Ignore comments and empty lines + if (line.StartsWith("#") || line.Trim().Length == 0) + { + continue; + } + + string[] entry = line.Split(new[] { ' ', '\t' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + // Hosts file example entry: + // 127.0.0.1 localhost loopback + + // 0. Check the size of the array + if (entry.Length < 2) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid entry in hosts file: {line}"); + + continue; + } + + // 1. Parse the address + if (!IPAddress.TryParse(entry[0], out IPAddress address)) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Failed to parse IP address in hosts file: {entry[0]}"); + + continue; + } + + // 2. Check for AMS hosts file extension: "%" + for (int i = 1; i < entry.Length; i++) + { + entry[i] = entry[i].Replace("%", IManager.NsdSettings.Environment); + } + + // 3. Add hostname to entry dictionary (updating duplicate entries) + foreach (string hostname in entry[1..]) + { + _mitmHostEntries[hostname] = address; + } + } + } + } + + public IPHostEntry ResolveAddress(string host) + { + foreach (var hostEntry in _mitmHostEntries) + { + // Check for AMS hosts file extension: "*" + // NOTE: MatchesSimpleExpression also allows "?" as a wildcard + if (FileSystemName.MatchesSimpleExpression(hostEntry.Key, host)) + { + Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Redirecting '{host}' to: {hostEntry.Value}"); + + return new IPHostEntry + { + AddressList = new[] { hostEntry.Value }, + HostName = hostEntry.Key, + Aliases = Array.Empty<string>() + }; + } + } + + // No match has been found, resolve the host using regular dns + return Dns.GetHostEntry(host); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs new file mode 100644 index 00000000..0a20e057 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct AddrInfo4 + { + public byte Length; + public byte Family; + public short Port; + public Array4<byte> Address; + public Array8<byte> Padding; + + public AddrInfo4(IPAddress address, short port) + { + Length = (byte)Unsafe.SizeOf<Array4<byte>>(); + Family = (byte)AddressFamily.InterNetwork; + Port = IPAddress.HostToNetworkOrder(port); + Address = new Array4<byte>(); + + address.TryWriteBytes(Address.AsSpan(), out _); + } + + public void ToNetworkOrder() + { + Port = IPAddress.HostToNetworkOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public void ToHostOrder() + { + Port = IPAddress.NetworkToHostOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public static void RawIpv4AddressNetworkEndianSwap(ref Array4<byte> address) + { + if (BitConverter.IsLittleEndian) + { + address.AsSpan().Reverse(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs new file mode 100644 index 00000000..a0613d7b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Utilities; +using System; +using System.Diagnostics; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + class AddrInfoSerialized + { + public AddrInfoSerializedHeader Header; + public AddrInfo4? SocketAddress; + public Array4<byte>? RawIPv4Address; + public string CanonicalName; + + public AddrInfoSerialized(AddrInfoSerializedHeader header, AddrInfo4? address, Array4<byte>? rawIPv4Address, string canonicalName) + { + Header = header; + SocketAddress = address; + RawIPv4Address = rawIPv4Address; + CanonicalName = canonicalName; + } + + public static AddrInfoSerialized Read(ReadOnlySpan<byte> buffer, out ReadOnlySpan<byte> rest) + { + if (!MemoryMarshal.TryRead(buffer, out AddrInfoSerializedHeader header)) + { + rest = buffer; + + return null; + } + + AddrInfo4? socketAddress = null; + Array4<byte>? rawIPv4Address = null; + string canonicalName; + + buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..]; + + header.ToHostOrder(); + + if (header.Magic != SfdnsresContants.AddrInfoMagic) + { + rest = buffer; + + return null; + } + + Debug.Assert(header.Magic == SfdnsresContants.AddrInfoMagic); + + if (header.AddressLength == 0) + { + rest = buffer; + + return null; + } + + if (header.Family == (int)AddressFamily.InterNetwork) + { + socketAddress = MemoryMarshal.Read<AddrInfo4>(buffer); + socketAddress.Value.ToHostOrder(); + + buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..]; + } + // AF_INET6 + else if (header.Family == 28) + { + throw new NotImplementedException(); + } + else + { + // Nintendo hardcode 4 bytes in that case here. + Array4<byte> address = MemoryMarshal.Read<Array4<byte>>(buffer); + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref address); + + rawIPv4Address = address; + + buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..]; + } + + canonicalName = StringUtils.ReadUtf8String(buffer, out int dataRead); + buffer = buffer[dataRead..]; + + rest = buffer; + + return new AddrInfoSerialized(header, socketAddress, rawIPv4Address, canonicalName); + } + + public Span<byte> Write(Span<byte> buffer) + { + int familly = Header.Family; + + Header.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, ref Header); + + buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..]; + + if (familly == (int)AddressFamily.InterNetwork) + { + AddrInfo4 socketAddress = SocketAddress.Value; + socketAddress.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, ref socketAddress); + + buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..]; + } + // AF_INET6 + else if (familly == 28) + { + throw new NotImplementedException(); + } + else + { + Array4<byte> rawIPv4Address = RawIPv4Address.Value; + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address); + + MemoryMarshal.Write(buffer, ref rawIPv4Address); + + buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..]; + } + + if (CanonicalName == null) + { + buffer[0] = 0; + + buffer = buffer[1..]; + } + else + { + byte[] canonicalName = Encoding.ASCII.GetBytes(CanonicalName + '\0'); + + canonicalName.CopyTo(buffer); + + buffer = buffer[canonicalName.Length..]; + } + + return buffer; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs new file mode 100644 index 00000000..8e304dfa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs @@ -0,0 +1,57 @@ +using Ryujinx.Common.Memory; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6 * sizeof(int))] + struct AddrInfoSerializedHeader + { + public uint Magic; + public int Flags; + public int Family; + public int SocketType; + public int Protocol; + public uint AddressLength; + + public AddrInfoSerializedHeader(IPAddress address, SocketType socketType) + { + Magic = SfdnsresContants.AddrInfoMagic; + Flags = 0; + Family = (int)address.AddressFamily; + SocketType = (int)socketType; + Protocol = 0; + + if (address.AddressFamily == AddressFamily.InterNetwork) + { + AddressLength = (uint)Unsafe.SizeOf<AddrInfo4>(); + } + else + { + AddressLength = (uint)Unsafe.SizeOf<Array4<byte>>(); + } + } + + public void ToNetworkOrder() + { + Magic = (uint)IPAddress.HostToNetworkOrder((int)Magic); + Flags = IPAddress.HostToNetworkOrder(Flags); + Family = IPAddress.HostToNetworkOrder(Family); + SocketType = IPAddress.HostToNetworkOrder(SocketType); + Protocol = IPAddress.HostToNetworkOrder(Protocol); + AddressLength = (uint)IPAddress.HostToNetworkOrder((int)AddressLength); + } + + public void ToHostOrder() + { + Magic = (uint)IPAddress.NetworkToHostOrder((int)Magic); + Flags = IPAddress.NetworkToHostOrder(Flags); + Family = IPAddress.NetworkToHostOrder(Family); + SocketType = IPAddress.NetworkToHostOrder(SocketType); + Protocol = IPAddress.NetworkToHostOrder(Protocol); + AddressLength = (uint)IPAddress.NetworkToHostOrder((int)AddressLength); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs new file mode 100644 index 00000000..f9f28b44 --- /dev/null +++ b/src/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/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs new file mode 100644 index 00000000..3c04c049 --- /dev/null +++ b/src/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 diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs new file mode 100644 index 00000000..d194a3c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + static class SfdnsresContants + { + public const uint AddrInfoMagic = 0xBEEFCAFE; + } +}
\ No newline at end of file |
