sceNetInet: Recvmsg, Sendmsg rewritten, and sendto without an address

Recvmsg was a stub that failed without setting errno. It now receives
like recvfrom, scattering into the buffers and waiting like the other
blocking calls. Sendmsg is rewritten around the same msghdr reading.
Following pspautotests net/inet/msg:
- Recvmsg truncates the address to namelen, gives TCP none and a namelen
  of 0, sets MSG_TRUNC in msg_flags, and controllen to 0.
- Sendmsg with no buffers sends an empty datagram, which macOS failed.
- sendto without an address works on a connected socket. Linux failed
  it, given an address of length 0.

On Windows, Sendmsg uses WSASendTo, since WSASendMsg only takes
datagram sockets, and Recvmsg WSARecvFrom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-28 12:25:39 +01:00
1 parent edfeb7653b
commit d76e1a4496
2 files changed
+192 -237

No files matched your search

+191 -237
View File
@@ -118,6 +118,7 @@ enum class InetWaitOp : int {
SendTo,
Accept,
Connect,
RecvMsg,
};
struct InetWait {
@@ -480,6 +481,7 @@ static InetTry TryRecvFrom(SceUID threadID, const u32 *args);
static InetTry TrySendTo(SceUID threadID, const u32 *args);
static InetTry TryAccept(SceUID threadID, const u32 *args);
static InetTry TryConnectDone(SceUID threadID, const u32 *args);
static InetTry TryRecvMsg(SceUID threadID, const u32 *args);
// The host socket behind a guest one, or false if there's none. Unlike GetHostSocketFromInetSocket,
// doesn't assert, since a socket can be closed while a call waits for it, and works on Windows,
@@ -770,6 +772,7 @@ static InetTry TryInetOp(InetWaitOp op, SceUID threadID, const u32 *args, bool f
case InetWaitOp::SendTo: return TrySendTo(threadID, args);
case InetWaitOp::Accept: return TryAccept(threadID, args);
case InetWaitOp::Connect: return TryConnectDone(threadID, args);
case InetWaitOp::RecvMsg: return TryRecvMsg(threadID, args);
default: return { -1, false, 0 };
}
}
@@ -1652,7 +1655,8 @@ static InetTry TrySendTo(SceUID threadID, const u32 *args) {
else {
// FIXME: On non-Windows(including PSP too?) broadcast to INADDR_BROADCAST(255.255.255.255) might not be received by the sender itself when binded to specific IP (ie. 192.168.0.2) or INADDR_BROADCAST.
// Meanwhile, it might be received by itself when binded to subnet (ie. 192.168.0.255) or INADDR_ANY(0.0.0.0).
retval = sendto(inetSock->sock, (char*)Memory::GetPointerOrException(bufferPtr), len, flgs | MSG_NOSIGNAL, (struct sockaddr*)&saddr.addr, dstlen);
// Without a destination, it's a send() on a connected socket. Linux fails an address of length 0.
retval = sendto(inetSock->sock, (char*)Memory::GetPointerOrException(bufferPtr), len, flgs | MSG_NOSIGNAL, dst ? (struct sockaddr*)&saddr.addr : nullptr, dst ? dstlen : 0);
}
if (retval < 0) {
const int hostErrno = socket_errno;
@@ -1701,228 +1705,133 @@ static int sceNetInetSendto(int socket, u32 bufferPtr, int len, int flags, u32 t
return hleLogDebug(Log::sceNet, attempt.result);
}
// Similar to POSIX's sendmsg or Winsock2's WSASendMsg? Are their packets compatible one another?
// Games using this: The Warrior's Code
static int sceNetInetSendmsg(int socket, u32 msghdrPtr, int flags) {
// Note: sendmsg is concatenating iovec buffers before sending it, and send/sendto is just a wrapper for sendmsg according to https://stackoverflow.com/questions/4258834/how-sendmsg-works
int retval = -1;
if (!InetValidRange(msghdrPtr, sizeof(InetMsghdr), __FUNCTION__)) {
return hleLogError(Log::sceNet, retval);
}
#if defined(_WIN32)
typedef WSABUF HostIovec;
#else
typedef iovec HostIovec;
#endif
// Reads a game's msghdr, checking that everything the host will read or write through it is in game
// memory, and builds the host's buffer list from msg_iov. Fails with errno set.
static bool ReadInetMsghdr(u32 msghdrPtr, InetMsghdr *msg, std::vector<HostIovec> *iov, const char *func, SceUID threadID) {
if (!InetValidRange(msghdrPtr, sizeof(InetMsghdr), func, threadID)) {
return false;
}
*msg = *(const InetMsghdr *)Memory::GetPointerUnchecked(msghdrPtr);
if (msg->msg_iovlen < 0) {
SetInetErrno(ERROR_INET_EINVAL, func, threadID);
return false;
}
// In 64 bits, so a huge count can't wrap around to a small size.
const u64 iovBytes = (u64)msg->msg_iovlen * sizeof(SceNetIovec);
if ((msg->msg_name && !InetValidRange(msg->msg_name, sizeof(SceNetInetSockaddr), func, threadID)) ||
(msg->msg_iov && !InetValidRange(msg->msg_iov, iovBytes > 0xFFFFFFFF ? 0xFFFFFFFF : (u32)iovBytes, func, threadID)) ||
(msg->msg_control && !InetValidRange(msg->msg_control, msg->msg_controllen, func, threadID))) {
return false;
}
iov->assign(msg->msg_iov ? msg->msg_iovlen : 0, HostIovec{});
for (size_t i = 0; i < iov->size(); i++) {
const SceNetIovec &pspIov = ((const SceNetIovec *)Memory::GetPointerUnchecked(msg->msg_iov))[i];
if (!pspIov.iov_base) {
continue;
}
if (!InetValidRange(pspIov.iov_base, pspIov.iov_len, func, threadID)) {
return false;
}
char *base = (char *)Memory::GetPointerUnchecked(pspIov.iov_base);
#if defined(_WIN32)
(*iov)[i].buf = base;
(*iov)[i].len = pspIov.iov_len;
#else
(*iov)[i].iov_base = base;
(*iov)[i].iov_len = pspIov.iov_len;
#endif
}
return true;
}
// Games using this: The Warrior's Code.
// Any control data fails it with EINVAL, as does an address shorter than a sockaddr_in, on UDP and
// TCP alike (pspautotests net/inet/msg).
static int sceNetInetSendmsg(int socket, u32 msghdrPtr, int flags) {
InetSocket *inetSock;
if (!GetCallSocket(socket, &inetSock)) {
return InetBadSocket(socket, __FUNCTION__);
}
InetMsghdr msg;
std::vector<HostIovec> iov;
if (!ReadInetMsghdr(msghdrPtr, &msg, &iov, __FUNCTION__, __KernelGetCurThread())) {
return hleLogError(Log::sceNet, -1, "bad msghdr");
}
if (msg.msg_control && msg.msg_controllen) {
SetInetErrno(ERROR_INET_EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, -1, "control data, %d bytes", (int)msg.msg_controllen);
}
if (msg.msg_name && msg.msg_namelen < sizeof(SceNetInetSockaddr)) {
SetInetErrno(ERROR_INET_EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, -1, "namelen %d", (int)msg.msg_namelen);
}
// Sending no buffers sends an empty datagram. macOS fails a msghdr without any with EMSGSIZE.
if (iov.empty()) {
iov.push_back(HostIovec{});
}
const int hostFlags = convertMSGFlagsPSP2Host(flags & ~PSP_NET_INET_MSG_DONTWAIT) | MSG_NOSIGNAL;
InetMsghdr* pspMsghdr = (InetMsghdr*)Memory::GetPointerOrException(msghdrPtr);
// Everything below that the host reads from the game's memory.
if (pspMsghdr->msg_iovlen < 0) {
SetInetErrno(ERROR_INET_EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, retval, "bad msg_iovlen %d", (int)pspMsghdr->msg_iovlen);
}
// In 64 bits, so a huge count can't wrap around to a small size.
const u64 iovBytes = (u64)pspMsghdr->msg_iovlen * sizeof(SceNetIovec);
if ((pspMsghdr->msg_name && !InetValidRange(pspMsghdr->msg_name, sizeof(SceNetInetSockaddr), __FUNCTION__)) ||
(pspMsghdr->msg_iov && !InetValidRange(pspMsghdr->msg_iov, iovBytes > 0xFFFFFFFF ? 0xFFFFFFFF : (u32)iovBytes, __FUNCTION__)) ||
(pspMsghdr->msg_control && !InetValidRange(pspMsghdr->msg_control, pspMsghdr->msg_controllen, __FUNCTION__))) {
return hleLogError(Log::sceNet, retval, "bad pointer in msghdr");
}
if (pspMsghdr->msg_iov) {
const SceNetIovec *pspIov = (const SceNetIovec *)Memory::GetPointerUnchecked(pspMsghdr->msg_iov);
for (int i = 0; i < pspMsghdr->msg_iovlen; i++) {
if (pspIov[i].iov_base && !InetValidRange(pspIov[i].iov_base, pspIov[i].iov_len, __FUNCTION__)) {
return hleLogError(Log::sceNet, retval, "bad iov[%d] %08x, %d bytes", i, (u32)pspIov[i].iov_base, (u32)pspIov[i].iov_len);
}
}
}
// Any control data fails it, as does an address shorter than a sockaddr_in, on UDP and TCP alike
// (pspautotests net/inet/msg).
if (pspMsghdr->msg_control && pspMsghdr->msg_controllen) {
SetInetErrno(ERROR_INET_EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, retval, "control data, %d bytes", (int)pspMsghdr->msg_controllen);
}
if (pspMsghdr->msg_name && pspMsghdr->msg_namelen < sizeof(SceNetInetSockaddr)) {
SetInetErrno(ERROR_INET_EINVAL, __FUNCTION__);
return hleLogError(Log::sceNet, retval, "namelen %d", (int)pspMsghdr->msg_namelen);
}
int flgs = flags & ~PSP_NET_INET_MSG_DONTWAIT; // removing non-POSIX flag, which is an alternative way to use non-blocking mode
flgs = convertMSGFlagsPSP2Host(flgs);
SockAddrIN4 saddr{};
#if defined(_WIN32)
WSAMSG hdr;
WSACMSGHDR* chdr = NULL;
size_t iovecsize = sizeof(WSABUF);
WSABUF* iov = (WSABUF*)malloc(pspMsghdr->msg_iovlen * iovecsize);
#else
msghdr hdr;
cmsghdr* chdr = nullptr;
size_t iovecsize = sizeof(iovec);
iovec* iov = (iovec*)malloc(pspMsghdr->msg_iovlen * iovecsize);
#endif
if (iov == NULL) {
UpdateErrnoFromHost(__KernelGetCurThread(), ENOBUFS, __FUNCTION__);
return hleLogError(Log::sceNet, retval);
}
memset(iov, 0, pspMsghdr->msg_iovlen * iovecsize);
memset(&hdr, 0, sizeof(hdr));
if (pspMsghdr->msg_name != 0) {
SceNetInetSockaddr* pspSaddr = (SceNetInetSockaddr*)Memory::GetPointerOrException(pspMsghdr->msg_name);
int namelen = 0;
if (msg.msg_name) {
const SceNetInetSockaddr *pspSaddr = (const SceNetInetSockaddr *)Memory::GetPointerUnchecked(msg.msg_name);
saddr.addr.sa_family = pspSaddr->sa_family;
size_t datalen = std::min(pspMsghdr->msg_namelen - (sizeof(pspSaddr->sa_len) + sizeof(pspSaddr->sa_family)), sizeof(saddr.addr.sa_data));
memcpy(saddr.addr.sa_data, pspSaddr->sa_data, datalen);
DEBUG_LOG(Log::sceNet, "SendMsg: Address = %s, Port = %d", ip2str(saddr.in.sin_addr).c_str(), ntohs(saddr.in.sin_port));
#if defined(_WIN32)
hdr.name = &saddr.addr;
hdr.namelen = static_cast<int>(datalen + sizeof(saddr.addr.sa_family));
#else
hdr.msg_name = &saddr.addr;
hdr.msg_namelen = static_cast<int>(datalen + sizeof(saddr.addr.sa_family));
#endif
memcpy(saddr.addr.sa_data, pspSaddr->sa_data, sizeof(pspSaddr->sa_data));
// In the host's terms: its sockaddr_in is 16 bytes everywhere, though macOS's sa_family is a single byte.
namelen = std::min((int)msg.msg_namelen, (int)sizeof(saddr.in));
}
auto sendOnce = [&]() -> int {
#if defined(_WIN32)
hdr.lpBuffers = iov;
hdr.dwBufferCount = pspMsghdr->msg_iovlen;
#else
hdr.msg_iov = iov;
hdr.msg_iovlen = pspMsghdr->msg_iovlen;
#endif
if (pspMsghdr->msg_iov != 0) {
SceNetIovec* pspIov = (SceNetIovec*)Memory::GetPointerOrException(pspMsghdr->msg_iov);
for (int i = 0; i < pspMsghdr->msg_iovlen; i++) {
if (pspIov[i].iov_base != 0) {
#if defined(_WIN32)
iov[i].buf = (char*)Memory::GetPointerOrException(pspIov[i].iov_base);
iov[i].len = pspIov[i].iov_len;
#else
iov[i].iov_base = (char*)Memory::GetPointerOrException(pspIov[i].iov_base);
iov[i].iov_len = pspIov[i].iov_len;
#endif
}
// WSASendMsg() only takes datagram sockets, and without control data this does the same.
DWORD sent = 0;
if (WSASendTo(inetSock->sock, iov.data(), (DWORD)iov.size(), &sent, hostFlags, msg.msg_name ? &saddr.addr : nullptr, namelen, nullptr, nullptr) == SOCKET_ERROR) {
return -1;
}
}
// Control's Level (ie. host's SOL_SOCKET to/from psp's PSP_NET_INET_SOL_SOCKET) and Type (ie. SCM_RIGHTS) might need to be converted to be cross-platform
if (pspMsghdr->msg_control != 0) {
#if defined(_WIN32)
chdr = (WSACMSGHDR*)malloc(pspMsghdr->msg_controllen);
return (int)sent;
#else
chdr = (cmsghdr*)malloc(pspMsghdr->msg_controllen);
msghdr hdr{};
hdr.msg_name = msg.msg_name ? &saddr.addr : nullptr;
hdr.msg_namelen = namelen;
hdr.msg_iov = iov.data();
hdr.msg_iovlen = iov.size();
return (int)sendmsg(inetSock->sock, &hdr, hostFlags);
#endif
if (chdr == NULL) {
UpdateErrnoFromHost(__KernelGetCurThread(), ENOBUFS, __FUNCTION__);
free(iov);
return hleLogError(Log::sceNet, retval);
}
InetCmsghdr* pspCmsghdr = (InetCmsghdr*)Memory::GetPointerOrException(pspMsghdr->msg_control);
// TODO: Convert InetCmsghdr into platform-specific struct as they're affected by 32/64bit
memcpy(chdr, pspCmsghdr, pspMsghdr->msg_controllen);
#if defined(_WIN32)
hdr.Control.buf = (char*)chdr; // (char*)pspCmsghdr;
hdr.Control.len = pspMsghdr->msg_controllen;
// Note: Many existing implementations of CMSG_FIRSTHDR never look at msg_controllen and just return the value of cmsg_control.
if (pspMsghdr->msg_controllen >= sizeof(InetCmsghdr)) {
// TODO: Creates our own CMSG_* macros (32-bit version of it, similar to the one on PSP) to avoid alignment/size issue that can lead to memory corruption/out of bound issue.
for (WSACMSGHDR* cmsgptr = WSA_CMSG_FIRSTHDR(&hdr); cmsgptr != NULL; cmsgptr = WSA_CMSG_NXTHDR(&hdr, cmsgptr)) {
if (cmsgptr->cmsg_len < sizeof(WSACMSGHDR))
break;
cmsgptr->cmsg_type = convertCMsgTypePSP2Host(cmsgptr->cmsg_type, cmsgptr->cmsg_level);
cmsgptr->cmsg_level = convertSockoptLevelPSP2Host(cmsgptr->cmsg_level);
}
}
#else
hdr.msg_control = (char*)chdr; // (char*)pspCmsghdr;
hdr.msg_controllen = pspMsghdr->msg_controllen;
// Note: Many existing implementations of CMSG_FIRSTHDR never look at msg_controllen and just return the value of cmsg_control.
if (pspMsghdr->msg_controllen >= sizeof(InetCmsghdr)) {
for (cmsghdr* cmsgptr = CMSG_FIRSTHDR(&hdr); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&hdr, cmsgptr)) {
// Some CMSG_NXTHDR implementations return the same header again for a zero length.
if (cmsgptr->cmsg_len < sizeof(cmsghdr))
break;
cmsgptr->cmsg_type = convertCMsgTypePSP2Host(cmsgptr->cmsg_type, cmsgptr->cmsg_level);
cmsgptr->cmsg_level = convertSockoptLevelPSP2Host(cmsgptr->cmsg_level);
}
}
#endif
}
// Flags (ie. PSP_NET_INET_MSG_OOB) might need to be converted to be cross-platform
#if defined(_WIN32)
hdr.dwFlags = convertMSGFlagsPSP2Host(pspMsghdr->msg_flags & ~PSP_NET_INET_MSG_DONTWAIT) | MSG_NOSIGNAL;
#else
hdr.msg_flags = convertMSGFlagsPSP2Host(pspMsghdr->msg_flags & ~PSP_NET_INET_MSG_DONTWAIT) | MSG_NOSIGNAL;
#endif
unsigned long sent = 0;
bool isBcast = isBroadcastIP(saddr.in.sin_addr.s_addr);
// Broadcast/Multicast, use real broadcast/multicast if there is no one in peerlist
if (isBcast && getActivePeerCount() > 0) {
// Acquire Peer Lock
peerlock.lock();
SceNetAdhocctlPeerInfo* peer = friends;
for (; peer != NULL; peer = peer->next) {
// Does Skipping sending to timed out friends could cause desync when players moving group at the time MP game started?
if (peer->last_recv == 0)
};
int retval;
if (msg.msg_name && isBroadcastIP(saddr.in.sin_addr.s_addr) && getActivePeerCount() > 0) {
// To each ad hoc peer instead, as in sceNetInetSendto.
std::lock_guard<std::recursive_mutex> guard(peerlock);
for (SceNetAdhocctlPeerInfo *peer = friends; peer != nullptr; peer = peer->next) {
if (peer->last_recv == 0) {
continue;
}
saddr.in.sin_addr.s_addr = peer->ip_addr;
#if defined(_WIN32)
int result = WSASendMsg(inetSock->sock, &hdr, flgs | MSG_NOSIGNAL, &sent, NULL, NULL);
if (static_cast<int>(sent) > retval)
retval = sent;
#else
size_t result = sendmsg(inetSock->sock, &hdr, flgs | MSG_NOSIGNAL);
if (static_cast<int>(result) > retval)
retval = result;
#endif
if (retval != SOCKET_ERROR) {
DEBUG_LOG(Log::sceNet, "SendMsg(BC): Address = %s, Port = %d", ip2str(saddr.in.sin_addr).c_str(), ntohs(saddr.in.sin_port));
} else {
if (sendOnce() < 0) {
DEBUG_LOG(Log::sceNet, "SendMsg(BC): Socket error %d", socket_errno);
} else {
DEBUG_LOG(Log::sceNet, "SendMsg(BC): Address = %s, Port = %d", ip2str(saddr.in.sin_addr).c_str(), ntohs(saddr.in.sin_port));
}
}
// Free Peer Lock
peerlock.unlock();
// TODO: Calculate number of bytes supposed to be sent
retval = std::max(retval, 0); // Broadcast always success?
}
// Unicast or real broadcast/multicast
else {
// FIXME: On non-Windows(including PSP too?) broadcast to INADDR_BROADCAST(255.255.255.255) might not be received by the sender itself when binded to specific IP (ie. 192.168.0.2) or INADDR_BROADCAST.
// Meanwhile, it might be received by itself when binded to subnet (ie. 192.168.0.255) or INADDR_ANY(0.0.0.0).
/*if (isBcast) {
// TODO: Replace Broadcast with Multicast to be more consistent across platform
// Replace Limited Broadcast(255.255.255.255) with Direct Broadcast(ie. 192.168.0.255) for accurate targetting when there are multiple interfaces, to avoid receiving it's own broadcasted data through IP 169.254.x.x on Windows (which is not recognized as it's own IP by the game)
// Get Local IP Address
sockaddr_in sockAddr{};
getLocalIp(&sockAddr);
// Change the last number to 255 to indicate a common broadcast address (the accurate way should be: ip | (~subnetmask))
((u8*)&sockAddr.sin_addr.s_addr)[3] = 255;
saddr.in.sin_addr.s_addr = sockAddr.sin_addr.s_addr;
DEBUG_LOG(Log::sceNet, "SendMsg(BC): Address Replacement = %s", ip2str(saddr.in.sin_addr).c_str());
}*/
retval = 0;
for (const HostIovec &v : iov) {
#if defined(_WIN32)
int result = WSASendMsg(inetSock->sock, &hdr, flgs | MSG_NOSIGNAL, &sent, NULL, NULL);
if (result != SOCKET_ERROR)
retval = sent;
retval += (int)v.len;
#else
retval = sendmsg(inetSock->sock, &hdr, flgs | MSG_NOSIGNAL);
retval += (int)v.iov_len;
#endif
}
} else {
retval = sendOnce();
}
free(chdr);
free(iov);
/* // Example with 1 Msg buffer and without CMsg
msghdr msg;
iovec iov[1];
int buflen = pspMsghdr->msg_iovlen;
char* buf = (char*)malloc(buflen);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 1;
iov[0].iov_base = buf;
iov[0].iov_len = buflen;
retval = sendmsg(inetSock->sock, &msg, flags);
free(buf);
*/
if (retval < 0) {
const int hostErrno = socket_errno;
SetSendErrno(__KernelGetCurThread(), inetSock, hostErrno, __FUNCTION__);
@@ -1931,53 +1840,98 @@ static int sceNetInetSendmsg(int socket, u32 msghdrPtr, int flags) {
}
return hleLogError(Log::sceNet, retval);
}
return hleLogInfo(Log::sceNet, retval); // returns number of bytes sent?
return hleLogInfo(Log::sceNet, retval);
}
// Similar to POSIX's recvmsg or Mswsock's WSARecvMsg? Are their packets compatible one another?
// Games using this: World of Poker
static int sceNetInetRecvmsg(MIPSState *mips, int socket, u32 msghdrPtr, int flags) {
ERROR_LOG(Log::sceNet, "UNIMPL %s(%i, %08x, %08x) at %08x", __FUNCTION__, socket, msghdrPtr, flags, mips->pc);
// args: socket, msghdr, flags.
static InetTry TryRecvMsg(SceUID threadID, const u32 *args) {
InetSocket *inetSock;
if (!g_socketManager.GetInetSocket((int)args[0], &inetSock)) {
UpdateErrnoFromHost(threadID, EBADF, "sceNetInetRecvmsg");
return { -1, false, 0 };
}
InetMsghdr msg;
std::vector<HostIovec> iov;
if (!ReadInetMsghdr(args[1], &msg, &iov, "sceNetInetRecvmsg", threadID)) {
return { -1, false, 0 };
}
const int hostFlags = convertMSGFlagsPSP2Host(args[2] & ~PSP_NET_INET_MSG_DONTWAIT) | MSG_NOSIGNAL;
SockAddrIN4 saddr{};
int hostNameLen;
int outFlags = 0;
int retval;
#if defined(_WIN32)
DWORD received = 0;
DWORD wsaFlags = hostFlags;
INT fromLen = sizeof(saddr);
if (WSARecvFrom(inetSock->sock, iov.data(), (DWORD)iov.size(), &received, &wsaFlags, &saddr.addr, &fromLen, nullptr, nullptr) == SOCKET_ERROR) {
if (WSAGetLastError() == WSAEMSGSIZE) {
// A datagram too big for the buffers, which are filled.
retval = 0;
for (const HostIovec &v : iov) {
retval += (int)v.len;
}
outFlags = MSG_TRUNC;
} else {
retval = -1;
}
} else {
retval = (int)received;
}
hostNameLen = fromLen;
#else
msghdr hdr{};
hdr.msg_name = &saddr.addr;
hdr.msg_namelen = sizeof(saddr);
hdr.msg_iov = iov.data();
hdr.msg_iovlen = iov.size();
retval = (int)recvmsg(inetSock->sock, &hdr, hostFlags);
hostNameLen = (int)hdr.msg_namelen;
outFlags = hdr.msg_flags;
#endif
if (retval < 0) {
const int hostErrno = socket_errno;
if (IsWouldBlock(hostErrno)) {
return { retval, true, hostErrno };
}
UpdateErrnoFromHost(threadID, hostErrno, "sceNetInetRecvmsg");
return { retval, false, 0 };
}
InetMsghdr *pspMsghdr = (InetMsghdr *)Memory::GetPointerUnchecked(args[1]);
// TCP gets no address, and namelen 0. Nothing receives control data (pspautotests net/inet/msg).
if (msg.msg_name) {
if (inetSock->type != PSP_NET_INET_SOCK_STREAM && hostNameLen > 0) {
WriteSockaddrToPSP(saddr, msg.msg_name, args[1] + (u32)offsetof(InetMsghdr, msg_namelen));
} else {
pspMsghdr->msg_namelen = 0;
}
}
pspMsghdr->msg_controllen = 0;
pspMsghdr->msg_flags = convertMSGFlagsHost2PSP(outFlags & MSG_TRUNC);
return { retval, false, 0 };
}
// Games using this: World of Poker.
static int sceNetInetRecvmsg(MIPSState *mips, int socket, u32 msghdrPtr, int flags) {
InetSocket *inetSock;
if (!GetCallSocket(socket, &inetSock)) {
return InetBadSocket(socket, __FUNCTION__);
}
// Reference: http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch14lev1sec5.html
int retval = -1;
if (!InetValidRange(msghdrPtr, sizeof(InetMsghdr), __FUNCTION__)) {
return hleLogError(Log::sceNet, retval);
const u32 args[] = { (u32)socket, msghdrPtr, (u32)flags };
InetTry attempt = TryRecvMsg(__KernelGetCurThread(), args);
if (attempt.wouldBlock) {
if (StartInetWait(InetWaitOp::RecvMsg, args, ARRAY_SIZE(args), SocketWaitUs(inetSock, flags, false))) {
return hleLogDebug(Log::sceNet, 0, "waiting");
}
return hleLogDebug(Log::sceNet, InetWouldBlockResult(InetWaitOp::RecvMsg, __KernelGetCurThread(), args, attempt.hostErrno, __FUNCTION__), "EAGAIN");
}
InetMsghdr* pspMsghdr = (InetMsghdr*)Memory::GetPointerOrException(msghdrPtr);
int flgs = flags & ~PSP_NET_INET_MSG_DONTWAIT; // removing non-POSIX flag, which is an alternative way to use non-blocking mode
flgs = convertMSGFlagsPSP2Host(flgs);
SockAddrIN4 saddr{};
#if defined(_WIN32)
WSAMSG hdr;
WSACMSGHDR* chdr = NULL;
size_t iovecsize = sizeof(WSABUF);
WSABUF* iov = (WSABUF*)malloc(pspMsghdr->msg_iovlen * iovecsize);
#else
msghdr hdr;
cmsghdr* chdr = nullptr;
size_t iovecsize = sizeof(iovec);
iovec* iov = (iovec*)malloc(pspMsghdr->msg_iovlen * iovecsize);
#endif
if (iov == NULL) {
UpdateErrnoFromHost(__KernelGetCurThread(), ENOBUFS, __FUNCTION__);
return hleLogError(Log::sceNet, retval);
if (attempt.result < 0) {
return hleLogError(Log::sceNet, attempt.result);
}
memset(iov, 0, pspMsghdr->msg_iovlen * iovecsize);
memset(&hdr, 0, sizeof(hdr));
// TODO: Do similar to the already working sceNetInetSendmsg but in reverse
//if (pspMsghdr->msg_name != 0) { ... }
//UNFINISHED
free(iov);
return hleLogError(Log::sceNet, retval); // returns number of bytes received?
return hleLogInfo(Log::sceNet, attempt.result);
}
// TODO: fix retmasks
+1
View File
@@ -356,6 +356,7 @@ tests_good = [
"net/inet/blocking",
"net/inet/addr",
"net/inet/options",
"net/inet/msg",
"net/inet/limits",
"net/inet/polledge",
"misc/libc",