Move solution and projects to src

This commit is contained in:
TSR Berry
2023-04-08 01:22:00 +02:00
committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View File

@@ -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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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)
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
[Service("bsdcfg")]
class ServerInterface : IpcService
{
public ServerInterface(ServiceCtx context) { }
}
}

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
enum BsdAddressFamily : uint
{
Unspecified,
InterNetwork = 2,
InterNetworkV6 = 28,
Unknown = uint.MaxValue
}
}

View File

@@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
enum BsdIoctl
{
AtMark = 0x40047307
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
enum BsdSocketShutdownFlags
{
Receive,
Send,
ReceiveAndSend
}
}

View File

@@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
enum BsdSocketType
{
Stream = 1,
Dgram,
Raw,
Rdm,
Seqpacket,
TypeMask = 0xFFFFFFF,
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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 */
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
public struct TimeVal
{
public ulong TvSec;
public ulong TvUsec;
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc
{
[Service("ethc:c")]
class IEthInterface : IpcService
{
public IEthInterface(ServiceCtx context) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc
{
[Service("ethc:i")]
class IEthInterfaceGroup : IpcService
{
public IEthInterfaceGroup(ServiceCtx context) { }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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,
}
}

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types
{
enum ApplicationServerEnvironmentType : byte
{
None,
Lp,
Sd,
Sp,
Dp
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd
{
class NsdSettings
{
public bool Initialized;
public bool TestMode;
public string Environment;
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
{
enum NetDbError
{
Internal = -1,
Success,
HostNotFound,
TryAgain,
NoRecovery,
NoData,
NoAddress = NoData
}
}

View File

@@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
{
static class SfdnsresContants
{
public const uint AddrInfoMagic = 0xBEEFCAFE;
}
}