From fc452c0dfd9da2f67a489e1c80a9f6ceb398c9ba Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 11 Feb 2021 03:20:48 +0700 Subject: [PATCH 1/2] Fix frozen (0 FPS) issue on Kao Challengers and Asterix & Obelix XX (probably also fix other games from the same developer with similar issue) https://github.com/hrydgard/ppsspp/issues/14103 --- Core/HLE/proAdhoc.h | 2 ++ Core/HLE/sceNetAdhoc.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index ad2b1a626c40..cc0978932b52 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -66,6 +66,7 @@ #undef EISCONN #undef EALREADY #undef ETIMEDOUT +#undef EOPNOTSUPP #define errno WSAGetLastError() #define ESHUTDOWN WSAESHUTDOWN #define ECONNABORTED WSAECONNABORTED @@ -77,6 +78,7 @@ #define EISCONN WSAEISCONN #define EALREADY WSAEALREADY #define ETIMEDOUT WSAETIMEDOUT +#define EOPNOTSUPP WSAEOPNOTSUPP inline bool connectInProgress(int errcode){ return (errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEALREADY); } inline bool isDisconnected(int errcode) { return (errcode == WSAECONNRESET || errcode == WSAECONNABORTED || errcode == WSAESHUTDOWN); } #else diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index ad2ab98096de..0ec5695e4573 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -412,6 +412,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); + // On Windows: Using MSG_TRUNC can sometimes getting socket error WSAEOPNOTSUPP, may be the socket wasn't ready to received anything right after created & binded? int ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); int sockerr = errno; @@ -452,7 +453,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { result = 0; } // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound yet), may need to disable SIO_UDP_CONNRESET error - else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET) { + else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET || sockerr == EOPNOTSUPP) { u64 now = (u64)(time_now_d() * 1000000.0); if (req.timeout == 0 || now - req.startTime <= req.timeout) { // Try again later @@ -483,6 +484,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { } result = ERROR_NET_ADHOC_NOT_ENOUGH_SPACE; } + // FIXME: Blocking operation with infinite timeout(0) should never get a TIMEOUT error, right? May be we should return INVALID_ARG instead if it was infinite timeout (0)? else result = ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED @@ -1689,6 +1691,7 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * // Receive Data. PDP always sent in full size or nothing(failed), recvfrom will always receive in full size as requested (blocking) or failed (non-blocking). If available UDP data is larger than buffer, excess data is lost. // Should peek first for the available data size if it's more than len return ERROR_NET_ADHOC_NOT_ENOUGH_SPACE along with required size in len to prevent losing excess data + // On Windows: Using MSG_TRUNC can sometimes getting socket error WSAEOPNOTSUPP, may be the socket wasn't ready to received anything right after created & binded? received = recvfrom(pdpsocket.id, (char*)buf, *len, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); if (received != SOCKET_ERROR && *len < received) { WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, *len, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); @@ -1719,7 +1722,7 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * error = errno; // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET || error == EOPNOTSUPP)) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; From 0ce2c2c6e9842aa5b65edda7db35bdb7d35247db Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 11 Feb 2021 06:48:03 +0700 Subject: [PATCH 2/2] Windows recvfrom doesn't support MSG_TRUNC, so we're using dummy buffer with max size as alternative for peeking actual size of the next pending message. --- Core/HLE/proAdhoc.cpp | 9 +++------ Core/HLE/proAdhoc.h | 2 ++ Core/HLE/sceNet.cpp | 3 +++ Core/HLE/sceNetAdhoc.cpp | 17 +++++++++++------ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 0e2b957f5867..dda1f661436b 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -60,6 +60,8 @@ uint16_t portOffset; uint32_t minSocketTimeoutUS; uint32_t fakePoolSize = 0; SceNetAdhocMatchingContext * contexts = NULL; +char* dummyPeekBuf64k = NULL; +int dummyPeekBuf64kSize = 65536; int one = 1; bool friendFinderRunning = false; SceNetAdhocctlPeerInfo * friends = NULL; @@ -1908,12 +1910,7 @@ u_long getAvailToRecv(int sock, int udpBufferSize) { return 0; if (udpBufferSize > 0 && n > 0) { - // TODO: Cap number of bytes of full DGRAM message(s) up to buffer size - char buf[8]; - // Each recv can only received one message, not sure how to read the next pending msg without actually receiving the first one - err = recvfrom(sock, buf, sizeof(buf), MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, NULL, NULL); - if (err >= 0) - return (u_long)err; + // TODO: Cap number of bytes of full DGRAM message(s) up to buffer size, but may cause Warriors Orochi 2 to get FPS drops } return n; } diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index cc0978932b52..421759611cdb 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -917,6 +917,8 @@ extern int defaultWlanChannel; // Default WLAN Channel for Auto, JPCSP uses 11 extern uint32_t fakePoolSize; extern SceNetAdhocMatchingContext * contexts; +extern char* dummyPeekBuf64k; +extern int dummyPeekBuf64kSize; extern int one; extern bool friendFinderRunning; extern SceNetAdhocctlPeerInfo * friends; diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index b13aad410cfe..48a51688b32e 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -203,6 +203,7 @@ void __NetInit() { g_adhocServerIP.in.sin_port = htons(SERVER_PORT); //27312 // Maybe read this from config too g_adhocServerIP.in.sin_addr.s_addr = INADDR_NONE; + dummyPeekBuf64k = (char*)malloc(dummyPeekBuf64kSize); InitLocalhostIP(); SceNetEtherAddr mac; @@ -235,6 +236,8 @@ void __NetShutdown() { // Since PortManager supposed to be general purpose for whatever port forwarding PPSSPP needed, may be we shouldn't clear & restore ports in here? it will be cleared and restored by PortManager's destructor when exiting PPSSPP anyway __UPnPShutdown(); + + free(dummyPeekBuf64k); } static void __UpdateApctlHandlers(u32 oldState, u32 newState, u32 flag, u32 error) { diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 0ec5695e4573..0b9d71268eb6 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -412,9 +412,11 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); - // On Windows: Using MSG_TRUNC can sometimes getting socket error WSAEOPNOTSUPP, may be the socket wasn't ready to received anything right after created & binded? - int ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + int ret = recvfrom(uid, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); int sockerr = errno; + if (ret > 0 && *req.length > 0) + memcpy(req.buffer, dummyPeekBuf64k, std::min(ret, *req.length)); // Note: UDP must not be received partially, otherwise leftover data in socket's buffer will be discarded if (ret >= 0 && ret <= *req.length) { @@ -453,7 +455,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { result = 0; } // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound yet), may need to disable SIO_UDP_CONNRESET error - else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET || sockerr == EOPNOTSUPP) { + else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET) { u64 now = (u64)(time_now_d() * 1000000.0); if (req.timeout == 0 || now - req.startTime <= req.timeout) { // Try again later @@ -1691,8 +1693,11 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * // Receive Data. PDP always sent in full size or nothing(failed), recvfrom will always receive in full size as requested (blocking) or failed (non-blocking). If available UDP data is larger than buffer, excess data is lost. // Should peek first for the available data size if it's more than len return ERROR_NET_ADHOC_NOT_ENOUGH_SPACE along with required size in len to prevent losing excess data - // On Windows: Using MSG_TRUNC can sometimes getting socket error WSAEOPNOTSUPP, may be the socket wasn't ready to received anything right after created & binded? - received = recvfrom(pdpsocket.id, (char*)buf, *len, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + received = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); + if (received > 0 && *len > 0) + memcpy(buf, dummyPeekBuf64k, std::min(received, *len)); + if (received != SOCKET_ERROR && *len < received) { WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, *len, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); *len = received; @@ -1722,7 +1727,7 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * error = errno; // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET || error == EOPNOTSUPP)) { + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id;