mirror of
https://github.com/Lime3DS/Lime3DS
synced 2025-01-09 13:43:27 +00:00
1858 lines
63 KiB
C++
1858 lines
63 KiB
C++
// Copyright 2014 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include "common/archives.h"
|
|
#include "common/assert.h"
|
|
#include "common/bit_field.h"
|
|
#include "common/common_types.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/scope_exit.h"
|
|
#include "common/swap.h"
|
|
#include "core/core.h"
|
|
#include "core/hle/ipc_helpers.h"
|
|
#include "core/hle/kernel/shared_memory.h"
|
|
#include "core/hle/result.h"
|
|
#include "core/hle/service/soc_u.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
|
|
// MinGW does not define several errno constants
|
|
#ifndef _MSC_VER
|
|
#define EBADMSG 104
|
|
#define ENODATA 120
|
|
#define ENOMSG 122
|
|
#define ENOSR 124
|
|
#define ENOSTR 125
|
|
#define ETIME 137
|
|
#endif // _MSC_VER
|
|
#else
|
|
#include <cerrno>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <ifaddrs.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#define WSAEAGAIN WSAEWOULDBLOCK
|
|
#define WSAEMULTIHOP -1 // Invalid dummy value
|
|
#define ERRNO(x) WSA##x
|
|
#define GET_ERRNO WSAGetLastError()
|
|
#define poll(x, y, z) WSAPoll(x, y, z);
|
|
#define SHUT_RD SD_RECEIVE
|
|
#define SHUT_WR SD_SEND
|
|
#define SHUT_RDWR SD_BOTH
|
|
#else
|
|
#define ERRNO(x) x
|
|
#define GET_ERRNO errno
|
|
#define closesocket(x) close(x)
|
|
#endif
|
|
#define MSGCUSTOM_HANDLE_DONTWAIT 0x80000000
|
|
|
|
SERIALIZE_EXPORT_IMPL(Service::SOC::SOC_U)
|
|
|
|
namespace Service::SOC {
|
|
|
|
const s32 SOCKET_ERROR_VALUE = -1;
|
|
|
|
static u32 SocketProtocolToPlatform(u32 protocol) {
|
|
switch (protocol) {
|
|
case 0:
|
|
return 0;
|
|
case 6:
|
|
return IPPROTO_TCP;
|
|
case 17:
|
|
return IPPROTO_UDP;
|
|
default:
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static u32 SocketProtocolFromPlatform(u32 protocol) {
|
|
switch (protocol) {
|
|
case 0:
|
|
return 0;
|
|
case IPPROTO_TCP:
|
|
return 6;
|
|
case IPPROTO_UDP:
|
|
return 17;
|
|
default:
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static u32 SocketDomainToPlatform(u32 domain) {
|
|
switch (domain) {
|
|
case 0:
|
|
return AF_UNSPEC;
|
|
case 2:
|
|
return AF_INET;
|
|
case 23:
|
|
return AF_INET6;
|
|
default:
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static u32 SocketDomainFromPlatform(u32 domain) {
|
|
switch (domain) {
|
|
case AF_UNSPEC:
|
|
return 0;
|
|
case AF_INET:
|
|
return 2;
|
|
case AF_INET6:
|
|
return 23;
|
|
default:
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static u32 SocketTypeToPlatform(u32 type) {
|
|
switch (type) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return SOCK_STREAM;
|
|
case 2:
|
|
return SOCK_DGRAM;
|
|
default:
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static u32 SocketTypeFromPlatform(u32 type) {
|
|
switch (type) {
|
|
case 0:
|
|
return 0;
|
|
case SOCK_STREAM:
|
|
return 1;
|
|
case SOCK_DGRAM:
|
|
return 2;
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
};
|
|
|
|
/// Holds the translation from system network socket options to 3DS network socket options
|
|
/// Note: -1 = No effect/unavailable
|
|
static constexpr u64 sockopt_map_key(int i, int j) {
|
|
return (u64)i << 32 | (unsigned int)j;
|
|
}
|
|
const std::unordered_map<u64, std::pair<int, int>> SOC_U::sockopt_map = {{
|
|
{sockopt_map_key(SOC_SOL_IP, 0x0007), {IPPROTO_IP, IP_TOS}},
|
|
{sockopt_map_key(SOC_SOL_IP, 0x0008), {IPPROTO_IP, IP_TTL}},
|
|
{sockopt_map_key(SOC_SOL_IP, 0x0009), {IPPROTO_IP, IP_MULTICAST_LOOP}}, // Never used?
|
|
{sockopt_map_key(SOC_SOL_IP, 0x000A), {IPPROTO_IP, IP_MULTICAST_TTL}}, // Never used?
|
|
{sockopt_map_key(SOC_SOL_IP, 0x000B), {IPPROTO_IP, IP_ADD_MEMBERSHIP}}, // Never used?
|
|
{sockopt_map_key(SOC_SOL_IP, 0x000C), {IPPROTO_IP, IP_DROP_MEMBERSHIP}}, // Never used?
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x0004), {SOL_SOCKET, SO_REUSEADDR}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x0080), {SOL_SOCKET, SO_LINGER}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x0100), {SOL_SOCKET, SO_OOBINLINE}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1001), {SOL_SOCKET, SO_SNDBUF}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1002), {SOL_SOCKET, SO_RCVBUF}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1003),
|
|
{SOL_SOCKET, SO_SNDLOWAT}}, // Not supported in winsock2
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1004),
|
|
{SOL_SOCKET, SO_RCVLOWAT}}, // Not supported in winsock2
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1008), {SOL_SOCKET, SO_TYPE}},
|
|
{sockopt_map_key(SOC_SOL_SOCKET, 0x1009), {SOL_SOCKET, SO_ERROR}},
|
|
{sockopt_map_key(SOC_SOL_TCP, 0x2001), {IPPROTO_TCP, TCP_NODELAY}},
|
|
{sockopt_map_key(SOC_SOL_TCP, 0x2002), {IPPROTO_TCP, -1}}, // TCP_MAXSEG, never used?
|
|
{sockopt_map_key(SOC_SOL_TCP, 0x2003), {IPPROTO_TCP, -1}}, // TCP_STDURG, never used?
|
|
}};
|
|
|
|
/// Converts a socket option from platform-specific to 3ds-specific
|
|
std::pair<int, int> SOC_U::TranslateSockOpt(int level, int opt) {
|
|
const auto& found = sockopt_map.find(sockopt_map_key(level, opt));
|
|
if (found != sockopt_map.end()) {
|
|
return found->second;
|
|
}
|
|
return std::make_pair(SOL_SOCKET, opt);
|
|
}
|
|
|
|
static void TranslateSockOptDataToPlatform(std::vector<u8>& out, const std::vector<u8>& in,
|
|
int platform_level, int platform_opt) {
|
|
// linger structure may be different between 3DS and platform
|
|
if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER &&
|
|
in.size() == sizeof(CTRLinger)) {
|
|
linger linger_out;
|
|
linger_out.l_onoff = static_cast<decltype(linger_out.l_onoff)>(
|
|
reinterpret_cast<const CTRLinger*>(in.data())->l_onoff);
|
|
linger_out.l_linger = static_cast<decltype(linger_out.l_linger)>(
|
|
reinterpret_cast<const CTRLinger*>(in.data())->l_linger);
|
|
out.resize(sizeof(linger));
|
|
memcpy(out.data(), &linger_out, sizeof(linger));
|
|
return;
|
|
}
|
|
// Other options should have the size of an int, even for booleans
|
|
int value;
|
|
if (in.size() == sizeof(u8)) {
|
|
value = static_cast<int>(*reinterpret_cast<const u8*>(in.data()));
|
|
} else if (in.size() == sizeof(u16)) {
|
|
value = static_cast<int>(*reinterpret_cast<const u16_le*>(in.data()));
|
|
} else if (in.size() == sizeof(u32)) {
|
|
value = static_cast<int>(*reinterpret_cast<const u32_le*>(in.data()));
|
|
} else {
|
|
LOG_ERROR(
|
|
Service_SOC,
|
|
"Unknown sockopt data combination: in_size={}, platform_level={}, platform_opt={}",
|
|
in.size(), platform_level, platform_opt);
|
|
out = in;
|
|
return;
|
|
}
|
|
out.resize(sizeof(int));
|
|
memcpy(out.data(), &value, sizeof(int));
|
|
}
|
|
|
|
static u32 TranslateSockOptSizeToPlatform(int platform_level, int platform_opt) {
|
|
if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER)
|
|
return sizeof(linger);
|
|
return sizeof(int);
|
|
}
|
|
|
|
static void TranslateSockOptDataFromPlatform(std::vector<u8>& out, const std::vector<u8>& in,
|
|
int platform_level, int platform_opt) {
|
|
if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER && in.size() == sizeof(linger)) {
|
|
CTRLinger linger_out;
|
|
linger_out.l_onoff = static_cast<decltype(linger_out.l_onoff)>(
|
|
reinterpret_cast<const linger*>(in.data())->l_onoff);
|
|
linger_out.l_linger = static_cast<decltype(linger_out.l_linger)>(
|
|
reinterpret_cast<const linger*>(in.data())->l_linger);
|
|
memcpy(out.data(), &linger_out, std::min(out.size(), sizeof(CTRLinger)));
|
|
return;
|
|
}
|
|
if (out.size() == sizeof(u8) && in.size() == sizeof(int)) {
|
|
*reinterpret_cast<u8*>(out.data()) =
|
|
static_cast<u8>(*reinterpret_cast<const int*>(in.data()));
|
|
} else if (out.size() == sizeof(u16_le) && in.size() == sizeof(int)) {
|
|
*reinterpret_cast<u16_le*>(out.data()) =
|
|
static_cast<u16_le>(*reinterpret_cast<const int*>(in.data()));
|
|
} else if (out.size() == sizeof(u32_le) && in.size() == sizeof(int)) {
|
|
*reinterpret_cast<u32_le*>(out.data()) =
|
|
static_cast<u32_le>(*reinterpret_cast<const int*>(in.data()));
|
|
} else {
|
|
LOG_ERROR(Service_SOC,
|
|
"Unknown sockopt data combination: in_size={}, out_size={}, platform_level={}, "
|
|
"platform_opt={}",
|
|
in.size(), out.size(), platform_level, platform_opt);
|
|
out = in;
|
|
return;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
ret |= MSG_OOB;
|
|
}
|
|
if (flags & 2) {
|
|
ret |= MSG_PEEK;
|
|
}
|
|
if (flags & 4) {
|
|
// Magic value to decide platform specific action
|
|
ret |= MSGCUSTOM_HANDLE_DONTWAIT;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static s32 ShutdownHowToPlatform(s32 how) {
|
|
if (how == 0) {
|
|
return SHUT_RD;
|
|
}
|
|
if (how == 1) {
|
|
return SHUT_WR;
|
|
}
|
|
if (how == 2) {
|
|
return SHUT_RDWR;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// Structure to represent the 3ds' pollfd structure, which is different than most implementations
|
|
struct CTRPollFD {
|
|
u32 fd; ///< Socket handle
|
|
|
|
union Events {
|
|
u32 hex; ///< The complete value formed by the flags
|
|
BitField<0, 1, u32> pollrdnorm;
|
|
BitField<1, 1, u32> pollrdband; // The 3DS OS mistakenly uses POLLRDBAND as POLLPRI,
|
|
BitField<2, 1, u32> pollpri; // this is reflected in the translate functions below.
|
|
BitField<3, 1, u32> pollwrnorm;
|
|
BitField<4, 1, u32> pollwrband;
|
|
BitField<5, 1, u32> pollerr;
|
|
BitField<6, 1, u32> pollhup;
|
|
BitField<7, 1, u32> pollnval;
|
|
|
|
/// Translates the resulting events of a Poll operation from platform-specific to 3ds
|
|
/// specific
|
|
static Events TranslateTo3DS(u32 input_event, u8 haslibctrbug) {
|
|
Events ev = {};
|
|
|
|
if (input_event & POLLRDNORM)
|
|
ev.pollrdnorm.Assign(1);
|
|
if (input_event & POLLRDBAND)
|
|
ev.pollpri.Assign(1);
|
|
if (input_event & POLLHUP)
|
|
ev.pollhup.Assign(1);
|
|
if (input_event & POLLERR)
|
|
ev.pollerr.Assign(1);
|
|
if (input_event & POLLWRNORM) {
|
|
if (haslibctrbug) {
|
|
ev.pollwrband.Assign(1);
|
|
} else {
|
|
ev.pollwrnorm.Assign(1);
|
|
}
|
|
}
|
|
if (input_event & POLLWRBAND)
|
|
ev.pollwrband.Assign(1);
|
|
if (input_event & POLLNVAL)
|
|
ev.pollnval.Assign(1);
|
|
return ev;
|
|
}
|
|
|
|
/// Translates the resulting events of a Poll operation from 3ds specific to platform
|
|
/// specific
|
|
static u32 TranslateToPlatform(Events input_event, bool isOutput, u8& has_libctru_bug) {
|
|
#if _WIN32
|
|
constexpr bool isWin = true;
|
|
#else
|
|
constexpr bool isWin = false;
|
|
#endif
|
|
has_libctru_bug = 0;
|
|
if (!input_event.pollwrnorm && input_event.pollwrband) {
|
|
// Fixes a bug in libctru and some homebrew using libcurl
|
|
// having the wrong value for POLLOUT
|
|
input_event.pollwrnorm.Assign(1);
|
|
input_event.pollwrband.Assign(0);
|
|
has_libctru_bug = 1;
|
|
}
|
|
u32 ret = 0;
|
|
if (input_event.pollrdnorm)
|
|
ret |= POLLRDNORM;
|
|
if (input_event.pollrdband && !isWin)
|
|
ret |= POLLPRI;
|
|
if (input_event.pollhup && isOutput)
|
|
ret |= POLLHUP;
|
|
if (input_event.pollerr && isOutput)
|
|
ret |= POLLERR;
|
|
if (input_event.pollwrnorm)
|
|
ret |= POLLWRNORM;
|
|
if (input_event.pollwrband && !isWin)
|
|
ret |= POLLWRBAND;
|
|
if (input_event.pollnval && isOutput)
|
|
ret |= POLLNVAL;
|
|
return ret;
|
|
}
|
|
};
|
|
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) {
|
|
ret |= NI_NOFQDN;
|
|
}
|
|
if (flags & 2) {
|
|
ret |= NI_NUMERICHOST;
|
|
}
|
|
if (flags & 4) {
|
|
ret |= NI_NAMEREQD;
|
|
}
|
|
if (flags & 8) {
|
|
ret |= NI_NUMERICSERV;
|
|
}
|
|
if (flags & 16) {
|
|
ret |= NI_DGRAM;
|
|
}
|
|
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();
|
|
}
|
|
|
|
void SOC_U::PostTimerAdjust(Kernel::HLERequestContext& ctx, const std::string& caller_method) {
|
|
std::chrono::time_point<std::chrono::steady_clock> new_timer = std::chrono::steady_clock::now();
|
|
ASSERT(new_timer >= adjust_value_last);
|
|
ctx.SleepClientThread(
|
|
fmt::format("soc_u::{}", caller_method),
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(new_timer - adjust_value_last),
|
|
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>());
|
|
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) {
|
|
rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code
|
|
rb.Skip(1, false);
|
|
return;
|
|
}
|
|
|
|
if (domain != AF_INET) {
|
|
rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code
|
|
rb.Skip(1, false);
|
|
return;
|
|
}
|
|
|
|
if (type != SOCK_DGRAM && type != SOCK_STREAM) {
|
|
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();
|
|
|
|
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 ((s64)ret == SOCKET_ERROR_VALUE)
|
|
ret = TranslateError(GET_ERRNO);
|
|
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(socketHandle);
|
|
}
|
|
|
|
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);
|
|
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;
|
|
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);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
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);
|
|
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 {
|
|
LOG_ERROR(Service_SOC, "Unsupported command ({}) in fcntl call", ctr_cmd);
|
|
posix_ret = TranslateError(EINVAL); // TODO: Find the correct error
|
|
return;
|
|
}
|
|
}
|
|
|
|
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);
|
|
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);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(ctr_addr_buf), 0);
|
|
}
|
|
|
|
void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x16, 0, 0);
|
|
|
|
u32 host_id = 0;
|
|
auto info = GetDefaultInterfaceInfo();
|
|
if (info.has_value()) {
|
|
host_id = info->address;
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(host_id);
|
|
}
|
|
|
|
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);
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
ret = TranslateError(send_error);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
void SOC_U::SendTo(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x0A, 4, 6);
|
|
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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
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
|
|
u32 addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
auto input_buff = rp.PopStaticBuffer();
|
|
auto dest_addr_buff = rp.PopStaticBuffer();
|
|
|
|
s32 ret = -1;
|
|
if (addr_len > 0) {
|
|
CTRSockAddr ctr_dest_addr;
|
|
std::memcpy(&ctr_dest_addr, dest_addr_buff.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);
|
|
}
|
|
|
|
auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
|
|
|
|
#ifdef _WIN32
|
|
if (dont_wait && was_blocking) {
|
|
SetSocketBlocking(fd_info->second, true);
|
|
}
|
|
#endif
|
|
|
|
if (ret == SOCKET_ERROR_VALUE)
|
|
ret = TranslateError(send_error);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x7, 4, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
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
|
|
u32 addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
auto& buffer = rp.PopMappedBuffer();
|
|
|
|
CTRSockAddr ctr_src_addr;
|
|
std::vector<u8> output_buff(len);
|
|
std::vector<u8> addr_buff(addr_len);
|
|
sockaddr src_addr;
|
|
socklen_t src_addr_len = sizeof(src_addr);
|
|
|
|
s32 ret = -1;
|
|
if (GetSocketBlocking(fd_info->second) && !dont_wait) {
|
|
PreTimerAdjust();
|
|
}
|
|
|
|
if (addr_len > 0) {
|
|
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
|
|
len, flags, &src_addr, &src_addr_len);
|
|
if (ret >= 0 && src_addr_len > 0) {
|
|
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
|
std::memcpy(addr_buff.data(), &ctr_src_addr, addr_len);
|
|
}
|
|
} else {
|
|
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
|
|
len, flags, NULL, 0);
|
|
addr_buff.resize(0);
|
|
}
|
|
int recv_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
|
|
if (GetSocketBlocking(fd_info->second) && !dont_wait) {
|
|
PostTimerAdjust(ctx, "RecvFromOther");
|
|
}
|
|
#ifdef _WIN32
|
|
if (dont_wait && was_blocking) {
|
|
SetSocketBlocking(fd_info->second, true);
|
|
}
|
|
#endif
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
ret = TranslateError(recv_error);
|
|
} else {
|
|
buffer.Write(output_buff.data(), 0, ret);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 4);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(addr_buff), 0);
|
|
rb.PushMappedBuffer(buffer);
|
|
}
|
|
|
|
void SOC_U::RecvFrom(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, 0x08, 4, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
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
|
|
u32 addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
|
|
CTRSockAddr ctr_src_addr;
|
|
std::vector<u8> output_buff(len);
|
|
std::vector<u8> addr_buff(addr_len);
|
|
sockaddr src_addr;
|
|
socklen_t src_addr_len = sizeof(src_addr);
|
|
|
|
s32 ret = -1;
|
|
if (GetSocketBlocking(fd_info->second) && !dont_wait) {
|
|
PreTimerAdjust();
|
|
}
|
|
if (addr_len > 0) {
|
|
// Only get src adr if input adr available
|
|
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
|
|
len, flags, &src_addr, &src_addr_len);
|
|
if (ret >= 0 && src_addr_len > 0) {
|
|
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
|
std::memcpy(addr_buff.data(), &ctr_src_addr, addr_len);
|
|
}
|
|
} else {
|
|
ret = ::recvfrom(fd_info->second.socket_fd, reinterpret_cast<char*>(output_buff.data()),
|
|
len, flags, NULL, 0);
|
|
addr_buff.resize(0);
|
|
}
|
|
int recv_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
|
|
if (GetSocketBlocking(fd_info->second) && !dont_wait) {
|
|
PostTimerAdjust(ctx, "RecvFrom");
|
|
}
|
|
#ifdef _WIN32
|
|
if (dont_wait && was_blocking) {
|
|
SetSocketBlocking(fd_info->second, true);
|
|
}
|
|
#endif
|
|
s32 total_received = ret;
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
ret = TranslateError(recv_error);
|
|
total_received = 0;
|
|
}
|
|
|
|
// Write only the data we received to avoid overwriting parts of the buffer with zeros
|
|
output_buff.resize(total_received);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(3, 4);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.Push(total_received);
|
|
rb.PushStaticBuffer(std::move(output_buff), 0);
|
|
rb.PushStaticBuffer(std::move(addr_buff), 1);
|
|
}
|
|
|
|
void SOC_U::Poll(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x14, 2, 4);
|
|
u32 nfds = rp.Pop<u32>();
|
|
s32 timeout = rp.Pop<s32>();
|
|
rp.PopPID();
|
|
auto input_fds = rp.PopStaticBuffer();
|
|
|
|
std::vector<CTRPollFD> ctr_fds(nfds);
|
|
std::memcpy(ctr_fds.data(), input_fds.data(), nfds * sizeof(CTRPollFD));
|
|
|
|
// The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different
|
|
// sizes)
|
|
// so we have to copy the data in order
|
|
std::vector<pollfd> platform_pollfd(nfds);
|
|
std::vector<u8> has_libctru_bug(nfds, false);
|
|
for (u32 i = 0; i < nfds; i++) {
|
|
platform_pollfd[i] = CTRPollFD::ToPlatform(*this, ctr_fds[i], has_libctru_bug[i]);
|
|
}
|
|
|
|
if (timeout) {
|
|
PreTimerAdjust();
|
|
}
|
|
s32 ret = ::poll(platform_pollfd.data(), nfds, timeout);
|
|
if (timeout) {
|
|
PostTimerAdjust(ctx, "Poll");
|
|
}
|
|
|
|
// Now update the output 3ds_pollfd structure
|
|
for (u32 i = 0; i < nfds; i++) {
|
|
ctr_fds[i] = CTRPollFD::FromPlatform(*this, platform_pollfd[i], has_libctru_bug[i]);
|
|
}
|
|
|
|
std::vector<u8> output_fds(nfds * sizeof(CTRPollFD));
|
|
std::memcpy(output_fds.data(), ctr_fds.data(), nfds * sizeof(CTRPollFD));
|
|
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
int err = GET_ERRNO;
|
|
LOG_ERROR(Service_SOC, "Socket error: {}", err);
|
|
|
|
ret = TranslateError(GET_ERRNO);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(output_fds), 0);
|
|
}
|
|
|
|
void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x17, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
[[maybe_unused]] const auto max_addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
|
|
sockaddr dest_addr;
|
|
socklen_t dest_addr_len = sizeof(dest_addr);
|
|
s32 ret = ::getsockname(fd_info->second.socket_fd, &dest_addr, &dest_addr_len);
|
|
|
|
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
|
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
|
|
std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr));
|
|
|
|
if (ret != 0)
|
|
ret = TranslateError(GET_ERRNO);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(dest_addr_buff), 0);
|
|
}
|
|
|
|
void SOC_U::Shutdown(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x0C, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
s32 how = ShutdownHowToPlatform(rp.Pop<s32>());
|
|
rp.PopPID();
|
|
|
|
s32 ret = ::shutdown(fd_info->second.socket_fd, how);
|
|
if (ret != 0)
|
|
ret = TranslateError(GET_ERRNO);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
void SOC_U::GetHostByName(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x0D, 2, 2);
|
|
[[maybe_unused]] u32 name_len = rp.Pop<u32>();
|
|
[[maybe_unused]] u32 out_buf_len = rp.Pop<u32>();
|
|
auto host_name = rp.PopStaticBuffer();
|
|
|
|
struct hostent* result = ::gethostbyname(reinterpret_cast<char*>(host_name.data()));
|
|
|
|
std::vector<u8> hbn_data_out(sizeof(HostByNameData));
|
|
HostByNameData& hbn_data = *reinterpret_cast<HostByNameData*>(hbn_data_out.data());
|
|
int ret = 0;
|
|
|
|
if (result) {
|
|
hbn_data.addr_type = result->h_addrtype;
|
|
hbn_data.addr_len = result->h_length;
|
|
std::strncpy(hbn_data.h_name.data(), result->h_name, 255);
|
|
u16 count;
|
|
for (count = 0; count < HostByNameData::max_entries; count++) {
|
|
char* curr = result->h_aliases[count];
|
|
if (!curr) {
|
|
break;
|
|
}
|
|
std::strncpy(hbn_data.aliases[count].data(), curr, 255);
|
|
}
|
|
hbn_data.alias_count = count;
|
|
for (count = 0; count < HostByNameData::max_entries; count++) {
|
|
char* curr = result->h_addr_list[count];
|
|
if (!curr) {
|
|
break;
|
|
}
|
|
std::memcpy(hbn_data.addresses[count].data(), curr, result->h_length);
|
|
}
|
|
hbn_data.addr_count = count;
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(hbn_data_out), 0);
|
|
}
|
|
|
|
void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x18, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
[[maybe_unused]] const auto max_addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
|
|
sockaddr dest_addr;
|
|
socklen_t dest_addr_len = sizeof(dest_addr);
|
|
const int ret = ::getpeername(fd_info->second.socket_fd, &dest_addr, &dest_addr_len);
|
|
|
|
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
|
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
|
|
std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr));
|
|
|
|
int result = 0;
|
|
if (ret != 0) {
|
|
result = TranslateError(GET_ERRNO);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(result);
|
|
rb.PushStaticBuffer(std::move(dest_addr_buff), 0);
|
|
}
|
|
|
|
void SOC_U::Connect(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, 0x06, 2, 4);
|
|
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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
[[maybe_unused]] const auto input_addr_len = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
auto input_addr_buf = rp.PopStaticBuffer();
|
|
|
|
CTRSockAddr ctr_input_addr;
|
|
std::memcpy(&ctr_input_addr, input_addr_buf.data(), sizeof(ctr_input_addr));
|
|
|
|
sockaddr input_addr = CTRSockAddr::ToPlatform(ctr_input_addr);
|
|
if (GetSocketBlocking(fd_info->second)) {
|
|
PreTimerAdjust();
|
|
}
|
|
s32 ret = ::connect(fd_info->second.socket_fd, &input_addr, sizeof(input_addr));
|
|
if (GetSocketBlocking(fd_info->second)) {
|
|
PostTimerAdjust(ctx, "Connect");
|
|
}
|
|
if (ret != 0)
|
|
ret = TranslateError(GET_ERRNO);
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
}
|
|
|
|
void SOC_U::InitializeSockets(Kernel::HLERequestContext& ctx) {
|
|
// TODO(Subv): Implement
|
|
IPC::RequestParser rp(ctx, 0x01, 1, 4);
|
|
[[maybe_unused]] const auto memory_block_size = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
rp.PopObject<Kernel::SharedMemory>();
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
}
|
|
|
|
void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) {
|
|
// TODO(Subv): Implement
|
|
IPC::RequestParser rp(ctx, 0x19, 0, 0);
|
|
CleanupSockets();
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
}
|
|
|
|
void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x11, 4, 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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
const u32 level = rp.Pop<u32>();
|
|
const s32 optname = rp.Pop<s32>();
|
|
u32 optlen = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
|
|
s32 err = 0;
|
|
|
|
std::vector<u8> optval(optlen);
|
|
|
|
if (optname < 0) {
|
|
#ifdef _WIN32
|
|
err = WSAEINVAL;
|
|
#else
|
|
err = EINVAL;
|
|
#endif
|
|
} else {
|
|
const auto level_opt = TranslateSockOpt(level, optname);
|
|
std::vector<u8> platform_data(
|
|
TranslateSockOptSizeToPlatform(level_opt.first, level_opt.second));
|
|
socklen_t platform_data_size = static_cast<socklen_t>(platform_data.size());
|
|
err = ::getsockopt(fd_info->second.socket_fd, level_opt.first, level_opt.second,
|
|
reinterpret_cast<char*>(platform_data.data()), &platform_data_size);
|
|
if (err == SOCKET_ERROR_VALUE) {
|
|
err = TranslateError(GET_ERRNO);
|
|
} else {
|
|
platform_data.resize(static_cast<size_t>(platform_data_size));
|
|
TranslateSockOptDataFromPlatform(optval, platform_data, level_opt.first,
|
|
level_opt.second);
|
|
}
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(err);
|
|
rb.Push(static_cast<u32>(optval.size()));
|
|
rb.PushStaticBuffer(std::move(optval), 0);
|
|
}
|
|
|
|
void SOC_U::SetSockOpt(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x12, 4, 4);
|
|
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);
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
rb.Push(ERR_INVALID_HANDLE);
|
|
return;
|
|
}
|
|
const auto level = rp.Pop<u32>();
|
|
const auto optname = rp.Pop<s32>();
|
|
const auto optlen = rp.Pop<u32>();
|
|
rp.PopPID();
|
|
auto optval = rp.PopStaticBuffer();
|
|
optval.resize(optlen);
|
|
|
|
s32 err = 0;
|
|
|
|
if (optname < 0) {
|
|
#ifdef _WIN32
|
|
err = WSAEINVAL;
|
|
#else
|
|
err = EINVAL;
|
|
#endif
|
|
} else {
|
|
std::vector<u8> platform_data;
|
|
const auto levelopt = TranslateSockOpt(level, optname);
|
|
TranslateSockOptDataToPlatform(platform_data, optval, levelopt.first, levelopt.second);
|
|
err = static_cast<u32>(::setsockopt(fd_info->second.socket_fd, levelopt.first,
|
|
levelopt.second,
|
|
reinterpret_cast<char*>(platform_data.data()),
|
|
static_cast<socklen_t>(platform_data.size())));
|
|
if (err == SOCKET_ERROR_VALUE) {
|
|
err = TranslateError(GET_ERRNO);
|
|
}
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(err);
|
|
}
|
|
|
|
void SOC_U::GetNetworkOpt(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x1A, 3, 0);
|
|
u32 level = rp.Pop<u32>();
|
|
u32 opt_name = rp.Pop<u32>();
|
|
u32 opt_len = rp.Pop<u32>();
|
|
std::vector<u8> opt_data(opt_len);
|
|
|
|
u32 err = SOC_ERR_INAVLID_ENUM_VALUE;
|
|
|
|
/// Only available level is SOC_SOL_CONFIG, any other value returns an error
|
|
if (level == SOC_SOL_CONFIG) {
|
|
switch (static_cast<NetworkOpt>(opt_name)) {
|
|
case NetworkOpt::NETOPT_MAC_ADDRESS: {
|
|
if (opt_len >= 6) {
|
|
std::array<u8, 6> fake_mac = {0};
|
|
memcpy(opt_data.data(), fake_mac.data(), fake_mac.size());
|
|
}
|
|
LOG_WARNING(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name);
|
|
err = 0;
|
|
} break;
|
|
case NetworkOpt::NETOPT_IP_INFO: {
|
|
if (opt_len >= sizeof(InterfaceInfo)) {
|
|
InterfaceInfo& out_info = *reinterpret_cast<InterfaceInfo*>(opt_data.data());
|
|
auto info = GetDefaultInterfaceInfo();
|
|
if (info.has_value()) {
|
|
out_info = info.value();
|
|
}
|
|
}
|
|
// Extra data not used normally, takes 0xC bytes more
|
|
if (opt_len >= sizeof(InterfaceInfo) + 0xC) {
|
|
LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={} opt_len >= 24",
|
|
level, opt_name);
|
|
}
|
|
err = 0;
|
|
} break;
|
|
default:
|
|
LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name);
|
|
break;
|
|
}
|
|
} else {
|
|
LOG_ERROR(Service_SOC, "Unknown level={}", level);
|
|
}
|
|
|
|
if (err != 0) {
|
|
opt_data.resize(0);
|
|
opt_len = 0;
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(err);
|
|
rb.Push(static_cast<u32>(opt_len));
|
|
rb.PushStaticBuffer(std::move(opt_data), 0);
|
|
}
|
|
|
|
void SOC_U::GetAddrInfoImpl(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x0F, 4, 6);
|
|
u32 node_length = rp.Pop<u32>();
|
|
u32 service_length = rp.Pop<u32>();
|
|
u32 hints_size = rp.Pop<u32>();
|
|
u32 out_size = rp.Pop<u32>();
|
|
auto node = rp.PopStaticBuffer();
|
|
auto service = rp.PopStaticBuffer();
|
|
auto hints_buff = rp.PopStaticBuffer();
|
|
|
|
const char* node_data = node_length > 0 ? reinterpret_cast<const char*>(node.data()) : nullptr;
|
|
const char* service_data =
|
|
service_length > 0 ? reinterpret_cast<const char*>(service.data()) : nullptr;
|
|
|
|
s32 ret = -1;
|
|
addrinfo* out = nullptr;
|
|
if (hints_size > 0) {
|
|
CTRAddrInfo ctr_hints;
|
|
std::memcpy(&ctr_hints, hints_buff.data(), hints_size);
|
|
addrinfo hints = CTRAddrInfo::ToPlatform(ctr_hints);
|
|
// Mimic what soc does in real hardware
|
|
if (hints.ai_socktype != 0 && hints.ai_protocol == 0) {
|
|
hints.ai_protocol = hints.ai_socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
|
|
}
|
|
if (hints.ai_protocol != 0 && hints.ai_socktype == 0) {
|
|
hints.ai_socktype = hints.ai_protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM;
|
|
}
|
|
ret = getaddrinfo(node_data, service_data, &hints, &out);
|
|
} else {
|
|
ret = getaddrinfo(node_data, service_data, nullptr, &out);
|
|
}
|
|
|
|
std::vector<u8> out_buff(out_size);
|
|
u32 count = 0;
|
|
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
ret = TranslateError(GET_ERRNO);
|
|
out_buff.resize(0);
|
|
} else {
|
|
std::size_t pos = 0;
|
|
addrinfo* cur = out;
|
|
while (cur != nullptr) {
|
|
if (pos <= out_size - sizeof(CTRAddrInfo)) {
|
|
// According to 3dbrew, this function fills whatever it can and does not error even
|
|
// if the buffer is not big enough. However the count returned is always correct.
|
|
CTRAddrInfo ctr_addr = CTRAddrInfo::FromPlatform(*cur);
|
|
std::memcpy(out_buff.data() + pos, &ctr_addr, sizeof(ctr_addr));
|
|
pos += sizeof(ctr_addr);
|
|
}
|
|
cur = cur->ai_next;
|
|
count++;
|
|
}
|
|
if (out != nullptr)
|
|
freeaddrinfo(out);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.Push(count);
|
|
rb.PushStaticBuffer(std::move(out_buff), 0);
|
|
}
|
|
|
|
void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) {
|
|
IPC::RequestParser rp(ctx, 0x10, 4, 2);
|
|
u32 socklen = rp.Pop<u32>();
|
|
u32 hostlen = rp.Pop<u32>();
|
|
u32 servlen = rp.Pop<u32>();
|
|
s32 flags = NameInfoFlagsToPlatform(rp.Pop<s32>());
|
|
auto sa_buff = rp.PopStaticBuffer();
|
|
|
|
CTRSockAddr ctr_sa;
|
|
std::memcpy(&ctr_sa, sa_buff.data(), socklen);
|
|
sockaddr sa = CTRSockAddr::ToPlatform(ctr_sa);
|
|
|
|
std::vector<u8> host(hostlen);
|
|
std::vector<u8> serv(servlen);
|
|
char* host_data = hostlen > 0 ? reinterpret_cast<char*>(host.data()) : nullptr;
|
|
char* serv_data = servlen > 0 ? reinterpret_cast<char*>(serv.data()) : nullptr;
|
|
|
|
s32 ret = getnameinfo(&sa, sizeof(sa), host_data, hostlen, serv_data, servlen, flags);
|
|
if (ret == SOCKET_ERROR_VALUE) {
|
|
ret = TranslateError(GET_ERRNO);
|
|
}
|
|
|
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 4);
|
|
rb.Push(RESULT_SUCCESS);
|
|
rb.Push(ret);
|
|
rb.PushStaticBuffer(std::move(host), 0);
|
|
rb.PushStaticBuffer(std::move(serv), 1);
|
|
}
|
|
|
|
SOC_U::SOC_U() : ServiceFramework("soc:U") {
|
|
static const FunctionInfo functions[] = {
|
|
// clang-format off
|
|
{IPC::MakeHeader(0x0001, 1, 4), &SOC_U::InitializeSockets, "InitializeSockets"},
|
|
{IPC::MakeHeader(0x0002, 3, 2), &SOC_U::Socket, "Socket"},
|
|
{IPC::MakeHeader(0x0003, 2, 2), &SOC_U::Listen, "Listen"},
|
|
{IPC::MakeHeader(0x0004, 2, 2), &SOC_U::Accept, "Accept"},
|
|
{IPC::MakeHeader(0x0005, 2, 4), &SOC_U::Bind, "Bind"},
|
|
{IPC::MakeHeader(0x0006, 2, 4), &SOC_U::Connect, "Connect"},
|
|
{IPC::MakeHeader(0x0007, 4, 4), &SOC_U::RecvFromOther, "recvfrom_other"},
|
|
{IPC::MakeHeader(0x0008, 4, 2), &SOC_U::RecvFrom, "RecvFrom"},
|
|
{IPC::MakeHeader(0x0009, 4, 6), &SOC_U::SendToOther, "SendToOther"},
|
|
{IPC::MakeHeader(0x000A, 4, 6), &SOC_U::SendTo, "SendTo"},
|
|
{IPC::MakeHeader(0x000B, 1, 2), &SOC_U::Close, "Close"},
|
|
{IPC::MakeHeader(0x000C, 2, 2), &SOC_U::Shutdown, "Shutdown"},
|
|
{IPC::MakeHeader(0x000D, 2, 2), &SOC_U::GetHostByName, "GetHostByName"},
|
|
{IPC::MakeHeader(0x000E, 3, 2), nullptr, "GetHostByAddr"},
|
|
{IPC::MakeHeader(0x000F, 4, 6), &SOC_U::GetAddrInfoImpl, "GetAddrInfo"},
|
|
{IPC::MakeHeader(0x0010, 4, 2), &SOC_U::GetNameInfoImpl, "GetNameInfo"},
|
|
{IPC::MakeHeader(0x0011, 4, 2), &SOC_U::GetSockOpt, "GetSockOpt"},
|
|
{IPC::MakeHeader(0x0012, 4, 4), &SOC_U::SetSockOpt, "SetSockOpt"},
|
|
{IPC::MakeHeader(0x0013, 3, 2), &SOC_U::Fcntl, "Fcntl"},
|
|
{IPC::MakeHeader(0x0014, 2, 4), &SOC_U::Poll, "Poll"},
|
|
{IPC::MakeHeader(0x0015, 1, 2), nullptr, "SockAtMark"},
|
|
{IPC::MakeHeader(0x0016, 0, 0), &SOC_U::GetHostId, "GetHostId"},
|
|
{IPC::MakeHeader(0x0017, 2, 2), &SOC_U::GetSockName, "GetSockName"},
|
|
{IPC::MakeHeader(0x0018, 2, 2), &SOC_U::GetPeerName, "GetPeerName"},
|
|
{IPC::MakeHeader(0x0019, 0, 0), &SOC_U::ShutdownSockets, "ShutdownSockets"},
|
|
{IPC::MakeHeader(0x001A, 3, 0), &SOC_U::GetNetworkOpt, "GetNetworkOpt"},
|
|
{IPC::MakeHeader(0x001B, 1, 0), nullptr, "ICMPSocket"},
|
|
{IPC::MakeHeader(0x001C, 4, 4), nullptr, "ICMPPing"},
|
|
{IPC::MakeHeader(0x001D, 1, 0), nullptr, "ICMPCancel"},
|
|
{IPC::MakeHeader(0x001E, 1, 0), nullptr, "ICMPClose"},
|
|
{IPC::MakeHeader(0x001F, 1, 0), nullptr, "GetResolverInfo"},
|
|
{IPC::MakeHeader(0x0021, 0, 2), nullptr, "CloseSockets"},
|
|
{IPC::MakeHeader(0x0023, 1, 0), nullptr, "AddGlobalSocket"},
|
|
// clang-format on
|
|
};
|
|
|
|
RegisterHandlers(functions);
|
|
|
|
#ifdef _WIN32
|
|
WSADATA data;
|
|
WSAStartup(MAKEWORD(2, 2), &data);
|
|
#endif
|
|
}
|
|
|
|
SOC_U::~SOC_U() {
|
|
CleanupSockets();
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() {
|
|
if (this->interface_info_cached) {
|
|
return InterfaceInfo(this->interface_info);
|
|
}
|
|
|
|
InterfaceInfo ret;
|
|
s64 sock_fd = -1;
|
|
bool interface_found = false;
|
|
struct sockaddr_in s_in = {.sin_family = AF_INET, .sin_port = htons(53), .sin_addr = {}};
|
|
s_in.sin_addr.s_addr = inet_addr("8.8.8.8");
|
|
socklen_t s_info_len = sizeof(struct sockaddr_in);
|
|
sockaddr_in s_info;
|
|
|
|
if ((sock_fd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (::connect(sock_fd, (struct sockaddr*)(&s_in), sizeof(struct sockaddr_in)) != 0) {
|
|
closesocket(sock_fd);
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (::getsockname(sock_fd, (struct sockaddr*)&s_info, &s_info_len) != 0 ||
|
|
s_info_len != sizeof(struct sockaddr_in)) {
|
|
closesocket(sock_fd);
|
|
return std::nullopt;
|
|
}
|
|
closesocket(sock_fd);
|
|
|
|
#ifdef _WIN32
|
|
sock_fd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
|
|
if (sock_fd == SOCKET_ERROR) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const int max_interfaces = 100;
|
|
std::vector<INTERFACE_INFO> interface_list_vec(max_interfaces);
|
|
INTERFACE_INFO* interface_list = reinterpret_cast<INTERFACE_INFO*>(interface_list_vec.data());
|
|
unsigned long bytes_used;
|
|
if (WSAIoctl(sock_fd, SIO_GET_INTERFACE_LIST, 0, 0, interface_list,
|
|
max_interfaces * sizeof(INTERFACE_INFO), &bytes_used, 0, 0) == SOCKET_ERROR) {
|
|
closesocket(sock_fd);
|
|
return std::nullopt;
|
|
}
|
|
closesocket(sock_fd);
|
|
|
|
int num_interfaces = bytes_used / sizeof(INTERFACE_INFO);
|
|
for (int i = 0; i < num_interfaces; i++) {
|
|
if (((sockaddr*)&(interface_list[i].iiAddress))->sa_family == AF_INET &&
|
|
memcmp(&((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr,
|
|
&s_info.sin_addr.s_addr, sizeof(s_info.sin_addr.s_addr)) == 0) {
|
|
ret.address = ((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr;
|
|
ret.netmask = ((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr.s_addr;
|
|
ret.broadcast =
|
|
((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr.s_addr;
|
|
interface_found = true;
|
|
{
|
|
char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0};
|
|
std::strncpy(address,
|
|
inet_ntoa(((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr),
|
|
sizeof(address) - 1);
|
|
std::strncpy(netmask,
|
|
inet_ntoa(((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr),
|
|
sizeof(netmask) - 1);
|
|
std::strncpy(
|
|
broadcast,
|
|
inet_ntoa(((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr),
|
|
sizeof(broadcast) - 1);
|
|
|
|
LOG_DEBUG(Service_SOC, "Found interface: (addr: {}, netmask: {}, broadcast: {})",
|
|
address, netmask, broadcast);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
struct ifaddrs* ifaddr;
|
|
struct ifaddrs* ifa;
|
|
if (getifaddrs(&ifaddr) == -1) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
|
if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
|
|
struct sockaddr_in* in_address = (struct sockaddr_in*)ifa->ifa_addr;
|
|
struct sockaddr_in* in_netmask = (struct sockaddr_in*)ifa->ifa_netmask;
|
|
struct sockaddr_in* in_broadcast = (struct sockaddr_in*)ifa->ifa_broadaddr;
|
|
if (in_address->sin_addr.s_addr == s_info.sin_addr.s_addr) {
|
|
ret.address = in_address->sin_addr.s_addr;
|
|
ret.netmask = in_netmask->sin_addr.s_addr;
|
|
ret.broadcast = in_broadcast->sin_addr.s_addr;
|
|
interface_found = true;
|
|
{
|
|
char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0};
|
|
std::strncpy(address, inet_ntoa(in_address->sin_addr), sizeof(address) - 1);
|
|
std::strncpy(netmask, inet_ntoa(in_netmask->sin_addr), sizeof(netmask) - 1);
|
|
std::strncpy(broadcast, inet_ntoa(in_broadcast->sin_addr),
|
|
sizeof(broadcast) - 1);
|
|
|
|
LOG_DEBUG(Service_SOC,
|
|
"Found interface: (addr: {}, netmask: {}, broadcast: {})", address,
|
|
netmask, broadcast);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
freeifaddrs(ifaddr);
|
|
#endif // _WIN32
|
|
if (interface_found) {
|
|
this->interface_info = ret;
|
|
this->interface_info_cached = true;
|
|
return ret;
|
|
} else {
|
|
LOG_DEBUG(Service_SOC, "Interface not found");
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<SOC_U> GetService(Core::System& system) {
|
|
return system.ServiceManager().GetService<SOC_U>("cfg:u");
|
|
}
|
|
|
|
void InstallInterfaces(Core::System& system) {
|
|
auto& service_manager = system.ServiceManager();
|
|
std::make_shared<SOC_U>()->InstallAsService(service_manager);
|
|
}
|
|
|
|
} // namespace Service::SOC
|