Compare commits
	
		
			1 Commits
		
	
	
		
			stop-it-ge
			...
			shoo-soc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e5495cbc4b | 
| @@ -53,8 +53,7 @@ add_library(citra_common STATIC | ||||
|     archives.h | ||||
|     assert.h | ||||
|     atomic_ops.h | ||||
|     detached_tasks.cpp | ||||
|     detached_tasks.h | ||||
|     bit_cast.h | ||||
|     bit_field.h | ||||
|     bit_set.h | ||||
|     bounded_threadsafe_queue.h | ||||
| @@ -66,6 +65,8 @@ add_library(citra_common STATIC | ||||
|     common_precompiled_headers.h | ||||
|     common_types.h | ||||
|     construct.h | ||||
|     detached_tasks.cpp | ||||
|     detached_tasks.h | ||||
|     dynamic_library/dynamic_library.cpp | ||||
|     dynamic_library/dynamic_library.h | ||||
|     dynamic_library/fdk-aac.cpp | ||||
| @@ -111,6 +112,7 @@ add_library(citra_common STATIC | ||||
|     settings.cpp | ||||
|     settings.h | ||||
|     slot_vector.h | ||||
|     socket_types.h | ||||
|     serialization/atomic.h | ||||
|     serialization/boost_discrete_interval.hpp | ||||
|     serialization/boost_flat_set.h | ||||
|   | ||||
| @@ -13,26 +13,20 @@ | ||||
| // lambda and force the compiler to not inline it. | ||||
|  | ||||
| #define ASSERT(_a_)                                                                                \ | ||||
|     do                                                                                             \ | ||||
| ([&]() CITRA_NO_INLINE {                                                                        \ | ||||
|         if (!(_a_)) [[unlikely]] {                                                                 \ | ||||
|             []() CITRA_NO_INLINE CITRA_NO_RETURN {                                                 \ | ||||
|                 LOG_CRITICAL(Debug, "Assertion Failed!");                                          \ | ||||
|                 Crash();                                                                           \ | ||||
|                 exit(1);                                                                           \ | ||||
|             }();                                                                                   \ | ||||
|         }                                                                                          \ | ||||
|     while (0) | ||||
|             LOG_CRITICAL(Debug, "Assertion Failed!");                                              \ | ||||
|             Crash();                                                                    \ | ||||
|     }                                                                                          \ | ||||
| }()) | ||||
|  | ||||
| #define ASSERT_MSG(_a_, ...)                                                                       \ | ||||
|     do                                                                                             \ | ||||
|         if (!(_a_)) [[unlikely]] {                                                                 \ | ||||
|             [&]() CITRA_NO_INLINE CITRA_NO_RETURN {                                                \ | ||||
|                 LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__);                            \ | ||||
|                 Crash();                                                                           \ | ||||
|                 exit(1);                                                                           \ | ||||
|             }();                                                                                   \ | ||||
|     ([&]() CITRA_NO_INLINE {                                                                        \ | ||||
|             if (!(_a_)) [[unlikely]] {                                                                 \ | ||||
|                 LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__);                                \ | ||||
|                 Crash();                                                                    \ | ||||
|         }                                                                                          \ | ||||
|     while (0) | ||||
|     }()) | ||||
|  | ||||
| #define UNREACHABLE()                                                                              \ | ||||
|     ([]() CITRA_NO_INLINE CITRA_NO_RETURN {                                                        \ | ||||
| @@ -58,3 +52,6 @@ | ||||
|  | ||||
| #define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") | ||||
| #define UNIMPLEMENTED_MSG(_a_, ...) LOG_CRITICAL(Debug, _a_, __VA_ARGS__) | ||||
|  | ||||
| #define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") | ||||
| #define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/common/bit_cast.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/common/bit_cast.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <version> | ||||
|  | ||||
| #ifdef __cpp_lib_bit_cast | ||||
| #include <bit> | ||||
| #endif | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| template <typename To, typename From> | ||||
| constexpr inline To BitCast(const From& from) { | ||||
| #ifdef __cpp_lib_bit_cast | ||||
|     return std::bit_cast<To>(from); | ||||
| #else | ||||
|     return __builtin_bit_cast(To, from); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| } // namespace Common | ||||
| @@ -110,6 +110,15 @@ __declspec(dllimport) void __stdcall DebugBreak(void); | ||||
|         return static_cast<T>(key) == 0;                                                           \ | ||||
|     } | ||||
|  | ||||
| #define CITRA_NON_COPYABLE(cls)                                                                    \ | ||||
|     cls(const cls&) = delete;                                                                      \ | ||||
|     cls& operator=(const cls&) = delete | ||||
|  | ||||
| #define CITRA_NON_MOVEABLE(cls)                                                                    \ | ||||
|     cls(cls&&) = delete;                                                                           \ | ||||
|     cls& operator=(cls&&) = delete | ||||
|  | ||||
|  | ||||
| // Generic function to get last error message. | ||||
| // Call directly after the command or use the error num. | ||||
| // This function might change the error code. | ||||
|   | ||||
| @@ -505,6 +505,9 @@ struct Values { | ||||
|     // Miscellaneous | ||||
|     Setting<std::string> log_filter{"*:Info", "log_filter"}; | ||||
|  | ||||
|     // Network | ||||
|     Setting<std::string> network_interface{std::string(), "network_interface"}; | ||||
|  | ||||
|     // Video Dumping | ||||
|     std::string output_format; | ||||
|     std::string format_options; | ||||
|   | ||||
							
								
								
									
										143
									
								
								src/common/socket_types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/common/socket_types.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| /// Address families | ||||
| enum class Domain : u32 { | ||||
|     UNSPEC = 0, | ||||
|     INET = 2, ///< Address family for IPv4 | ||||
|     INET6 = 23, | ||||
| }; | ||||
|  | ||||
| /// Socket types | ||||
| enum class Type : u32 { | ||||
|     STREAM = 1, | ||||
|     DGRAM = 2, | ||||
| }; | ||||
|  | ||||
| /// Shutdown mode | ||||
| enum class ShutdownHow { | ||||
|     RD, | ||||
|     WR, | ||||
|     RDWR, | ||||
| }; | ||||
|  | ||||
| enum class FcntlCmd : u32 { | ||||
|     GETFL = 3, | ||||
|     SETFL = 4, | ||||
| }; | ||||
|  | ||||
| enum class SendFlags : u32 { | ||||
|     OOB = 1, | ||||
|     PEEK = 2, | ||||
|     DONTWAIT = 4, | ||||
| }; | ||||
|  | ||||
| DECLARE_ENUM_FLAG_OPERATORS(SendFlags) | ||||
|  | ||||
| /// Array of IPv4 address | ||||
| using IPv4Address = std::array<u8, 4>; | ||||
|  | ||||
| /// Union to represent the 3DS sockaddr structure | ||||
| union CTRSockAddr { | ||||
|     /// Structure to represent a raw sockaddr | ||||
|     struct { | ||||
|         u8 len;           ///< The length of the entire structure, only the set fields count | ||||
|         u8 sa_family;     ///< The address family of the sockaddr | ||||
|         u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family | ||||
|     } raw; | ||||
|  | ||||
|     /// Structure to represent the 3ds' sockaddr_in structure | ||||
|     struct CTRSockAddrIn { | ||||
|         u8 len;        ///< The length of the entire structure | ||||
|         u8 sin_family; ///< The address family of the sockaddr_in | ||||
|         u16 sin_port;  ///< The port associated with this sockaddr_in | ||||
|         u32 sin_addr;  ///< The actual address of the sockaddr_in | ||||
|     } in; | ||||
|     static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size"); | ||||
| }; | ||||
|  | ||||
| struct CTRAddrInfo { | ||||
|     s32_le ai_flags; | ||||
|     s32_le ai_family; | ||||
|     s32_le ai_socktype; | ||||
|     s32_le ai_protocol; | ||||
|     s32_le ai_addrlen; | ||||
|     char ai_canonname[256]; | ||||
|     CTRSockAddr ai_addr; | ||||
|  | ||||
|     static u32 AddressInfoFlagsToPlatform(u32 flags) { | ||||
|         u32 ret = 0; | ||||
|         if (flags & 1) { | ||||
|             ret |= AI_PASSIVE; | ||||
|         } | ||||
|         if (flags & 2) { | ||||
|             ret |= AI_CANONNAME; | ||||
|         } | ||||
|         if (flags & 4) { | ||||
|             ret |= AI_NUMERICHOST; | ||||
|         } | ||||
|         if (flags & 8) { | ||||
|             ret |= AI_NUMERICSERV; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     static u32 AddressInfoFlagsFromPlatform(u32 flags) { | ||||
|         u32 ret = 0; | ||||
|         if (flags & AI_PASSIVE) { | ||||
|             ret |= 1; | ||||
|         } | ||||
|         if (flags & AI_CANONNAME) { | ||||
|             ret |= 2; | ||||
|         } | ||||
|         if (flags & AI_NUMERICHOST) { | ||||
|             ret |= 4; | ||||
|         } | ||||
|         if (flags & AI_NUMERICSERV) { | ||||
|             ret |= 8; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|            /// Converts a platform-specific addrinfo to a 3ds addrinfo. | ||||
|     static CTRAddrInfo FromPlatform(const addrinfo& addr) { | ||||
|         CTRAddrInfo ctr_addr{ | ||||
|             .ai_flags = static_cast<s32_le>(AddressInfoFlagsFromPlatform(addr.ai_flags)), | ||||
|             .ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)), | ||||
|             .ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)), | ||||
|             .ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)), | ||||
|             .ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr), | ||||
|         }; | ||||
|         ctr_addr.ai_addrlen = static_cast<s32_le>(ctr_addr.ai_addr.raw.len); | ||||
|         if (addr.ai_canonname) | ||||
|             std::strncpy(ctr_addr.ai_canonname, addr.ai_canonname, | ||||
|                          sizeof(ctr_addr.ai_canonname) - 1); | ||||
|         return ctr_addr; | ||||
|     } | ||||
|  | ||||
|            /// Converts a platform-specific addrinfo to a 3ds addrinfo. | ||||
|     static addrinfo ToPlatform(const CTRAddrInfo& ctr_addr) { | ||||
|         // Only certain fields are meaningful in hints, copy them manually | ||||
|         addrinfo addr = { | ||||
|             .ai_flags = static_cast<int>(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)), | ||||
|             .ai_family = static_cast<int>(SocketDomainToPlatform(ctr_addr.ai_family)), | ||||
|             .ai_socktype = static_cast<int>(SocketTypeToPlatform(ctr_addr.ai_socktype)), | ||||
|             .ai_protocol = static_cast<int>(SocketProtocolToPlatform(ctr_addr.ai_protocol)), | ||||
|         }; | ||||
|         return addr; | ||||
|     } | ||||
| }; | ||||
| static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct"); | ||||
|  | ||||
| constexpr u32 FLAG_MSG_PEEK = 0x2; | ||||
| constexpr u32 FLAG_MSG_DONTWAIT = 0x80; | ||||
| constexpr u32 FLAG_O_NONBLOCK = 0x800; | ||||
|  | ||||
| } // namespace Network | ||||
| @@ -439,6 +439,11 @@ add_library(citra_core STATIC | ||||
|     hw/rsa/rsa.h | ||||
|     hw/y2r.cpp | ||||
|     hw/y2r.h | ||||
|     network/network.cpp | ||||
|     network/network.h | ||||
|     network/network_interface.cpp | ||||
|     network/network_interface.h | ||||
|     network/sockets.h | ||||
|     loader/3dsx.cpp | ||||
|     loader/3dsx.h | ||||
|     loader/elf.cpp | ||||
|   | ||||
| @@ -304,13 +304,13 @@ MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process | ||||
|     perms = desc.perms; | ||||
| } | ||||
|  | ||||
| void MappedBuffer::Read(void* dest_buffer, std::size_t offset, std::size_t size) { | ||||
| void MappedBuffer::Read(void* dest_buffer, std::size_t offset, std::size_t size) const { | ||||
|     ASSERT(perms & IPC::R); | ||||
|     ASSERT(offset + size <= this->size); | ||||
|     memory->ReadBlock(*process, address + static_cast<VAddr>(offset), dest_buffer, size); | ||||
| } | ||||
|  | ||||
| void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t size) { | ||||
| void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t size) const { | ||||
|     ASSERT(perms & IPC::W); | ||||
|     ASSERT(offset + size <= this->size); | ||||
|     memory->WriteBlock(*process, address + static_cast<VAddr>(offset), src_buffer, size); | ||||
|   | ||||
| @@ -129,14 +129,16 @@ public: | ||||
|     MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process, u32 descriptor, | ||||
|                  VAddr address, u32 id); | ||||
|  | ||||
|     // interface for service | ||||
|     void Read(void* dest_buffer, std::size_t offset, std::size_t size); | ||||
|     void Write(const void* src_buffer, std::size_t offset, std::size_t size); | ||||
|     // Interface for service | ||||
|     void Read(void* dest_buffer, std::size_t offset, std::size_t size) const; | ||||
|  | ||||
|     void Write(const void* src_buffer, std::size_t offset, std::size_t size) const; | ||||
|  | ||||
|     std::size_t GetSize() const { | ||||
|         return size; | ||||
|     } | ||||
|  | ||||
|     // interface for ipc helper | ||||
|     // Interface for ipc helper | ||||
|     u32 GenerateDescriptor() const { | ||||
|         return IPC::MappedBufferDesc(size, perms); | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // Copyright 2015 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma optimize("", off) | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <boost/serialization/array.hpp> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #pragma optimize("", off) | ||||
| #include <array> | ||||
| #include <cstring> | ||||
| #include <type_traits> | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| #include "core/hle/kernel/shared_memory.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/soc_u.h" | ||||
| #include "core/network/network.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <winsock2.h> | ||||
| @@ -153,112 +154,6 @@ static u32 SocketTypeFromPlatform(u32 type) { | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| /// Holds the translation from system network errors to 3DS network errors | ||||
| static const std::unordered_map<int, int> error_map = {{ | ||||
|     {E2BIG, 1}, | ||||
|     {ERRNO(EACCES), 2}, | ||||
|     {ERRNO(EADDRINUSE), 3}, | ||||
|     {ERRNO(EADDRNOTAVAIL), 4}, | ||||
|     {ERRNO(EAFNOSUPPORT), 5}, | ||||
|     {EAGAIN, 6}, | ||||
| #ifdef _WIN32 | ||||
|     {WSAEWOULDBLOCK, 6}, | ||||
| #else | ||||
| #if EAGAIN != EWOULDBLOCK | ||||
|     {EWOULDBLOCK, 6}, | ||||
| #endif | ||||
| #endif // _WIN32 | ||||
|     {ERRNO(EALREADY), 7}, | ||||
|     {ERRNO(EBADF), 8}, | ||||
|     {EBADMSG, 9}, | ||||
|     {EBUSY, 10}, | ||||
|     {ECANCELED, 11}, | ||||
|     {ECHILD, 12}, | ||||
|     {ERRNO(ECONNABORTED), 13}, | ||||
|     {ERRNO(ECONNREFUSED), 14}, | ||||
|     {ERRNO(ECONNRESET), 15}, | ||||
|     {EDEADLK, 16}, | ||||
|     {ERRNO(EDESTADDRREQ), 17}, | ||||
|     {EDOM, 18}, | ||||
|     {ERRNO(EDQUOT), 19}, | ||||
|     {EEXIST, 20}, | ||||
|     {ERRNO(EFAULT), 21}, | ||||
|     {EFBIG, 22}, | ||||
|     {ERRNO(EHOSTUNREACH), 23}, | ||||
|     {EIDRM, 24}, | ||||
|     {EILSEQ, 25}, | ||||
|     {ERRNO(EINPROGRESS), 26}, | ||||
|     {ERRNO(EINTR), 27}, | ||||
|     {ERRNO(EINVAL), 28}, | ||||
|     {EIO, 29}, | ||||
|     {ERRNO(EISCONN), 30}, | ||||
|     {EISDIR, 31}, | ||||
|     {ERRNO(ELOOP), 32}, | ||||
|     {ERRNO(EMFILE), 33}, | ||||
|     {EMLINK, 34}, | ||||
|     {ERRNO(EMSGSIZE), 35}, | ||||
| #ifdef EMULTIHOP | ||||
|     {ERRNO(EMULTIHOP), 36}, | ||||
| #endif | ||||
|     {ERRNO(ENAMETOOLONG), 37}, | ||||
|     {ERRNO(ENETDOWN), 38}, | ||||
|     {ERRNO(ENETRESET), 39}, | ||||
|     {ERRNO(ENETUNREACH), 40}, | ||||
|     {ENFILE, 41}, | ||||
|     {ERRNO(ENOBUFS), 42}, | ||||
| #ifdef ENODATA | ||||
|     {ENODATA, 43}, | ||||
| #endif | ||||
|     {ENODEV, 44}, | ||||
|     {ENOENT, 45}, | ||||
|     {ENOEXEC, 46}, | ||||
|     {ENOLCK, 47}, | ||||
|     {ENOLINK, 48}, | ||||
|     {ENOMEM, 49}, | ||||
|     {ENOMSG, 50}, | ||||
|     {ERRNO(ENOPROTOOPT), 51}, | ||||
|     {ENOSPC, 52}, | ||||
| #ifdef ENOSR | ||||
|     {ENOSR, 53}, | ||||
| #endif | ||||
| #ifdef ENOSTR | ||||
|     {ENOSTR, 54}, | ||||
| #endif | ||||
|     {ENOSYS, 55}, | ||||
|     {ERRNO(ENOTCONN), 56}, | ||||
|     {ENOTDIR, 57}, | ||||
|     {ERRNO(ENOTEMPTY), 58}, | ||||
|     {ERRNO(ENOTSOCK), 59}, | ||||
|     {ENOTSUP, 60}, | ||||
|     {ENOTTY, 61}, | ||||
|     {ENXIO, 62}, | ||||
|     {ERRNO(EOPNOTSUPP), 63}, | ||||
|     {EOVERFLOW, 64}, | ||||
|     {EPERM, 65}, | ||||
|     {EPIPE, 66}, | ||||
|     {EPROTO, 67}, | ||||
|     {ERRNO(EPROTONOSUPPORT), 68}, | ||||
|     {ERRNO(EPROTOTYPE), 69}, | ||||
|     {ERANGE, 70}, | ||||
|     {EROFS, 71}, | ||||
|     {ESPIPE, 72}, | ||||
|     {ESRCH, 73}, | ||||
|     {ERRNO(ESTALE), 74}, | ||||
| #ifdef ETIME | ||||
|     {ETIME, 75}, | ||||
| #endif | ||||
|     {ERRNO(ETIMEDOUT), 76}, | ||||
| }}; | ||||
|  | ||||
| /// Converts a network error from platform-specific to 3ds-specific | ||||
| static int TranslateError(int error) { | ||||
|     const auto& found = error_map.find(error); | ||||
|     if (found != error_map.end()) { | ||||
|         return -found->second; | ||||
|     } | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| struct CTRLinger { | ||||
|     u32_le l_onoff; | ||||
|     u32_le l_linger; | ||||
| @@ -371,42 +266,6 @@ static void TranslateSockOptDataFromPlatform(std::vector<u8>& out, const std::ve | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool SOC_U::GetSocketBlocking(const SocketHolder& socket_holder) { | ||||
|     return socket_holder.blocking; | ||||
| } | ||||
| u32 SOC_U::SetSocketBlocking(SocketHolder& socket_holder, bool blocking) { | ||||
|     u32 posix_ret = 0; | ||||
| #ifdef _WIN32 | ||||
|     unsigned long nonblocking = (blocking) ? 0 : 1; | ||||
|     int ret = ioctlsocket(socket_holder.socket_fd, FIONBIO, &nonblocking); | ||||
|     if (ret == SOCKET_ERROR_VALUE) { | ||||
|         posix_ret = TranslateError(GET_ERRNO); | ||||
|         return posix_ret; | ||||
|     } | ||||
|     socket_holder.blocking = blocking; | ||||
| #else | ||||
|     int flags = ::fcntl(socket_holder.socket_fd, F_GETFL, 0); | ||||
|     if (flags == SOCKET_ERROR_VALUE) { | ||||
|         posix_ret = TranslateError(GET_ERRNO); | ||||
|         return posix_ret; | ||||
|     } | ||||
|  | ||||
|     flags &= ~O_NONBLOCK; | ||||
|     if (!blocking) { // O_NONBLOCK | ||||
|         flags |= O_NONBLOCK; | ||||
|     } | ||||
|  | ||||
|     socket_holder.blocking = blocking; | ||||
|  | ||||
|     const int ret = ::fcntl(socket_holder.socket_fd, F_SETFL, flags); | ||||
|     if (ret == SOCKET_ERROR_VALUE) { | ||||
|         posix_ret = TranslateError(GET_ERRNO); | ||||
|         return posix_ret; | ||||
|     } | ||||
| #endif | ||||
|     return posix_ret; | ||||
| } | ||||
|  | ||||
| static u32 SendRecvFlagsToPlatform(u32 flags) { | ||||
|     u32 ret = 0; | ||||
|     if (flags & 1) { | ||||
| @@ -513,173 +372,10 @@ struct CTRPollFD { | ||||
|     }; | ||||
|     Events events;  ///< Events to poll for (input) | ||||
|     Events revents; ///< Events received (output) | ||||
|  | ||||
|     /// Converts a platform-specific pollfd to a 3ds specific structure | ||||
|     static CTRPollFD FromPlatform(SOC::SOC_U& socu, pollfd const& fd, u8 has_libctru_bug) { | ||||
|         CTRPollFD result; | ||||
|         result.events.hex = Events::TranslateTo3DS(fd.events, has_libctru_bug).hex; | ||||
|         result.revents.hex = Events::TranslateTo3DS(fd.revents, has_libctru_bug).hex; | ||||
|         for (const auto& socket : socu.open_sockets) { | ||||
|             if (socket.second.socket_fd == fd.fd) { | ||||
|                 result.fd = socket.first; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// Converts a 3ds specific pollfd to a platform-specific structure | ||||
|     static pollfd ToPlatform(SOC::SOC_U& socu, CTRPollFD const& fd, u8& haslibctrbug) { | ||||
|         pollfd result; | ||||
|         u8 unused = 0; | ||||
|         result.events = Events::TranslateToPlatform(fd.events, false, haslibctrbug); | ||||
|         result.revents = Events::TranslateToPlatform(fd.revents, true, unused); | ||||
|         auto iter = socu.open_sockets.find(fd.fd); | ||||
|         result.fd = (iter != socu.open_sockets.end()) ? iter->second.socket_fd : 0; | ||||
|         if (iter == socu.open_sockets.end()) { | ||||
|             LOG_ERROR(Service_SOC, "Invalid socket handle: {}", fd.fd); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| }; | ||||
| static_assert(std::is_trivially_copyable_v<CTRPollFD>, | ||||
|               "CTRPollFD is used with std::memcpy and must be trivially copyable"); | ||||
|  | ||||
| /// Union to represent the 3ds' sockaddr structure | ||||
| union CTRSockAddr { | ||||
|     /// Structure to represent a raw sockaddr | ||||
|     struct { | ||||
|         u8 len;           ///< The length of the entire structure, only the set fields count | ||||
|         u8 sa_family;     ///< The address family of the sockaddr | ||||
|         u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family | ||||
|     } raw; | ||||
|  | ||||
|     /// Structure to represent the 3ds' sockaddr_in structure | ||||
|     struct CTRSockAddrIn { | ||||
|         u8 len;        ///< The length of the entire structure | ||||
|         u8 sin_family; ///< The address family of the sockaddr_in | ||||
|         u16 sin_port;  ///< The port associated with this sockaddr_in | ||||
|         u32 sin_addr;  ///< The actual address of the sockaddr_in | ||||
|     } in; | ||||
|     static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size"); | ||||
|  | ||||
|     /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr | ||||
|     static sockaddr ToPlatform(CTRSockAddr const& ctr_addr) { | ||||
|         sockaddr result; | ||||
|         ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn), | ||||
|                    "Unhandled address size (len) in CTRSockAddr::ToPlatform"); | ||||
|         result.sa_family = SocketDomainToPlatform(ctr_addr.raw.sa_family); | ||||
|         memset(result.sa_data, 0, sizeof(result.sa_data)); | ||||
|  | ||||
|         // We can not guarantee ABI compatibility between platforms so we copy the fields manually | ||||
|         switch (result.sa_family) { | ||||
|         case AF_INET: { | ||||
|             sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result); | ||||
|             result_in->sin_port = ctr_addr.in.sin_port; | ||||
|             result_in->sin_addr.s_addr = ctr_addr.in.sin_addr; | ||||
|             memset(result_in->sin_zero, 0, sizeof(result_in->sin_zero)); | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); | ||||
|             break; | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr | ||||
|     static CTRSockAddr FromPlatform(sockaddr const& addr) { | ||||
|         CTRSockAddr result; | ||||
|         result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.sa_family)); | ||||
|         // We can not guarantee ABI compatibility between platforms so we copy the fields manually | ||||
|         switch (addr.sa_family) { | ||||
|         case AF_INET: { | ||||
|             sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr); | ||||
|             result.raw.len = sizeof(CTRSockAddrIn); | ||||
|             result.in.sin_port = addr_in->sin_port; | ||||
|             result.in.sin_addr = addr_in->sin_addr.s_addr; | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); | ||||
|             break; | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct CTRAddrInfo { | ||||
|     s32_le ai_flags; | ||||
|     s32_le ai_family; | ||||
|     s32_le ai_socktype; | ||||
|     s32_le ai_protocol; | ||||
|     s32_le ai_addrlen; | ||||
|     char ai_canonname[256]; | ||||
|     CTRSockAddr ai_addr; | ||||
|  | ||||
|     static u32 AddressInfoFlagsToPlatform(u32 flags) { | ||||
|         u32 ret = 0; | ||||
|         if (flags & 1) { | ||||
|             ret |= AI_PASSIVE; | ||||
|         } | ||||
|         if (flags & 2) { | ||||
|             ret |= AI_CANONNAME; | ||||
|         } | ||||
|         if (flags & 4) { | ||||
|             ret |= AI_NUMERICHOST; | ||||
|         } | ||||
|         if (flags & 8) { | ||||
|             ret |= AI_NUMERICSERV; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     static u32 AddressInfoFlagsFromPlatform(u32 flags) { | ||||
|         u32 ret = 0; | ||||
|         if (flags & AI_PASSIVE) { | ||||
|             ret |= 1; | ||||
|         } | ||||
|         if (flags & AI_CANONNAME) { | ||||
|             ret |= 2; | ||||
|         } | ||||
|         if (flags & AI_NUMERICHOST) { | ||||
|             ret |= 4; | ||||
|         } | ||||
|         if (flags & AI_NUMERICSERV) { | ||||
|             ret |= 8; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /// Converts a platform-specific addrinfo to a 3ds addrinfo. | ||||
|     static CTRAddrInfo FromPlatform(const addrinfo& addr) { | ||||
|         CTRAddrInfo ctr_addr{ | ||||
|             .ai_flags = static_cast<s32_le>(AddressInfoFlagsFromPlatform(addr.ai_flags)), | ||||
|             .ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)), | ||||
|             .ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)), | ||||
|             .ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)), | ||||
|             .ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr), | ||||
|         }; | ||||
|         ctr_addr.ai_addrlen = static_cast<s32_le>(ctr_addr.ai_addr.raw.len); | ||||
|         if (addr.ai_canonname) | ||||
|             std::strncpy(ctr_addr.ai_canonname, addr.ai_canonname, | ||||
|                          sizeof(ctr_addr.ai_canonname) - 1); | ||||
|         return ctr_addr; | ||||
|     } | ||||
|  | ||||
|     /// Converts a platform-specific addrinfo to a 3ds addrinfo. | ||||
|     static addrinfo ToPlatform(const CTRAddrInfo& ctr_addr) { | ||||
|         // Only certain fields are meaningful in hints, copy them manually | ||||
|         addrinfo addr = { | ||||
|             .ai_flags = static_cast<int>(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)), | ||||
|             .ai_family = static_cast<int>(SocketDomainToPlatform(ctr_addr.ai_family)), | ||||
|             .ai_socktype = static_cast<int>(SocketTypeToPlatform(ctr_addr.ai_socktype)), | ||||
|             .ai_protocol = static_cast<int>(SocketProtocolToPlatform(ctr_addr.ai_protocol)), | ||||
|         }; | ||||
|         return addr; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| static u32 NameInfoFlagsToPlatform(u32 flags) { | ||||
|     u32 ret = 0; | ||||
|     if (flags & 1) { | ||||
| @@ -700,8 +396,6 @@ static u32 NameInfoFlagsToPlatform(u32 flags) { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct"); | ||||
|  | ||||
| void SOC_U::PreTimerAdjust() { | ||||
|     adjust_value_last = std::chrono::steady_clock::now(); | ||||
| } | ||||
| @@ -715,84 +409,64 @@ void SOC_U::PostTimerAdjust(Kernel::HLERequestContext& ctx, const std::string& c | ||||
|         nullptr); | ||||
| } | ||||
|  | ||||
| void SOC_U::CleanupSockets() { | ||||
|     for (const auto& sock : open_sockets) | ||||
|         closesocket(sock.second.socket_fd); | ||||
|     open_sockets.clear(); | ||||
| } | ||||
|  | ||||
| void SOC_U::Socket(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x02, 3, 2); | ||||
|     u32 domain = SocketDomainToPlatform(rp.Pop<u32>()); // Address family | ||||
|     u32 type = SocketTypeToPlatform(rp.Pop<u32>()); | ||||
|     u32 protocol = SocketProtocolToPlatform(rp.Pop<u32>()); | ||||
|     const auto domain = rp.Pop<Network::Domain>(); | ||||
|     const auto type = rp.Pop<Network::Type>(); | ||||
|     const u32 protocol = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|  | ||||
|     // Only 0 is allowed according to 3dbrew, using 0 will let the OS decide which protocol to use | ||||
|     if (protocol != 0) { | ||||
|     if (protocol != 0) [[unlikely]] { | ||||
|         rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code | ||||
|         rb.Skip(1, false); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (domain != AF_INET) { | ||||
|     if (domain != Network::Domain::INET) [[unlikely]] { | ||||
|         rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code | ||||
|         rb.Skip(1, false); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (type != SOCK_DGRAM && type != SOCK_STREAM) { | ||||
|     if (type != Network::Type::DGRAM && type != Network::Type::STREAM) [[unlikely]] { | ||||
|         rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code | ||||
|         rb.Skip(1, false); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     u64 ret = static_cast<u64>(::socket(domain, type, protocol)); | ||||
|     u32 socketHandle = GetNextSocketID(); | ||||
|     auto socket = std::make_unique<Network::Socket>(); | ||||
|     const auto ret = socket->Initialize(domain, type, protocol); | ||||
|     const u32 socket_handle = GetNextSocketID(); | ||||
|  | ||||
|     if ((s64)ret != SOCKET_ERROR_VALUE) { | ||||
|         open_sockets[socketHandle] = {static_cast<decltype(SocketHolder::socket_fd)>(ret), true}; | ||||
| #if _WIN32 | ||||
|         // Disable UDP connection reset | ||||
|         int new_behavior = 0; | ||||
|         unsigned long bytes_returned = 0; | ||||
|         WSAIoctl(static_cast<SOCKET>(ret), _WSAIOW(IOC_VENDOR, 12), &new_behavior, | ||||
|                  sizeof(new_behavior), NULL, 0, &bytes_returned, NULL, NULL); | ||||
| #endif | ||||
|     if (ret == Network::Errno::SUCCESS) { | ||||
|         open_sockets.emplace(socket_handle, std::move(socket)); | ||||
|     } | ||||
|  | ||||
|     if ((s64)ret == SOCKET_ERROR_VALUE) | ||||
|         ret = TranslateError(GET_ERRNO); | ||||
|  | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(socketHandle); | ||||
|     rb.Push(socket_handle); | ||||
| } | ||||
|  | ||||
| void SOC_U::Bind(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x05, 2, 4); | ||||
|     u32 socket_handle = rp.Pop<u32>(); | ||||
|     auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     const u32 socket_handle = rp.Pop<u32>(); | ||||
|     const u32 len = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|     const auto sock_addr_buf = rp.PopStaticBuffer(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     u32 len = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|     auto sock_addr_buf = rp.PopStaticBuffer(); | ||||
|  | ||||
|     CTRSockAddr ctr_sock_addr; | ||||
|     Network::CTRSockAddr ctr_sock_addr{}; | ||||
|     std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), len); | ||||
|  | ||||
|     sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr); | ||||
|  | ||||
|     s32 ret = ::bind(fd_info->second.socket_fd, &sock_addr, sizeof(sock_addr)); | ||||
|  | ||||
|     if (ret != 0) | ||||
|         ret = TranslateError(GET_ERRNO); | ||||
|     const auto ret = socket->Bind(ctr_sock_addr); | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| @@ -801,92 +475,87 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) { | ||||
|  | ||||
| void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x13, 3, 2); | ||||
|     u32 socket_handle = rp.Pop<u32>(); | ||||
|     auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     const u32 socket_handle = rp.Pop<u32>(); | ||||
|     const auto ctr_cmd = rp.Pop<Network::FcntlCmd>(); | ||||
|     const u32 ctr_arg = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     u32 ctr_cmd = rp.Pop<u32>(); | ||||
|     u32 ctr_arg = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     u32 posix_ret = 0; // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX) | ||||
|     SCOPE_EXIT({ | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|         rb.Push(RESULT_SUCCESS); | ||||
|         rb.Push(posix_ret); | ||||
|     }); | ||||
|  | ||||
|     if (ctr_cmd == 3) { // F_GETFL | ||||
|         posix_ret = 0; | ||||
|         if (GetSocketBlocking(fd_info->second) == false) | ||||
|             posix_ret |= 4;    // O_NONBLOCK | ||||
|     } else if (ctr_cmd == 4) { // F_SETFL | ||||
|         posix_ret = SetSocketBlocking(fd_info->second, !(ctr_arg & 4)); | ||||
|     } else { | ||||
|     // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX) | ||||
|     u32 posix_ret = 0; | ||||
|     switch (ctr_cmd) { | ||||
|     case Network::FcntlCmd::GETFL: | ||||
|         if (!socket->IsBlocking()) { | ||||
|             posix_ret |= 4; // O_NONBLOCK | ||||
|         } | ||||
|         break; | ||||
|     case Network::FcntlCmd::SETFL: | ||||
|         posix_ret = static_cast<u32>(socket->SetNonBlock(ctr_arg & 4)); | ||||
|     default: | ||||
|         LOG_ERROR(Service_SOC, "Unsupported command ({}) in fcntl call", ctr_cmd); | ||||
|         posix_ret = TranslateError(EINVAL); // TODO: Find the correct error | ||||
|         return; | ||||
|         posix_ret = static_cast<u32>(Network::Errno::INVAL); // TODO: Find the correct error | ||||
|     } | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(posix_ret); | ||||
| } | ||||
|  | ||||
| void SOC_U::Listen(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x03, 2, 2); | ||||
|     u32 socket_handle = rp.Pop<u32>(); | ||||
|     auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     const u32 socket_handle = rp.Pop<u32>(); | ||||
|     const u32 backlog = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     u32 backlog = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     s32 ret = ::listen(fd_info->second.socket_fd, backlog); | ||||
|     if (ret != 0) | ||||
|         ret = TranslateError(GET_ERRNO); | ||||
|     const auto ret = socket->Listen(backlog); | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(ret); | ||||
| } | ||||
|  | ||||
| static_assert(sizeof(socklen_t) == sizeof(u32), "socklen_t has incorrect size!"); | ||||
|  | ||||
| void SOC_U::Accept(Kernel::HLERequestContext& ctx) { | ||||
|     // TODO(Subv): Calling this function on a blocking socket will block the emu thread, | ||||
|     // preventing graceful shutdown when closing the emulator, this can be fixed by always | ||||
|     // performing nonblocking operations and spinlock until the data is available | ||||
|     IPC::RequestParser rp(ctx, 0x04, 2, 2); | ||||
|     const auto socket_handle = rp.Pop<u32>(); | ||||
|     auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     [[maybe_unused]] const auto max_addr_len = rp.Pop<socklen_t>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     [[maybe_unused]] const auto max_addr_len = static_cast<socklen_t>(rp.Pop<u32>()); | ||||
|     rp.PopPID(); | ||||
|     sockaddr addr; | ||||
|     socklen_t addr_len = sizeof(addr); | ||||
|     u32 ret = static_cast<u32>(::accept(fd_info->second.socket_fd, &addr, &addr_len)); | ||||
|  | ||||
|     if (static_cast<s32>(ret) != SOCKET_ERROR_VALUE) { | ||||
|         u32 socketID = GetNextSocketID(); | ||||
|         open_sockets[socketID] = {static_cast<decltype(SocketHolder::socket_fd)>(ret), true}; | ||||
|         ret = socketID; | ||||
|     } | ||||
|     auto [result, err] = socket->Accept(); | ||||
|  | ||||
|     CTRSockAddr ctr_addr; | ||||
|     std::vector<u8> ctr_addr_buf(sizeof(ctr_addr)); | ||||
|     if (static_cast<s32>(ret) == SOCKET_ERROR_VALUE) { | ||||
|         ret = TranslateError(GET_ERRNO); | ||||
|     } else { | ||||
|         ctr_addr = CTRSockAddr::FromPlatform(addr); | ||||
|         std::memcpy(ctr_addr_buf.data(), &ctr_addr, sizeof(ctr_addr)); | ||||
|     constexpr u32 size = sizeof(result.sockaddr_in); | ||||
|     std::vector<u8> ctr_addr_buf(size); | ||||
|  | ||||
|     u32 ret = static_cast<u32>(err); | ||||
|     if (err == Network::Errno::SUCCESS) { | ||||
|         const u32 socket_id = GetNextSocketID(); | ||||
|         open_sockets.emplace(socket_id, std::move(result.socket)); | ||||
|         std::memcpy(ctr_addr_buf.data(), &result.sockaddr_in, size); | ||||
|         ret = socket_id; | ||||
|     } | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); | ||||
| @@ -899,7 +568,7 @@ void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x16, 0, 0); | ||||
|  | ||||
|     u32 host_id = 0; | ||||
|     auto info = GetDefaultInterfaceInfo(); | ||||
|     const auto info = GetDefaultInterfaceInfo(); | ||||
|     if (info.has_value()) { | ||||
|         host_id = info->address; | ||||
|     } | ||||
| @@ -911,25 +580,19 @@ void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) { | ||||
|  | ||||
| void SOC_U::Close(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x0B, 1, 2); | ||||
|     u32 socket_handle = rp.Pop<u32>(); | ||||
|     auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     const u32 socket_handle = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     rp.PopPID(); | ||||
|  | ||||
|     s32 ret = 0; | ||||
|  | ||||
|     ret = closesocket(fd_info->second.socket_fd); | ||||
|  | ||||
|     const auto ret = socket->Close(); | ||||
|     open_sockets.erase(socket_handle); | ||||
|  | ||||
|     if (ret != 0) | ||||
|         ret = TranslateError(GET_ERRNO); | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|     rb.Push(ret); | ||||
| @@ -938,59 +601,48 @@ void SOC_U::Close(Kernel::HLERequestContext& ctx) { | ||||
| void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x09, 4, 6); | ||||
|     const u32 socket_handle = rp.Pop<u32>(); | ||||
|     const auto fd_info = open_sockets.find(socket_handle); | ||||
|     if (fd_info == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", socket_handle); | ||||
|     const u32 len = rp.Pop<u32>(); | ||||
|     const auto flags = rp.Pop<Network::SendFlags>(); | ||||
|     const u32 addr_len = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|     const auto dest_addr_buffer = rp.PopStaticBuffer(); | ||||
|     const auto input_mapped_buff = rp.PopMappedBuffer(); | ||||
|  | ||||
|     auto socket = FindSocket(socket_handle); | ||||
|     if (!socket) { | ||||
|         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|         rb.Push(ERR_INVALID_HANDLE); | ||||
|         return; | ||||
|     } | ||||
|     const u32 len = rp.Pop<u32>(); | ||||
|     u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>()); | ||||
|     bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0; | ||||
|     flags &= ~MSGCUSTOM_HANDLE_DONTWAIT; | ||||
| #ifdef _WIN32 | ||||
|     bool was_blocking = GetSocketBlocking(fd_info->second); | ||||
|     if (dont_wait && was_blocking) { | ||||
|         SetSocketBlocking(fd_info->second, false); | ||||
|     } | ||||
| #else | ||||
|     if (dont_wait) { | ||||
|         flags |= MSG_DONTWAIT; | ||||
|     } | ||||
| #endif // _WIN32 | ||||
|     const u32 addr_len = rp.Pop<u32>(); | ||||
|     rp.PopPID(); | ||||
|     const auto dest_addr_buffer = rp.PopStaticBuffer(); | ||||
|  | ||||
|     auto input_mapped_buff = rp.PopMappedBuffer(); | ||||
|     std::vector<u8> input_buff(len); | ||||
|     input_mapped_buff.Read(input_buff.data(), 0, | ||||
|                            std::min(input_mapped_buff.GetSize(), static_cast<size_t>(len))); | ||||
|  | ||||
|     s32 ret = -1; | ||||
|     if (addr_len > 0) { | ||||
|         CTRSockAddr ctr_dest_addr; | ||||
|         std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(), sizeof(ctr_dest_addr)); | ||||
|         sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr); | ||||
|         ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast<const char*>(input_buff.data()), | ||||
|                        len, flags, &dest_addr, sizeof(dest_addr)); | ||||
|     } else { | ||||
|         ret = ::sendto(fd_info->second.socket_fd, reinterpret_cast<const char*>(input_buff.data()), | ||||
|                        len, flags, nullptr, 0); | ||||
|     } | ||||
|  | ||||
|     const auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|     if (dont_wait && was_blocking) { | ||||
|         SetSocketBlocking(fd_info->second, true); | ||||
| #if WIN32 | ||||
|     const bool unblock = True(flags & Network::SendFlags::DONTWAIT) && socket->IsBlocking(); | ||||
|     if (unblock) { | ||||
|         socket->SetNonBlock(true); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     if (ret == SOCKET_ERROR_VALUE) { | ||||
|         ret = TranslateError(send_error); | ||||
|     std::vector<u8> message(len); | ||||
|     const size_t size = std::min<size_t>(input_mapped_buff.GetSize(), len); | ||||
|     input_mapped_buff.Read(message.data(), 0, size); | ||||
|  | ||||
|     const auto [result, err] = [&] { | ||||
|         if (addr_len > 0) { | ||||
|             Network::CTRSockAddr ctr_dest_addr; | ||||
|             std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(), sizeof(ctr_dest_addr)); | ||||
|             return socket->SendTo(flags, message, &ctr_dest_addr); | ||||
|         } else { | ||||
|             return socket->SendTo(flags, message, nullptr); | ||||
|         } | ||||
|     }(); | ||||
|  | ||||
|     const u32 ret = err != Network::Errno::SUCCESS ? static_cast<u32>(err) : result; | ||||
|  | ||||
| #if WIN32 | ||||
|     if (unblock) { | ||||
|         socket->SetNonBlock(false); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| @@ -1430,7 +1082,7 @@ void SOC_U::InitializeSockets(Kernel::HLERequestContext& ctx) { | ||||
| void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) { | ||||
|     // TODO(Subv): Implement | ||||
|     IPC::RequestParser rp(ctx, 0x19, 0, 0); | ||||
|     CleanupSockets(); | ||||
|     open_sockets.clear(); | ||||
|  | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| @@ -1674,6 +1326,15 @@ void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) { | ||||
|     rb.PushStaticBuffer(std::move(serv), 1); | ||||
| } | ||||
|  | ||||
| Network::Socket* SOC_U::FindSocket(u32 handle) { | ||||
|     const auto it = open_sockets.find(handle); | ||||
|     if (it == open_sockets.end()) { | ||||
|         LOG_ERROR(Service_SOC, "Invalid socket handle: {}", handle); | ||||
|         return nullptr; | ||||
|     } | ||||
|     return it->second.get(); | ||||
| } | ||||
|  | ||||
| SOC_U::SOC_U() : ServiceFramework("soc:U") { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         // clang-format off | ||||
| @@ -1714,19 +1375,9 @@ SOC_U::SOC_U() : ServiceFramework("soc:U") { | ||||
|     }; | ||||
|  | ||||
|     RegisterHandlers(functions); | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|     WSADATA data; | ||||
|     WSAStartup(MAKEWORD(2, 2), &data); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| SOC_U::~SOC_U() { | ||||
|     CleanupSockets(); | ||||
| #ifdef _WIN32 | ||||
|     WSACleanup(); | ||||
| #endif | ||||
| } | ||||
| SOC_U::~SOC_U() = default; | ||||
|  | ||||
| std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() { | ||||
|     if (this->interface_info_cached) { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include <boost/serialization/unordered_map.hpp> | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/service.h" | ||||
| #include "core/network/sockets.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| @@ -16,26 +17,6 @@ class System; | ||||
|  | ||||
| namespace Service::SOC { | ||||
|  | ||||
| /// Holds information about a particular socket | ||||
| struct SocketHolder { | ||||
| #ifdef _WIN32 | ||||
|     using SOCKET = unsigned long long; | ||||
|     SOCKET socket_fd; ///< The socket descriptor | ||||
| #else | ||||
|     int socket_fd; ///< The socket descriptor | ||||
| #endif // _WIN32 | ||||
|  | ||||
|     bool blocking = true; ///< Whether the socket is blocking or not. | ||||
|  | ||||
| private: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& socket_fd; | ||||
|         ar& blocking; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
|  | ||||
| class SOC_U final : public ServiceFramework<SOC_U> { | ||||
| public: | ||||
|     SOC_U(); | ||||
| @@ -63,11 +44,8 @@ private: | ||||
|  | ||||
|     static const std::unordered_map<u64, std::pair<int, int>> sockopt_map; | ||||
|     static std::pair<int, int> TranslateSockOpt(int level, int opt); | ||||
|     bool GetSocketBlocking(const SocketHolder& socket_holder); | ||||
|     u32 SetSocketBlocking(SocketHolder& socket_holder, bool blocking); | ||||
|  | ||||
|     // From | ||||
|     // https://github.com/devkitPro/libctru/blob/1de86ea38aec419744149daf692556e187d4678a/libctru/include/3ds/services/soc.h#L15 | ||||
|     // From https://github.com/devkitPro/libctru/blob/1de86ea3/libctru/include/3ds/services/soc.h#L15 | ||||
|     enum class NetworkOpt { | ||||
|         NETOPT_MAC_ADDRESS = 0x1004,     ///< The mac address of the interface | ||||
|         NETOPT_ARP_TABLE = 0x3002,       ///< The ARP table | ||||
| @@ -123,30 +101,14 @@ private: | ||||
|     void GetAddrInfoImpl(Kernel::HLERequestContext& ctx); | ||||
|     void GetNameInfoImpl(Kernel::HLERequestContext& ctx); | ||||
|  | ||||
|     // Socked ids | ||||
|     u32 next_socket_id = 3; | ||||
|     u32 GetNextSocketID() { | ||||
|         return next_socket_id++; | ||||
|     } | ||||
|     Network::Socket* FindSocket(u32 handle); | ||||
|  | ||||
|     // System timer adjust | ||||
|     std::chrono::time_point<std::chrono::steady_clock> adjust_value_last; | ||||
|     void PreTimerAdjust(); | ||||
|     void PostTimerAdjust(Kernel::HLERequestContext& ctx, const std::string& caller_method); | ||||
|  | ||||
|     /// Close all open sockets | ||||
|     void CleanupSockets(); | ||||
|  | ||||
|     /// Holds info about the currently open sockets | ||||
|     friend struct CTRPollFD; | ||||
|     std::unordered_map<u32, SocketHolder> open_sockets; | ||||
|  | ||||
|     /// Cache interface info for the current session | ||||
|     /// These two fields are not saved to savestates on purpose | ||||
|     /// as network interfaces may change and it's better to. | ||||
|     /// obtain them again between play sessions. | ||||
|     bool interface_info_cached = false; | ||||
|     InterfaceInfo interface_info; | ||||
|     [[nodiscard]] u32 GetNextSocketID() { | ||||
|         return next_socket_id++; | ||||
|     } | ||||
|  | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
| @@ -154,6 +116,18 @@ private: | ||||
|         ar& open_sockets; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
|  | ||||
| private: | ||||
|     std::chrono::time_point<std::chrono::steady_clock> adjust_value_last; | ||||
|     std::unordered_map<u32, std::unique_ptr<Network::Socket>> open_sockets; | ||||
|     u32 next_socket_id = 3; | ||||
|  | ||||
|     // Cache interface info for the current session | ||||
|     // These two fields are not saved to savestates on purpose | ||||
|     // as network interfaces may change and it's better to. | ||||
|     // obtain them again between play sessions. | ||||
|     bool interface_info_cached = false; | ||||
|     InterfaceInfo interface_info; | ||||
| }; | ||||
|  | ||||
| std::shared_ptr<SOC_U> GetService(Core::System& system); | ||||
|   | ||||
							
								
								
									
										747
									
								
								src/core/network/network.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										747
									
								
								src/core/network/network.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,747 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| #include <limits> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/error.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <winsock2.h> | ||||
| #include <ws2tcpip.h> | ||||
| #elif CITRA_UNIX | ||||
| #include <arpa/inet.h> | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <netdb.h> | ||||
| #include <netinet/in.h> | ||||
| #include <poll.h> | ||||
| #include <sys/socket.h> | ||||
| #include <unistd.h> | ||||
| #else | ||||
| #error "Unimplemented platform" | ||||
| #endif | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/network/network.h" | ||||
| #include "core/network/network_interface.h" | ||||
| #include "core/network/sockets.h" | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| using socklen_t = int; | ||||
|  | ||||
| #define ERRNO(x) WSA##x | ||||
|  | ||||
| void Initialize() { | ||||
|     WSADATA wsa_data; | ||||
|     (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); | ||||
| } | ||||
|  | ||||
| void Finalize() { | ||||
|     WSACleanup(); | ||||
| } | ||||
|  | ||||
| sockaddr TranslateFromSockAddrIn(CTRSockAddr ctr_addr) { | ||||
|     sockaddr result; | ||||
|     ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddr), | ||||
|                "Unhandled address size ({})", static_cast<u32>(ctr_addr.raw.len)); | ||||
|     result.sa_family = ctr_addr.raw.sa_family; | ||||
|     std::memset(result.sa_data, 0, sizeof(result.sa_data)); | ||||
|  | ||||
| #if CITRA_UNIX | ||||
|     result.sin_len = sizeof(result); | ||||
| #endif | ||||
|  | ||||
|     // We can not guarantee ABI compatibility between platforms so we copy the fields manually | ||||
|     const auto domain = static_cast<Domain>(result.sa_family); | ||||
|     switch (domain) { | ||||
|     case Domain::INET: { | ||||
|         sockaddr_in result_in; | ||||
|         std::memcpy(&result_in, &result, sizeof(result_in)); | ||||
|         result_in.sin_port = ctr_addr.in.sin_port; | ||||
|         result_in.sin_addr.s_addr = ctr_addr.in.sin_addr; | ||||
|         std::memset(result_in.sin_zero, 0, sizeof(result_in.sin_zero)); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|         ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| LINGER MakeLinger(bool enable, u32 linger_value) { | ||||
|     ASSERT(linger_value <= std::numeric_limits<u_short>::max()); | ||||
|  | ||||
|     LINGER value; | ||||
|     value.l_onoff = enable ? 1 : 0; | ||||
|     value.l_linger = static_cast<u_short>(linger_value); | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| bool EnableNonBlock(SOCKET fd, bool enable) { | ||||
|     u_long value = enable ? 1 : 0; | ||||
|     return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | ||||
| } | ||||
|  | ||||
| #elif CITRA_UNIX // ^ _WIN32 v CITRA_UNIX | ||||
|  | ||||
| using SOCKET = int; | ||||
| using WSAPOLLFD = pollfd; | ||||
| using ULONG = u64; | ||||
|  | ||||
| constexpr SOCKET SOCKET_ERROR = -1; | ||||
|  | ||||
| constexpr int SD_RECEIVE = SHUT_RD; | ||||
| constexpr int SD_SEND = SHUT_WR; | ||||
| constexpr int SD_BOTH = SHUT_RDWR; | ||||
|  | ||||
| #define ERRNO(x) x | ||||
|  | ||||
| void Initialize() {} | ||||
|  | ||||
| void Finalize() {} | ||||
|  | ||||
| sockaddr TranslateFromSockAddrIn(SockAddrIn input) { | ||||
|     sockaddr_in result; | ||||
|  | ||||
|     switch (static_cast<Domain>(input.family)) { | ||||
|     case Domain::INET: | ||||
|         result.sin_family = AF_INET; | ||||
|         break; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family); | ||||
|         result.sin_family = AF_INET; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     result.sin_port = htons(input.portno); | ||||
|  | ||||
|     result.sin_addr.s_addr = input.ip[0] | input.ip[1] << 8 | input.ip[2] << 16 | input.ip[3] << 24; | ||||
|  | ||||
|     sockaddr addr; | ||||
|     std::memcpy(&addr, &result, sizeof(addr)); | ||||
|     return addr; | ||||
| } | ||||
|  | ||||
| int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) { | ||||
|     return poll(fds, static_cast<nfds_t>(nfds), timeout); | ||||
| } | ||||
|  | ||||
| int closesocket(SOCKET fd) { | ||||
|     return close(fd); | ||||
| } | ||||
|  | ||||
| linger MakeLinger(bool enable, u32 linger_value) { | ||||
|     linger value; | ||||
|     value.l_onoff = enable ? 1 : 0; | ||||
|     value.l_linger = linger_value; | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| bool EnableNonBlock(int fd, bool enable) { | ||||
|     int flags = fcntl(fd, F_GETFL); | ||||
|     if (flags == -1) { | ||||
|         return false; | ||||
|     } | ||||
|     if (enable) { | ||||
|         flags |= O_NONBLOCK; | ||||
|     } else { | ||||
|         flags &= ~O_NONBLOCK; | ||||
|     } | ||||
|     return fcntl(fd, F_SETFL, flags) == 0; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| Errno TranslateNativeError(int e) { | ||||
|     switch (e) { | ||||
|     case E2BIG: | ||||
|         return Errno::TBIG; | ||||
|     case ERRNO(EACCES): | ||||
|         return Errno::ACCES; | ||||
|     case ERRNO(EADDRINUSE): | ||||
|         return Errno::ADDRINUSE; | ||||
|     case ERRNO(EADDRNOTAVAIL): | ||||
|         return Errno::ADDRNOTAVAIL; | ||||
|     case ERRNO(EAFNOSUPPORT): | ||||
|         return Errno::AFNOSUPPORT; | ||||
|     case EAGAIN: | ||||
| #if WIN32 || EAGAIN != EWOULDBLOCK | ||||
|     case ERRNO(EWOULDBLOCK): | ||||
| #endif | ||||
|         return Errno::WOULDBLOCK; | ||||
|     case ERRNO(EALREADY): | ||||
|         return Errno::ALREADY; | ||||
|     case ERRNO(EBADF): | ||||
|         return Errno::BADF; | ||||
|     case EBADMSG: | ||||
|         return Errno::BADMSG; | ||||
|     case EBUSY: | ||||
|         return Errno::BUSY; | ||||
|     case ECANCELED: | ||||
|         return Errno::CANCELED; | ||||
|     case ECHILD: | ||||
|         return Errno::CHILD; | ||||
|     case ERRNO(ECONNABORTED): | ||||
|         return Errno::CONNABORTED; | ||||
|     case ERRNO(ECONNREFUSED): | ||||
|         return Errno::CONNREFUSED; | ||||
|     case ERRNO(ECONNRESET): | ||||
|         return Errno::CONNRESET; | ||||
|     case EDEADLK: | ||||
|         return Errno::DEADLK; | ||||
|     case ERRNO(EDESTADDRREQ): | ||||
|         return Errno::DESTADDRREQ; | ||||
|     case EDOM: | ||||
|         return Errno::DOM; | ||||
|     case ERRNO(EDQUOT): | ||||
|         return Errno::DQUOT; | ||||
|     case EEXIST: | ||||
|         return Errno::EXIST; | ||||
|     case ERRNO(EFAULT): | ||||
|         return Errno::FAULT; | ||||
|     case EFBIG: | ||||
|         return Errno::FBIG; | ||||
|     case ERRNO(EHOSTUNREACH): | ||||
|         return Errno::HOSTUNREACH; | ||||
|     case EIDRM: | ||||
|         return Errno::IDRM; | ||||
|     case EILSEQ: | ||||
|         return Errno::ILSEQ; | ||||
|     case ERRNO(EINPROGRESS): | ||||
|         return Errno::INPROGRESS; | ||||
|     case ERRNO(EINTR): | ||||
|         return Errno::INTR; | ||||
|     case ERRNO(EINVAL): | ||||
|         return Errno::INVAL; | ||||
|     case EIO: | ||||
|         return Errno::IO; | ||||
|     case ERRNO(EISCONN): | ||||
|         return Errno::ISCONN; | ||||
|     case EISDIR: | ||||
|         return Errno::ISDIR; | ||||
|     case ERRNO(ELOOP): | ||||
|         return Errno::LOOP; | ||||
|     case ERRNO(EMFILE): | ||||
|         return Errno::MFILE; | ||||
|     case EMLINK: | ||||
|         return Errno::MLINK; | ||||
|     case ERRNO(EMSGSIZE): | ||||
|         return Errno::MSGSIZE; | ||||
| #ifdef EMULTIHOP | ||||
|     case ERRNO(EMULTIHOP): | ||||
|         return Errno::MULTIHOP; | ||||
| #endif | ||||
|     case ERRNO(ENAMETOOLONG): | ||||
|         return Errno::NAMETOOLONG; | ||||
|     case ERRNO(ENETDOWN): | ||||
|         return Errno::NETDOWN; | ||||
|     case ERRNO(ENETRESET): | ||||
|         return Errno::NETRESET; | ||||
|     case ERRNO(ENETUNREACH): | ||||
|         return Errno::NETUNREACH; | ||||
|     case ENFILE: | ||||
|         return Errno::NFILE; | ||||
|     case ERRNO(ENOBUFS): | ||||
|         return Errno::NOBUFS; | ||||
| #ifdef ENODATA | ||||
|     case ENODATA: | ||||
|         return Errno::NODATA; | ||||
| #endif | ||||
|     case ENODEV: | ||||
|         return Errno::NODEV; | ||||
|     case ENOENT: | ||||
|         return Errno::NOENT; | ||||
|     case ENOEXEC: | ||||
|         return Errno::NOEXEC; | ||||
|     case ENOLCK: | ||||
|         return Errno::NOLCK; | ||||
|     case ENOLINK: | ||||
|         return Errno::NOLINK; | ||||
|     case ENOMEM: | ||||
|         return Errno::NOMEM; | ||||
|     case ENOMSG: | ||||
|         return Errno::NOMSG; | ||||
|     case ERRNO(ENOPROTOOPT): | ||||
|         return Errno::NOPROTOOPT; | ||||
|     case ENOSPC: | ||||
|         return Errno::NOSPC; | ||||
| #ifdef ENOSR | ||||
|     case ENOSR: | ||||
|         return Errno::NOSR; | ||||
| #endif | ||||
| #ifdef ENOSTR | ||||
|     case ENOSTR: | ||||
|         return Errno::NOSTR; | ||||
| #endif | ||||
|     case ENOSYS: | ||||
|         return Errno::NOSYS; | ||||
|     case ERRNO(ENOTCONN): | ||||
|         return Errno::NOTCONN; | ||||
|     case ENOTDIR: | ||||
|         return Errno::NOTDIR; | ||||
|     case ERRNO(ENOTEMPTY): | ||||
|         return Errno::NOTEMPTY; | ||||
|     case ERRNO(ENOTSOCK): | ||||
|         return Errno::NOTSOCK; | ||||
|     case ENOTSUP: | ||||
|         return Errno::NOTSUP; | ||||
|     case ENOTTY: | ||||
|         return Errno::NOTTY; | ||||
|     case ENXIO: | ||||
|         return Errno::NXIO; | ||||
|     case ERRNO(EOPNOTSUPP): | ||||
|         return Errno::OPNOTSUPP; | ||||
|     case EOVERFLOW: | ||||
|         return Errno::OVERFLOW; | ||||
|     case EPERM: | ||||
|         return Errno::PERM; | ||||
|     case EPIPE: | ||||
|         return Errno::PIPE; | ||||
|     case EPROTO: | ||||
|         return Errno::PROTO; | ||||
|     case ERRNO(EPROTONOSUPPORT): | ||||
|         return Errno::PROTONOSUPPORT; | ||||
|     case ERRNO(EPROTOTYPE): | ||||
|         return Errno::PROTOTYPE; | ||||
|     case ERANGE: | ||||
|         return Errno::RANGE; | ||||
|     case EROFS: | ||||
|         return Errno::ROFS; | ||||
|     case ESPIPE: | ||||
|         return Errno::PIPE; | ||||
|     case ESRCH: | ||||
|         return Errno::SRCH; | ||||
|     case ERRNO(ESTALE): | ||||
|         return Errno::STALE; | ||||
| #ifdef ETIME | ||||
|     case ETIME: | ||||
|         return Errno::TIME; | ||||
| #endif | ||||
|     case ERRNO(ETIMEDOUT): | ||||
|         return Errno::TIMEDOUT; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unimplemented errno={}", e); | ||||
|         return Errno::OTHER; | ||||
|     } | ||||
| } | ||||
|  | ||||
| Errno GetAndLogLastError() { | ||||
| #ifdef _WIN32 | ||||
|     int e = WSAGetLastError(); | ||||
| #else | ||||
|     int e = errno; | ||||
| #endif | ||||
|     const Errno err = TranslateNativeError(e); | ||||
|     if (err == Errno::WOULDBLOCK || err == Errno::TIMEDOUT) { | ||||
|         return err; | ||||
|     } | ||||
|     LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); | ||||
|     return err; | ||||
| } | ||||
|  | ||||
| int TranslateDomain(Domain domain) { | ||||
|     switch (domain) { | ||||
|     case Domain::INET: | ||||
|         return AF_INET; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| int TranslateType(Type type) { | ||||
|     switch (type) { | ||||
|     case Type::STREAM: | ||||
|         return SOCK_STREAM; | ||||
|     case Type::DGRAM: | ||||
|         return SOCK_DGRAM; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unimplemented type={}", type); | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| CTRSockAddr TranslateToSockAddrIn(sockaddr input) { | ||||
|     CTRSockAddr result; | ||||
|     result.raw.sa_family = input.sa_family; | ||||
|  | ||||
|     // We can not guarantee ABI compatibility between platforms so we copy the fields manually | ||||
|     const auto domain = static_cast<Domain>(input.sa_family); | ||||
|     switch (domain) { | ||||
|     case Domain::INET: { | ||||
|         sockaddr_in addr_in; | ||||
|         std::memcpy(&addr_in, &input, sizeof(addr_in)); | ||||
|         result.raw.len = sizeof(CTRSockAddr::CTRSockAddrIn); | ||||
|         result.in.sin_port = addr_in.sin_port; | ||||
|         result.in.sin_addr = addr_in.sin_addr.s_addr; | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|         ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| short TranslatePollEvents(PollEvents events) { | ||||
|     short result = 0; | ||||
|  | ||||
|     if (True(events & PollEvents::In)) { | ||||
|         events &= ~PollEvents::In; | ||||
|         result |= POLLIN; | ||||
|     } | ||||
|     if (True(events & PollEvents::Pri)) { | ||||
|         events &= ~PollEvents::Pri; | ||||
| #ifdef _WIN32 | ||||
|         LOG_WARNING(Service, "Winsock doesn't support POLLPRI"); | ||||
| #else | ||||
|         result |= POLLPRI; | ||||
| #endif | ||||
|     } | ||||
|     if (True(events & PollEvents::Out)) { | ||||
|         events &= ~PollEvents::Out; | ||||
|         result |= POLLOUT; | ||||
|     } | ||||
|  | ||||
|     UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events); | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| PollEvents TranslatePollRevents(short revents) { | ||||
|     PollEvents result{}; | ||||
|     const auto translate = [&result, &revents](short host, PollEvents guest) { | ||||
|         if ((revents & host) != 0) { | ||||
|             revents &= static_cast<short>(~host); | ||||
|             result |= guest; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     translate(POLLIN, PollEvents::In); | ||||
|     translate(POLLPRI, PollEvents::Pri); | ||||
|     translate(POLLOUT, PollEvents::Out); | ||||
|     translate(POLLERR, PollEvents::Err); | ||||
|     translate(POLLHUP, PollEvents::Hup); | ||||
|  | ||||
|     UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| NetworkInstance::NetworkInstance() { | ||||
|     Initialize(); | ||||
| } | ||||
|  | ||||
| NetworkInstance::~NetworkInstance() { | ||||
|     Finalize(); | ||||
| } | ||||
|  | ||||
| std::optional<IPv4Address> GetHostIPv4Address() { | ||||
|     const auto network_interface = Network::GetSelectedNetworkInterface(); | ||||
|     if (!network_interface.has_value()) { | ||||
|         LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     std::array<char, 16> ip_addr = {}; | ||||
|     ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) != | ||||
|            nullptr); | ||||
|     return TranslateIPv4(network_interface->ip_address); | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { | ||||
|     const size_t num = pollfds.size(); | ||||
|  | ||||
|     std::vector<WSAPOLLFD> host_pollfds(pollfds.size()); | ||||
|     std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) { | ||||
|         WSAPOLLFD result; | ||||
|         result.fd = fd.socket->GetFD(); | ||||
|         result.events = TranslatePollEvents(fd.events); | ||||
|         result.revents = 0; | ||||
|         return result; | ||||
|     }); | ||||
|  | ||||
|     const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); | ||||
|     if (result == 0) { | ||||
|         ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), | ||||
|                            [](WSAPOLLFD fd) { return fd.revents == 0; })); | ||||
|         return {0, Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     for (size_t i = 0; i < num; ++i) { | ||||
|         pollfds[i].revents = TranslatePollRevents(host_pollfds[i].revents); | ||||
|     } | ||||
|  | ||||
|     if (result > 0) { | ||||
|         return {result, Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(result == SOCKET_ERROR); | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| Socket::~Socket() { | ||||
|     if (fd == INVALID_SOCKET) { | ||||
|         return; | ||||
|     } | ||||
|     (void)closesocket(fd); | ||||
|     fd = INVALID_SOCKET; | ||||
| } | ||||
|  | ||||
| Socket::Socket(Socket&& rhs) noexcept { | ||||
|     fd = std::exchange(rhs.fd, INVALID_SOCKET); | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
| Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { | ||||
|     const int result = | ||||
|         setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value)); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| Errno Socket::Initialize(Domain domain, Type type, u32 protocol) { | ||||
|     fd = socket(TranslateDomain(domain), TranslateType(type), protocol); | ||||
|     if (fd == INVALID_SOCKET) { | ||||
|         return GetAndLogLastError(); | ||||
|     } | ||||
|  | ||||
| #if _WIN32 | ||||
|     // Disable UDP connection reset | ||||
|     int new_behavior = 0; | ||||
|     unsigned long bytes_returned = 0; | ||||
|     WSAIoctl(fd, _WSAIOW(IOC_VENDOR, 12), &new_behavior, | ||||
|              sizeof(new_behavior), NULL, 0, &bytes_returned, NULL, NULL); | ||||
| #endif | ||||
|  | ||||
|     return Errno::SUCCESS; | ||||
| } | ||||
|  | ||||
| std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { | ||||
|     sockaddr addr; | ||||
|     socklen_t addrlen = sizeof(addr); | ||||
|     const SOCKET new_socket = accept(fd, &addr, &addrlen); | ||||
|  | ||||
|     if (new_socket == INVALID_SOCKET) { | ||||
|         return {AcceptResult{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(addrlen == sizeof(sockaddr_in)); | ||||
|  | ||||
|     AcceptResult result{ | ||||
|         .socket = std::make_unique<Socket>(new_socket), | ||||
|         .sockaddr_in = TranslateToSockAddrIn(addr), | ||||
|     }; | ||||
|  | ||||
|     return {std::move(result), Errno::SUCCESS}; | ||||
| } | ||||
|  | ||||
| Errno Socket::Connect(CTRSockAddr addr_in) { | ||||
|     const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in); | ||||
|     if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| std::pair<CTRSockAddr, Errno> Socket::GetPeerName() { | ||||
|     sockaddr addr; | ||||
|     socklen_t addrlen = sizeof(addr); | ||||
|     if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) { | ||||
|         return {CTRSockAddr{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(addrlen == sizeof(sockaddr_in)); | ||||
|     return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; | ||||
| } | ||||
|  | ||||
| std::pair<CTRSockAddr, Errno> Socket::GetSockName() { | ||||
|     sockaddr addr; | ||||
|     socklen_t addrlen = sizeof(addr); | ||||
|     if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) { | ||||
|         return {CTRSockAddr{}, GetAndLogLastError()}; | ||||
|     } | ||||
|  | ||||
|     ASSERT(addrlen == sizeof(sockaddr_in)); | ||||
|     return {TranslateToSockAddrIn(addr), Errno::SUCCESS}; | ||||
| } | ||||
|  | ||||
| Errno Socket::Bind(CTRSockAddr addr) { | ||||
|     const sockaddr addr_in = TranslateFromSockAddrIn(addr); | ||||
|     if (bind(fd, &addr_in, sizeof(addr_in)) != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| Errno Socket::Listen(s32 backlog) { | ||||
|     if (listen(fd, backlog) != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| Errno Socket::Shutdown(ShutdownHow how) { | ||||
|     int host_how = 0; | ||||
|     switch (how) { | ||||
|     case ShutdownHow::RD: | ||||
|         host_how = SD_RECEIVE; | ||||
|         break; | ||||
|     case ShutdownHow::WR: | ||||
|         host_how = SD_SEND; | ||||
|         break; | ||||
|     case ShutdownHow::RDWR: | ||||
|         host_how = SD_BOTH; | ||||
|         break; | ||||
|     default: | ||||
|         UNIMPLEMENTED_MSG("Unimplemented flag how={}", how); | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     if (shutdown(fd, host_how) != SOCKET_ERROR) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|  | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) { | ||||
|     ASSERT(flags == 0); | ||||
|     ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | ||||
|  | ||||
|     const auto result = | ||||
|         recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, CTRSockAddr* addr) { | ||||
|     ASSERT(flags == 0); | ||||
|     ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | ||||
|  | ||||
|     sockaddr addr_in{}; | ||||
|     socklen_t addrlen = sizeof(addr_in); | ||||
|     socklen_t* const p_addrlen = addr ? &addrlen : nullptr; | ||||
|     sockaddr* const p_addr_in = addr ? &addr_in : nullptr; | ||||
|  | ||||
|     const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()), | ||||
|                                  static_cast<int>(message.size()), 0, p_addr_in, p_addrlen); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         if (addr) { | ||||
|             ASSERT(addrlen == sizeof(addr_in)); | ||||
|             *addr = TranslateToSockAddrIn(addr_in); | ||||
|         } | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { | ||||
|     ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | ||||
|     ASSERT(flags == 0); | ||||
|  | ||||
|     const auto result = send(fd, reinterpret_cast<const char*>(message.data()), | ||||
|                              static_cast<int>(message.size()), 0); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::SendTo(SendFlags flags, std::span<const u8> message, | ||||
|                                      const CTRSockAddr* addr) { | ||||
|     const sockaddr* to = nullptr; | ||||
|     const int to_len = addr ? sizeof(sockaddr) : 0; | ||||
|     sockaddr host_addr_in; | ||||
|  | ||||
|     if (addr) { | ||||
|         host_addr_in = TranslateFromSockAddrIn(*addr); | ||||
|         to = &host_addr_in; | ||||
|     } | ||||
|  | ||||
|     const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()), | ||||
|                                static_cast<int>(message.size()), static_cast<int>(flags), to, to_len); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
| } | ||||
|  | ||||
| Errno Socket::Close() { | ||||
|     [[maybe_unused]] const int result = closesocket(fd); | ||||
|     ASSERT(result == 0); | ||||
|     fd = INVALID_SOCKET; | ||||
|  | ||||
|     return Errno::SUCCESS; | ||||
| } | ||||
|  | ||||
| Errno Socket::SetLinger(bool enable, u32 linger) { | ||||
|     return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger)); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetReuseAddr(bool enable) { | ||||
|     return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetKeepAlive(bool enable) { | ||||
|     return SetSockOpt<u32>(fd, SO_KEEPALIVE, enable ? 1 : 0); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetBroadcast(bool enable) { | ||||
|     return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetSndBuf(u32 value) { | ||||
|     return SetSockOpt(fd, SO_SNDBUF, value); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetRcvBuf(u32 value) { | ||||
|     return SetSockOpt(fd, SO_RCVBUF, value); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetSndTimeo(u32 value) { | ||||
|     return SetSockOpt(fd, SO_SNDTIMEO, value); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetRcvTimeo(u32 value) { | ||||
|     return SetSockOpt(fd, SO_RCVTIMEO, value); | ||||
| } | ||||
|  | ||||
| Errno Socket::SetNonBlock(bool enable) { | ||||
|     blocking = !enable; | ||||
|     if (EnableNonBlock(fd, enable)) { | ||||
|         return Errno::SUCCESS; | ||||
|     } | ||||
|     return GetAndLogLastError(); | ||||
| } | ||||
|  | ||||
| bool Socket::IsOpened() const { | ||||
|     return fd != INVALID_SOCKET; | ||||
| } | ||||
|  | ||||
| } // namespace Network | ||||
							
								
								
									
										150
									
								
								src/core/network/network.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/core/network/network.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <optional> | ||||
|  | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/socket_types.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <winsock2.h> | ||||
| #undef OVERFLOW | ||||
| #elif CITRA_UNIX | ||||
| #include <netinet/in.h> | ||||
| #endif | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| class SocketBase; | ||||
| class Socket; | ||||
|  | ||||
| /// Error code for network functions | ||||
| enum class Errno { | ||||
|     SUCCESS, | ||||
|     TBIG, | ||||
|     ACCES, | ||||
|     ADDRINUSE, | ||||
|     ADDRNOTAVAIL, | ||||
|     AFNOSUPPORT, | ||||
|     WOULDBLOCK, | ||||
|     ALREADY, | ||||
|     BADF, | ||||
|     BADMSG, | ||||
|     BUSY, | ||||
|     CANCELED, | ||||
|     CHILD, | ||||
|     CONNABORTED, | ||||
|     CONNREFUSED, | ||||
|     CONNRESET, | ||||
|     DEADLK, | ||||
|     DESTADDRREQ, | ||||
|     DOM, | ||||
|     DQUOT, | ||||
|     EXIST, | ||||
|     FAULT, | ||||
|     FBIG, | ||||
|     HOSTUNREACH, | ||||
|     IDRM, | ||||
|     ILSEQ, | ||||
|     INPROGRESS, | ||||
|     INTR, | ||||
|     INVAL, | ||||
|     IO, | ||||
|     ISCONN, | ||||
|     ISDIR, | ||||
|     LOOP, | ||||
|     MFILE, | ||||
|     MLINK, | ||||
|     MSGSIZE, | ||||
|     MULTIHOP, | ||||
|     NAMETOOLONG, | ||||
|     NETDOWN, | ||||
|     NETRESET, | ||||
|     NETUNREACH, | ||||
|     NFILE, | ||||
|     NOBUFS, | ||||
|     NODATA, | ||||
|     NODEV, | ||||
|     NOENT, | ||||
|     NOEXEC, | ||||
|     NOLCK, | ||||
|     NOLINK, | ||||
|     NOMEM, | ||||
|     NOMSG, | ||||
|     NOPROTOOPT, | ||||
|     NOSPC, | ||||
|     NOSR, | ||||
|     NOSTR, | ||||
|     NOSYS, | ||||
|     NOTCONN, | ||||
|     NOTDIR, | ||||
|     NOTEMPTY, | ||||
|     NOTSOCK, | ||||
|     NOTSUP, | ||||
|     NOTTY, | ||||
|     NXIO, | ||||
|     OPNOTSUPP, | ||||
|     OVERFLOW, | ||||
|     PERM, | ||||
|     PIPE, | ||||
|     PROTO, | ||||
|     PROTONOSUPPORT, | ||||
|     PROTOTYPE, | ||||
|     RANGE, | ||||
|     ROFS, | ||||
|     SPIPE, | ||||
|     SRCH, | ||||
|     STALE, | ||||
|     TIME, | ||||
|     TIMEDOUT, | ||||
|     OTHER, | ||||
| }; | ||||
|  | ||||
| /// Cross-platform poll fd structure | ||||
|  | ||||
| enum class PollEvents : u16 { | ||||
|     // Using Pascal case because IN is a macro on Windows. | ||||
|     In = 1 << 0, | ||||
|     Pri = 1 << 1, | ||||
|     Out = 1 << 2, | ||||
|     Err = 1 << 3, | ||||
|     Hup = 1 << 4, | ||||
|     Nval = 1 << 5, | ||||
| }; | ||||
|  | ||||
| DECLARE_ENUM_FLAG_OPERATORS(PollEvents); | ||||
|  | ||||
| struct PollFD { | ||||
|     SocketBase* socket; | ||||
|     PollEvents events; | ||||
|     PollEvents revents; | ||||
| }; | ||||
|  | ||||
| class NetworkInstance { | ||||
| public: | ||||
|     explicit NetworkInstance(); | ||||
|     ~NetworkInstance(); | ||||
| }; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| constexpr IPv4Address TranslateIPv4(in_addr addr) { | ||||
|     auto& bytes = addr.S_un.S_un_b; | ||||
|     return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4}; | ||||
| } | ||||
| #elif CITRA_UNIX | ||||
| constexpr IPv4Address TranslateIPv4(in_addr addr) { | ||||
|     const u32 bytes = addr.s_addr; | ||||
|     return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8), | ||||
|                        static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)}; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| /// @brief Returns host's IPv4 address | ||||
| /// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array | ||||
| std::optional<IPv4Address> GetHostIPv4Address(); | ||||
|  | ||||
| } // namespace Network | ||||
							
								
								
									
										218
									
								
								src/core/network/network_interface.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/core/network/network_interface.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| #include <vector> | ||||
|  | ||||
| #include "common/bit_cast.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/network/network_interface.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <iphlpapi.h> | ||||
| #else | ||||
| #include <cerrno> | ||||
| #include <ifaddrs.h> | ||||
| #include <net/if.h> | ||||
| #endif | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { | ||||
|     std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses; | ||||
|     DWORD ret = ERROR_BUFFER_OVERFLOW; | ||||
|     DWORD buf_size = 0; | ||||
|  | ||||
|     // retry up to 5 times | ||||
|     for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) { | ||||
|         ret = GetAdaptersAddresses( | ||||
|             AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, | ||||
|             nullptr, adapter_addresses.data(), &buf_size); | ||||
|  | ||||
|         if (ret != ERROR_BUFFER_OVERFLOW) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1); | ||||
|     } | ||||
|  | ||||
|     if (ret != NO_ERROR) { | ||||
|         LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses"); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     std::vector<NetworkInterface> result; | ||||
|  | ||||
|     for (auto current_address = adapter_addresses.data(); current_address != nullptr; | ||||
|          current_address = current_address->Next) { | ||||
|         if (current_address->FirstUnicastAddress == nullptr || | ||||
|             current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (current_address->OperStatus != IfOperStatusUp) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const auto ip_addr = Common::BitCast<struct sockaddr_in>( | ||||
|                                  *current_address->FirstUnicastAddress->Address.lpSockaddr) | ||||
|                                  .sin_addr; | ||||
|  | ||||
|         ULONG mask = 0; | ||||
|         if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength, | ||||
|                                     &mask) != NO_ERROR) { | ||||
|             LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask"); | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         struct in_addr gateway = {.S_un{.S_addr{0}}}; | ||||
|         if (current_address->FirstGatewayAddress != nullptr && | ||||
|             current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) { | ||||
|             gateway = Common::BitCast<struct sockaddr_in>( | ||||
|                           *current_address->FirstGatewayAddress->Address.lpSockaddr) | ||||
|                           .sin_addr; | ||||
|         } | ||||
|  | ||||
|         result.emplace_back(NetworkInterface{ | ||||
|             .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})}, | ||||
|             .ip_address{ip_addr}, | ||||
|             .subnet_mask = in_addr{.S_un{.S_addr{mask}}}, | ||||
|             .gateway = gateway}); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| std::vector<NetworkInterface> GetAvailableNetworkInterfaces() { | ||||
|     struct ifaddrs* ifaddr = nullptr; | ||||
|  | ||||
|     if (getifaddrs(&ifaddr) != 0) { | ||||
|         LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}", | ||||
|                   std::strerror(errno)); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     std::vector<NetworkInterface> result; | ||||
|  | ||||
|     for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { | ||||
|         if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (ifa->ifa_addr->sa_family != AF_INET) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         u32 gateway{}; | ||||
|  | ||||
|         std::ifstream file{"/proc/net/route"}; | ||||
|         if (!file.is_open()) { | ||||
|             LOG_ERROR(Network, "Failed to open \"/proc/net/route\""); | ||||
|  | ||||
|             result.emplace_back(NetworkInterface{ | ||||
|                 .name{ifa->ifa_name}, | ||||
|                 .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}, | ||||
|                 .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr}, | ||||
|                 .gateway{in_addr{.s_addr = gateway}}}); | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // ignore header | ||||
|         file.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); | ||||
|  | ||||
|         bool gateway_found = false; | ||||
|  | ||||
|         for (std::string line; std::getline(file, line);) { | ||||
|             std::istringstream iss{line}; | ||||
|  | ||||
|             std::string iface_name; | ||||
|             iss >> iface_name; | ||||
|             if (iface_name != ifa->ifa_name) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             iss >> std::hex; | ||||
|  | ||||
|             u32 dest{}; | ||||
|             iss >> dest; | ||||
|             if (dest != 0) { | ||||
|                 // not the default route | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             iss >> gateway; | ||||
|  | ||||
|             u16 flags{}; | ||||
|             iss >> flags; | ||||
|  | ||||
|             // flag RTF_GATEWAY (defined in <linux/route.h>) | ||||
|             if ((flags & 0x2) == 0) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             gateway_found = true; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         if (!gateway_found) { | ||||
|             gateway = 0; | ||||
|         } | ||||
|  | ||||
|         result.emplace_back(NetworkInterface{ | ||||
|             .name{ifa->ifa_name}, | ||||
|             .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}, | ||||
|             .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr}, | ||||
|             .gateway{in_addr{.s_addr = gateway}}}); | ||||
|     } | ||||
|  | ||||
|     freeifaddrs(ifaddr); | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| std::optional<NetworkInterface> GetSelectedNetworkInterface() { | ||||
|     const auto& selected_network_interface = Settings::values.network_interface.GetValue(); | ||||
|     const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); | ||||
|     if (network_interfaces.empty()) { | ||||
|         LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); | ||||
|         return std::nullopt; | ||||
|     } | ||||
|  | ||||
|     const auto res = | ||||
|         std::find_if(network_interfaces.begin(), network_interfaces.end(), [&selected_network_interface](const auto& iface) { | ||||
|             return iface.name == selected_network_interface; | ||||
|         }); | ||||
|  | ||||
|     if (res == network_interfaces.end()) { | ||||
|         LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); | ||||
|         return std::nullopt; | ||||
|     } | ||||
|  | ||||
|     return *res; | ||||
| } | ||||
|  | ||||
| void SelectFirstNetworkInterface() { | ||||
|     const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); | ||||
|  | ||||
|     if (network_interfaces.empty()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     Settings::values.network_interface.SetValue(network_interfaces[0].name); | ||||
| } | ||||
|  | ||||
| } // namespace Network | ||||
							
								
								
									
										31
									
								
								src/core/network/network_interface.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/core/network/network_interface.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <winsock2.h> | ||||
| #else | ||||
| #include <netinet/in.h> | ||||
| #endif | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| struct NetworkInterface { | ||||
|     std::string name; | ||||
|     struct in_addr ip_address; | ||||
|     struct in_addr subnet_mask; | ||||
|     struct in_addr gateway; | ||||
| }; | ||||
|  | ||||
| std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); | ||||
|  | ||||
| std::optional<NetworkInterface> GetSelectedNetworkInterface(); | ||||
|  | ||||
| void SelectFirstNetworkInterface(); | ||||
|  | ||||
| } // namespace Network | ||||
							
								
								
									
										174
									
								
								src/core/network/sockets.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/core/network/sockets.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| // Copyright 2020 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <utility> | ||||
| #include <boost/serialization/access.hpp> | ||||
|  | ||||
| #if defined(_WIN32) | ||||
| #elif !CITRA_UNIX | ||||
| #error "Platform not implemented" | ||||
| #endif | ||||
|  | ||||
| #include "common/common_types.h" | ||||
| #include "core/network/network.h" | ||||
|  | ||||
| namespace Network { | ||||
|  | ||||
| class SocketBase { | ||||
| public: | ||||
| #ifdef CITRA_UNIX | ||||
|     using SOCKET = int; | ||||
|     static constexpr SOCKET INVALID_SOCKET = -1; | ||||
|     static constexpr SOCKET SOCKET_ERROR = -1; | ||||
| #endif | ||||
|  | ||||
|     struct AcceptResult { | ||||
|         std::unique_ptr<SocketBase> socket; | ||||
|         CTRSockAddr sockaddr_in; | ||||
|     }; | ||||
|  | ||||
|     SocketBase() = default; | ||||
|     explicit SocketBase(SOCKET fd_) : fd{fd_} {} | ||||
|     virtual ~SocketBase() = default; | ||||
|  | ||||
|     CITRA_NON_COPYABLE(SocketBase); | ||||
|     CITRA_NON_MOVEABLE(SocketBase); | ||||
|  | ||||
|     virtual Errno Initialize(Domain domain, Type type, u32 protocol) = 0; | ||||
|  | ||||
|     virtual Errno Close() = 0; | ||||
|  | ||||
|     virtual std::pair<AcceptResult, Errno> Accept() = 0; | ||||
|  | ||||
|     virtual Errno Connect(CTRSockAddr addr_in) = 0; | ||||
|  | ||||
|     virtual std::pair<CTRSockAddr, Errno> GetPeerName() = 0; | ||||
|  | ||||
|     virtual std::pair<CTRSockAddr, Errno> GetSockName() = 0; | ||||
|  | ||||
|     virtual Errno Bind(CTRSockAddr addr) = 0; | ||||
|  | ||||
|     virtual Errno Listen(s32 backlog) = 0; | ||||
|  | ||||
|     virtual Errno Shutdown(ShutdownHow how) = 0; | ||||
|  | ||||
|     virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0; | ||||
|  | ||||
|     virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, | ||||
|                                            CTRSockAddr* addr) = 0; | ||||
|  | ||||
|     virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0; | ||||
|  | ||||
|     virtual std::pair<s32, Errno> SendTo(SendFlags flags, std::span<const u8> message, | ||||
|                                          const CTRSockAddr* addr) = 0; | ||||
|  | ||||
|     virtual Errno SetLinger(bool enable, u32 linger) = 0; | ||||
|  | ||||
|     virtual Errno SetReuseAddr(bool enable) = 0; | ||||
|  | ||||
|     virtual Errno SetKeepAlive(bool enable) = 0; | ||||
|  | ||||
|     virtual Errno SetBroadcast(bool enable) = 0; | ||||
|  | ||||
|     virtual Errno SetSndBuf(u32 value) = 0; | ||||
|  | ||||
|     virtual Errno SetRcvBuf(u32 value) = 0; | ||||
|  | ||||
|     virtual Errno SetSndTimeo(u32 value) = 0; | ||||
|  | ||||
|     virtual Errno SetRcvTimeo(u32 value) = 0; | ||||
|  | ||||
|     virtual Errno SetNonBlock(bool enable) = 0; | ||||
|  | ||||
|     virtual bool IsOpened() const = 0; | ||||
|  | ||||
|     [[nodiscard]] SOCKET GetFD() const { | ||||
|         return fd; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] bool IsBlocking() const { | ||||
|         return blocking; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     template <class Archive> | ||||
|     void serialize(Archive& ar, const unsigned int) { | ||||
|         ar& fd; | ||||
|         ar& blocking; | ||||
|     } | ||||
|     friend class boost::serialization::access; | ||||
|  | ||||
| protected: | ||||
|     SOCKET fd = INVALID_SOCKET; | ||||
|     bool blocking = true; | ||||
| }; | ||||
|  | ||||
| class Socket : public SocketBase { | ||||
| public: | ||||
|     Socket() = default; | ||||
|     explicit Socket(SOCKET fd_) : SocketBase{fd_} {} | ||||
|  | ||||
|     ~Socket() override; | ||||
|  | ||||
|     Socket(Socket&& rhs) noexcept; | ||||
|  | ||||
|     Errno Initialize(Domain domain, Type type, u32 protocol) override; | ||||
|  | ||||
|     Errno Close() override; | ||||
|  | ||||
|     std::pair<AcceptResult, Errno> Accept() override; | ||||
|  | ||||
|     Errno Connect(CTRSockAddr addr_in) override; | ||||
|  | ||||
|     std::pair<CTRSockAddr, Errno> GetPeerName() override; | ||||
|  | ||||
|     std::pair<CTRSockAddr, Errno> GetSockName() override; | ||||
|  | ||||
|     Errno Bind(CTRSockAddr addr) override; | ||||
|  | ||||
|     Errno Listen(s32 backlog) override; | ||||
|  | ||||
|     Errno Shutdown(ShutdownHow how) override; | ||||
|  | ||||
|     std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override; | ||||
|  | ||||
|     std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, CTRSockAddr* addr) override; | ||||
|  | ||||
|     std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override; | ||||
|  | ||||
|     std::pair<s32, Errno> SendTo(SendFlags flags, std::span<const u8> message, | ||||
|                                  const CTRSockAddr* addr) override; | ||||
|  | ||||
|     Errno SetLinger(bool enable, u32 linger) override; | ||||
|  | ||||
|     Errno SetReuseAddr(bool enable) override; | ||||
|  | ||||
|     Errno SetKeepAlive(bool enable) override; | ||||
|  | ||||
|     Errno SetBroadcast(bool enable) override; | ||||
|  | ||||
|     Errno SetSndBuf(u32 value) override; | ||||
|  | ||||
|     Errno SetRcvBuf(u32 value) override; | ||||
|  | ||||
|     Errno SetSndTimeo(u32 value) override; | ||||
|  | ||||
|     Errno SetRcvTimeo(u32 value) override; | ||||
|  | ||||
|     Errno SetNonBlock(bool enable) override; | ||||
|  | ||||
|     template <typename T> | ||||
|     Errno SetSockOpt(SOCKET fd, int option, T value); | ||||
|  | ||||
|     bool IsOpened() const override; | ||||
| }; | ||||
|  | ||||
| std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); | ||||
|  | ||||
| } // namespace Network | ||||
| @@ -10,6 +10,7 @@ | ||||
| #include <vector> | ||||
| #include <boost/serialization/vector.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "common/socket_types.h" | ||||
| #include "network/room.h" | ||||
|  | ||||
| namespace Network { | ||||
| @@ -44,6 +45,15 @@ private: | ||||
|     friend class boost::serialization::access; | ||||
| }; | ||||
|  | ||||
| /// Information about the received proxy packets. | ||||
| struct ProxyPacket { | ||||
|     SockAddrIn local_endpoint; | ||||
|     SockAddrIn remote_endpoint; | ||||
|     Protocol protocol; | ||||
|     bool broadcast; | ||||
|     std::vector<u8> data; | ||||
| }; | ||||
|  | ||||
| /// Represents a chat message. | ||||
| struct ChatEntry { | ||||
|     std::string nickname; ///< Nickname of the client who sent this message. | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // Copyright 2015 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma optimize("", off) | ||||
| #include "common/arch.h" | ||||
| #if CITRA_ARCH(x86_64) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user