diff --git a/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs b/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs index b5944412..da1e51e1 100644 --- a/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs +++ b/Ryujinx.Core/OsHle/Services/Bsd/ServiceBsd.cs @@ -1,14 +1,64 @@ +using ChocolArm64.Memory; using Ryujinx.Core.OsHle.Ipc; +using Ryujinx.Core.OsHle.Utilities; +using System; using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; namespace Ryujinx.Core.OsHle.IpcServices.Bsd { + + //bsd_errno == (SocketException.ErrorCode - 10000) + //https://github.com/freebsd/freebsd/blob/master/sys/sys/errno.h + public enum BsdError + { + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + EOPNOTSUPP = 45, /* Operation not supported */ + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61 /* Connection refused */ + } + + class SocketBsd + { + public int Family; + public int Type; + public int Protocol; + public IPAddress IpAddress; + public IPEndPoint RemoteEP; + public Socket Handle; + } + class ServiceBsd : IIpcService { private Dictionary m_Commands; public IReadOnlyDictionary Commands => m_Commands; + private List Sockets = new List(); + public ServiceBsd() { m_Commands = new Dictionary() @@ -16,26 +66,31 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd { 0, Initialize }, { 1, StartMonitoring }, { 2, Socket }, + { 6, Poll }, + { 8, Recv }, { 10, Send }, - { 14, Connect } + { 11, SendTo }, + { 12, Accept }, + { 13, Bind }, + { 14, Connect }, + { 18, Listen }, + { 21, SetSockOpt }, + { 26, Close } }; } - //Initialize(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno + //(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno public long Initialize(ServiceCtx Context) { /* typedef struct { u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. - u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. - u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). - u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). } BsdBufferConfig; */ @@ -52,7 +107,7 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd return 0; } - //StartMonitoring(u64, pid) + //(u64, pid) public long StartMonitoring(ServiceCtx Context) { //Todo: Stub @@ -60,37 +115,378 @@ namespace Ryujinx.Core.OsHle.IpcServices.Bsd return 0; } - //Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) public long Socket(ServiceCtx Context) { - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); + SocketBsd NewBSDSocket = new SocketBsd + { + Family = Context.RequestData.ReadInt32(), + Type = Context.RequestData.ReadInt32(), + Protocol = Context.RequestData.ReadInt32() + }; - //Todo: Stub + Sockets.Add(NewBSDSocket); + + Sockets[Sockets.Count - 1].Handle = new Socket((AddressFamily)Sockets[Sockets.Count - 1].Family, + (SocketType)Sockets[Sockets.Count - 1].Type, + (ProtocolType)Sockets[Sockets.Count - 1].Protocol); + + Context.ResponseData.Write(Sockets.Count - 1); + Context.ResponseData.Write(0); return 0; } - //Connect(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) - public long Connect(ServiceCtx Context) + //(u32, u32, buffer) -> (i32 ret, u32 bsd_errno, buffer) + public long Poll(ServiceCtx Context) { - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); + int PollCount = Context.RequestData.ReadInt32(); + int TimeOut = Context.RequestData.ReadInt32(); - //Todo: Stub + //https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h + //https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx + //https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343 + //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634 + //https://linux.die.net/man/2/poll + + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + (int)Context.Request.SendBuff[0].Size); + int SocketId = Get32(SentBuffer, 0); + short RequestedEvents = (short)Get16(SentBuffer, 4); + short ReturnedEvents = (short)Get16(SentBuffer, 6); + + //Todo: Stub - Need to implemented the Type-22 buffer. + + Context.ResponseData.Write(1); + Context.ResponseData.Write(0); return 0; } - //Send(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) + //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer message) + public long Recv(ServiceCtx Context) + { + try + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size]; + int ReadedBytes = Sockets[SocketId].Handle.Receive(ReceivedBuffer); + + //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer)); + + AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position, ReceivedBuffer); + + Context.ResponseData.Write(ReadedBytes); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) public long Send(ServiceCtx Context) { - Context.ResponseData.Write(0); - Context.ResponseData.Write(0); + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + (int)Context.Request.SendBuff[0].Size); - //Todo: Stub + try + { + //Logging.Debug("Sended Buffer:" + Environment.NewLine + Logging.HexDump(SendedBuffer)); + + int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + + Context.ResponseData.Write(BytesSent); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } return 0; } + + //(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) + public long SendTo(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketFlags = Context.RequestData.ReadInt32(); + byte[] SentBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + (int)Context.Request.SendBuff[0].Size); + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[1].Position, + (int)Context.Request.SendBuff[1].Size); + + if (!Sockets[SocketId].Handle.Connected) + { + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + } + + try + { + //Logging.Debug("Sended Buffer:" + Environment.NewLine + Logging.HexDump(SendedBuffer)); + + int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer); + + Context.ResponseData.Write(BytesSent); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public long Accept(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position; + + Socket HandleAccept = null; + + var TimeOut = Task.Factory.StartNew(() => + { + try + { + HandleAccept = Sockets[SocketId].Handle.Accept(); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + }); + + TimeOut.Wait(10000); + + if (HandleAccept != null) + { + SocketBsd NewBSDSocket = new SocketBsd + { + IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address, + RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint), + Handle = HandleAccept + }; + + Sockets.Add(NewBSDSocket); + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write((byte)0); + Writer.Write((byte)Sockets[Sockets.Count - 1].Handle.AddressFamily); + Writer.Write((Int16)((IPEndPoint)Sockets[Sockets.Count - 1].Handle.LocalEndPoint).Port); + + string[] IpAdress = Sockets[Sockets.Count - 1].IpAddress.ToString().Split('.'); + Writer.Write(byte.Parse(IpAdress[0])); + Writer.Write(byte.Parse(IpAdress[1])); + Writer.Write(byte.Parse(IpAdress[2])); + Writer.Write(byte.Parse(IpAdress[3])); + + AMemoryHelper.WriteBytes(Context.Memory, AddrBufferPtr, MS.ToArray()); + + Context.ResponseData.Write(Sockets.Count - 1); + Context.ResponseData.Write(0); + Context.ResponseData.Write(MS.Length); + } + } + else + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write((int)BsdError.ETIMEDOUT); + } + + return 0; + } + + //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + public long Bind(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + (int)Context.Request.SendBuff[0].Size); + + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + public long Connect(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + byte[] AddressBuffer = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.SendBuff[0].Position, + (int)Context.Request.SendBuff[0].Size); + + try + { + ParseAddrBuffer(SocketId, AddressBuffer); + + Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public long Listen(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int BackLog = Context.RequestData.ReadInt32(); + + try + { + Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP); + Sockets[SocketId].Handle.Listen(BackLog); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket, u32 level, u32 option_name, buffer) -> (i32 ret, u32 bsd_errno) + public long SetSockOpt(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + int SocketLevel = Context.RequestData.ReadInt32(); + int SocketOptionName = Context.RequestData.ReadInt32(); + + byte[] SocketOptionValue = AMemoryHelper.ReadBytes(Context.Memory, + Context.Request.PtrBuff[0].Position, + Context.Request.PtrBuff[0].Size); + + try + { + Sockets[SocketId].Handle.SetSocketOption((SocketOptionLevel)SocketLevel, + (SocketOptionName)SocketOptionName, + Get32(SocketOptionValue, 0)); + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + //(u32 socket) -> (i32 ret, u32 bsd_errno) + public long Close(ServiceCtx Context) + { + int SocketId = Context.RequestData.ReadInt32(); + + try + { + Sockets[SocketId].Handle.Close(); + Sockets[SocketId] = null; + + Context.ResponseData.Write(0); + Context.ResponseData.Write(0); + } + catch (SocketException Ex) + { + Context.ResponseData.Write(-1); + Context.ResponseData.Write(Ex.ErrorCode - 10000); + } + + return 0; + } + + public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer) + { + using (MemoryStream MS = new MemoryStream(AddrBuffer)) + { + BinaryReader Reader = new BinaryReader(MS); + + int Size = Reader.ReadByte(); + int Family = Reader.ReadByte(); + int Port = EndianSwap.Swap16(Reader.ReadInt16()); + string IpAddress = Reader.ReadByte().ToString() + + "." + Reader.ReadByte().ToString() + + "." + Reader.ReadByte().ToString() + + "." + Reader.ReadByte().ToString(); + + Logging.Debug($"Try to connect to {IpAddress}:{Port}"); + + Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress); + Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port); + } + } + + private int Get16(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8; + } + + private int Get32(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8 | + Data[Address + 2] << 16 | + Data[Address + 3] << 24; + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/ServiceFactory.cs b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs index 31c8aa2c..2b855d25 100644 --- a/Ryujinx.Core/OsHle/Services/ServiceFactory.cs +++ b/Ryujinx.Core/OsHle/Services/ServiceFactory.cs @@ -35,6 +35,7 @@ namespace Ryujinx.Core.OsHle.IpcServices case "appletOE": return new ServiceAppletOE(); case "audout:u": return new ServiceAudOut(); case "audren:u": return new ServiceAudRen(); + case "bsd:s": return new ServiceBsd(); case "bsd:u": return new ServiceBsd(); case "friend:a": return new ServiceFriend(); case "fsp-srv": return new ServiceFspSrv(); diff --git a/Ryujinx.Core/OsHle/Utilities/EndianSwap.cs b/Ryujinx.Core/OsHle/Utilities/EndianSwap.cs new file mode 100644 index 00000000..6758f1f2 --- /dev/null +++ b/Ryujinx.Core/OsHle/Utilities/EndianSwap.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Core.OsHle.Utilities +{ + static class EndianSwap + { + public static short Swap16(short Value) => (short)(((Value >> 8) & 0xff) | (Value << 8)); + } +}