From 155d3e047883dff843342e058413c4c2e0258bbe Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Tue, 15 Mar 2022 01:10:16 +0700 Subject: [PATCH 01/10] Ignore disconnected error on PtpFlush. Fixes disconnection issue on some games (ie. R-Type Command) --- Core/HLE/sceNetAdhoc.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 217245e46a..cbfeb7c79d 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -876,13 +876,6 @@ int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { else result = ERROR_NET_ADHOC_TIMEOUT; } - else if (isDisconnected(sockerr)) { - // Change Socket State. // FIXME: Does Alerted Socket should be closed too? - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - result = ERROR_NET_ADHOC_DISCONNECTED; - } if (sockerr != 0) { DEBUG_LOG(SCENET, "sceNetAdhocPtpFlush[%i]: Socket Error (%i)", req.id, sockerr); @@ -4262,13 +4255,6 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PTP_FLUSH, id, nullptr, nullptr, timeout, nullptr, nullptr, "ptp flush"); } - else if (isDisconnected(error)) { - // Change Socket State - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); - } if (error != 0) DEBUG_LOG(SCENET, "sceNetAdhocPtpFlush[%i:%u -> %s:%u]: Error:%i", id, ptpsocket.lport, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport, error); From 9209fb7c6e17c4be3ff16058be9042a11ff9dc90 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Tue, 15 Mar 2022 05:06:15 +0700 Subject: [PATCH 02/10] Shouldn't return as soon as possible when a blocking PtpConnect is getting ECONNREFUSED error, since it should be treated as ETIMEDOUT on non-Windows platform --- Core/HLE/sceNetAdhoc.cpp | 71 +++++++++++++++++++++++++++------------- Core/HLE/sceNetAdhoc.h | 1 + 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index cbfeb7c79d..11d6a802c2 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -795,7 +795,7 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeer) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -808,16 +808,35 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { return 0; } - int sockerr; - // Wait for Connection (assuming "connect" has been called before) - int ret = IsSocketReady(uid, false, true, &sockerr); + int sockerr, ret; + // Try to connect again if the first attempt failed due to remote side was not listening yet (ie. ECONNREFUSED or ETIMEDOUT) + if (sock->attemptCount < 1) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = targetPeer.peers[0].ip; + sin.sin_port = htons(ptpsocket.pport + targetPeer.peers[0].portOffset); + + // Try to Connect + int ret = connect(uid, (struct sockaddr*)&sin, sizeof(sin)); + int sockerr = errno; + if (connectInProgress(sockerr)) { + sock->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; + sock->attemptCount = 1; + } + } + // Check if the connection has completed (assuming "connect" has been called before) + ret = IsSocketReady(uid, false, true, &sockerr); // Connection is ready if (ret > 0) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); - getpeername(uid, (struct sockaddr*)&sin, &sinlen); + ret = getpeername(uid, (struct sockaddr*)&sin, &sinlen); + if (ret == SOCKET_ERROR) { + WARN_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: getpeername error %i", req.id, ptpsocket.lport, errno); + } // Set Connected State ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; @@ -838,7 +857,7 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { if (sock->nonblocking) result = ERROR_NET_ADHOC_WOULD_BLOCK; else - result = ERROR_NET_ADHOC_TIMEOUT; // FIXME: PSP never returned ERROR_NET_ADHOC_TIMEOUT on PtpConnect? or only returned ERROR_NET_ADHOC_TIMEOUT when the host is too busy? Seems to be returning ERROR_NET_ADHOC_CONNECTION_REFUSED on timedout instead (if the other side in not listening yet). + result = ERROR_NET_ADHOC_TIMEOUT; // FIXME: PSP never returned ERROR_NET_ADHOC_TIMEOUT on PtpConnect? or only returned ERROR_NET_ADHOC_TIMEOUT when the host is too busy? Seems to be returning ERROR_NET_ADHOC_CONNECTION_REFUSED on timedout instead (if the other side in not listening yet, which is similar to BSD). } } // Select was interrupted or contains invalid args? @@ -987,7 +1006,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_CONNECT: - if (DoBlockingPtpConnect(uid, req, result)) { + if (DoBlockingPtpConnect(uid, req, result, sendTargetPeers[userdata])) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -1565,7 +1584,8 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Get Peer IP. Some games (ie. Vulcanus Seek and Destroy) seems to try to send to zero-MAC (ie. 00:00:00:00:00:00) first before sending to the actual destination MAC.. So may be sending to zero-MAC has a special meaning? (ie. to peek send buffer availability may be?) if (resolveMAC((SceNetEtherAddr *)daddr, (uint32_t *)&target.sin_addr.s_addr)) { // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho - target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(target.sin_addr.s_addr)) ? 0 : portOffset)); + uint16_t finalPortOffset = ((isOriPort && !isPrivateIP(target.sin_addr.s_addr)) ? 0 : portOffset); + target.sin_port = htons(dport + finalPortOffset); // Acquire Network Lock //_acquireNetworkLock(); @@ -3358,6 +3378,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, changeBlockingMode(tcpsocket, 1); // Initiate PtpConnect (ie. The Warrior seems to try to PtpSend right after PtpOpen without trying to PtpConnect first) + // TODO: Need to handle ECONNREFUSED better on non-Windows, if there are games that never called PtpConnect and only relies on [blocking?] PtpOpen to get connected NetAdhocPtp_Connect(i + 1, rexmt_int, 1, false); // Workaround to give some time to get connected before returning from PtpOpen over high latency @@ -3648,7 +3669,8 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) // Grab Peer IP if (resolveMAC(&ptpsocket.paddr, (uint32_t*)&sin.sin_addr.s_addr)) { // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho - sin.sin_port = htons(ptpsocket.pport + ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset)); + uint16_t finalPortOffset = ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset); + sin.sin_port = htons(ptpsocket.pport + finalPortOffset); // Connect Socket to Peer // NOTE: Based on what i read at stackoverflow, The First Non-blocking POSIX connect will always returns EAGAIN/EWOULDBLOCK because it returns without waiting for ACK/handshake, But GvG Next Plus is treating non-blocking PtpConnect just like blocking connect, May be on a real PSP the first non-blocking sceNetAdhocPtpConnect can be successfull? @@ -3678,31 +3700,36 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) // Error handling else if (connectresult == SOCKET_ERROR) { - // Connection in Progress - if (connectInProgress(errorcode)) { - socket->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; - socket->attemptCount++; + // Connection in Progress, or + // ECONNREFUSED = No connection could be made because the target device actively refused it (on Windows/Linux/Android), or no one listening on the remote address (on Linux/Android) thus should try to connect again later (treated similarly to ETIMEDOUT/ENETUNREACH). + if (connectInProgress(errorcode) || errorcode == ECONNREFUSED || errorcode == ENETUNREACH) { + if (connectInProgress(errorcode)) + { + socket->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; + socket->attemptCount++; + } socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); // Blocking Mode // Workaround: Forcing first attempt to be blocking to prevent issue related to lobby or high latency networks. (can be useful for GvG Next Plus, Dissidia 012, and Fate Unlimited Codes) - if (!flag || (allowForcedConnect && g_Config.bForcedFirstConnect && socket->attemptCount == 1)) { + if (!flag || (allowForcedConnect && g_Config.bForcedFirstConnect && socket->attemptCount <= 1)) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + if (sendTargetPeers.find(threadSocketId) != sendTargetPeers.end()) { + DEBUG_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Socket(%d) is Busy!", id, ptpsocket.lport, ptpsocket.id); + return hleLogError(SCENET, ERROR_NET_ADHOC_BUSY, "busy?"); + } + + AdhocSendTargets dest = { 0, {}, false }; + dest.peers.push_back({ sin.sin_addr.s_addr, ptpsocket.pport, finalPortOffset }); + sendTargetPeers[threadSocketId] = dest; return WaitBlockingAdhocSocket(threadSocketId, PTP_CONNECT, id, nullptr, nullptr, (flag) ? std::max((int)socket->retry_interval, timeout) : timeout, nullptr, nullptr, "ptp connect"); } // NonBlocking Mode else { + // Returning WOULD_BLOCK as Workaround for ERROR_NET_ADHOC_CONNECTION_REFUSED to be more cross-platform, since there is no way to simulate ERROR_NET_ADHOC_CONNECTION_REFUSED properly on Windows return hleLogDebug(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); } } - // No connection could be made because the target device actively refused it (on Windows/Linux/Android), or no one listening on the remote address (on Linux/Android). - else if (errorcode == ECONNREFUSED) { - // Workaround for ERROR_NET_ADHOC_CONNECTION_REFUSED to be more cross-platform, since there is no way to simulate ERROR_NET_ADHOC_CONNECTION_REFUSED properly on Windows - if (flag) - return hleLogError(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "connection refused workaround"); - else - return hleLogError(SCENET, ERROR_NET_ADHOC_TIMEOUT, "connection refused workaround"); - } } } diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index 114e7cec72..e2f53efaf6 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -45,6 +45,7 @@ struct AdhocctlRequest { struct AdhocSendTarget { u32 ip; u16 port; // original port + u16 portOffset; // port offset specific for this target IP }; struct AdhocSendTargets { From 2b09f81ff89544d30d72335046b56184133bec49 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Wed, 16 Mar 2022 03:51:46 +0700 Subject: [PATCH 03/10] Initialize some socket-related structs to avoid unexpected behavior. (May fix Unknown Source Port issue) --- Core/HLE/proAdhoc.cpp | 8 ++++---- Core/HLE/sceNetAdhoc.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 54bc7695e1..05ef72df00 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1795,7 +1795,7 @@ int getLocalIp(sockaddr_in* SocketAddress) { #if !PPSSPP_PLATFORM(SWITCH) if (metasocket != (int)INVALID_SOCKET) { - struct sockaddr_in localAddr; + struct sockaddr_in localAddr {}; localAddr.sin_addr.s_addr = INADDR_ANY; socklen_t addrLen = sizeof(localAddr); int ret = getsockname((int)metasocket, (struct sockaddr*)&localAddr, &addrLen); @@ -1876,7 +1876,7 @@ int getLocalIp(sockaddr_in* SocketAddress) { } uint32_t getLocalIp(int sock) { - struct sockaddr_in localAddr; + struct sockaddr_in localAddr {}; localAddr.sin_addr.s_addr = INADDR_ANY; socklen_t addrLen = sizeof(localAddr); getsockname(sock, (struct sockaddr*)&localAddr, &addrLen); @@ -1887,7 +1887,7 @@ uint32_t getLocalIp(int sock) { } static std::vector> InitPrivateIPRanges() { - struct sockaddr_in saNet, saMask; + struct sockaddr_in saNet {}, saMask{}; std::vector> ip_ranges; if (1 == inet_pton(AF_INET, "192.168.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.255.0.0", &(saMask.sin_addr))) @@ -1934,7 +1934,7 @@ void getLocalMac(SceNetEtherAddr * addr){ } uint16_t getLocalPort(int sock) { - struct sockaddr_in localAddr; + struct sockaddr_in localAddr {}; localAddr.sin_port = 0; socklen_t addrLen = sizeof(localAddr); getsockname(sock, (struct sockaddr*)&localAddr, &addrLen); diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 11d6a802c2..989d0e0636 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -600,7 +600,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa bool retry = false; for (auto peer = targetPeers.peers.begin(); peer != targetPeers.peers.end(); ) { // Fill in Target Structure - struct sockaddr_in target; + struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_addr.s_addr = peer->ip; target.sin_port = htons(peer->port + ((isOriPort && !isPrivateIP(peer->ip)) ? 0 : portOffset)); @@ -1379,7 +1379,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f setUDPConnReset(usocket, false); // Binding Information for local Port - struct sockaddr_in addr; + struct sockaddr_in addr {}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; if (isLocalServer) { @@ -1577,7 +1577,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Single Target if (!isBroadcastMAC(daddr)) { // Fill in Target Structure - struct sockaddr_in target; + struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_port = htons(dport + portOffset); @@ -1687,7 +1687,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Iterate Peers for (auto peer : dest.peers) { // Fill in Target Structure - struct sockaddr_in target; + struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_addr.s_addr = peer.ip; target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(peer.ip)) ? 0 : portOffset)); @@ -2164,7 +2164,7 @@ int NetAdhocPdp_Delete(int id, int unknown) { // Valid Socket if (sock != NULL && sock->type == SOCK_PDP) { // Close Connection - struct linger sl; + struct linger sl {}; sl.l_onoff = 1; // non-zero value enables linger option in kernel sl.l_linger = 0; // timeout interval in seconds setsockopt(sock->data.pdp.id, SOL_SOCKET, SO_LINGER, (const char*)&sl, sizeof(sl)); @@ -3307,7 +3307,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, setSockNoDelay(tcpsocket, 1); // Binding Information for local Port - struct sockaddr_in addr; + struct sockaddr_in addr {}; // addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; @@ -3776,7 +3776,7 @@ int NetAdhocPtp_Close(int id, int unknown) { // Valid Socket if (socket != NULL && socket->type == SOCK_PTP) { // Close Connection - struct linger sl; + struct linger sl {}; sl.l_onoff = 1; // non-zero value enables linger option in kernel sl.l_linger = 0; // timeout interval in seconds setsockopt(socket->data.ptp.id, SOL_SOCKET, SO_LINGER, (const char*)&sl, sizeof(sl)); @@ -3892,7 +3892,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int setSockNoDelay(tcpsocket, 1); // Binding Information for local Port - struct sockaddr_in addr; + struct sockaddr_in addr {}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; if (isLocalServer) { From 5e739f32b2a58da2a458a595eaa1191df982b1ee Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Wed, 16 Mar 2022 05:21:45 +0700 Subject: [PATCH 04/10] Use reference when possible to avoid copying. --- Core/HLE/proAdhoc.cpp | 2 +- Core/HLE/sceNetAdhoc.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 05ef72df00..5e892dfc7b 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1906,7 +1906,7 @@ static std::vector> InitPrivateIPRanges() { bool isPrivateIP(uint32_t ip) { static const std::vector> ip_ranges = InitPrivateIPRanges(); - for (auto ipRange : ip_ranges) { + for (auto& ipRange : ip_ranges) { if ((ip & ipRange.second) == (ipRange.first & ipRange.second)) // We can just use ipRange.first directly if it's already correctly formatted return true; } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 989d0e0636..a6808fc473 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -1685,7 +1685,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Non-blocking else { // Iterate Peers - for (auto peer : dest.peers) { + for (auto& peer : dest.peers) { // Fill in Target Structure struct sockaddr_in target {}; target.sin_family = AF_INET; @@ -4492,7 +4492,7 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { gmuinfo = (GameModeUpdateInfo*)Memory::GetPointer(infoAddr); } - for (auto gma : replicaGameModeAreas) { + for (auto& gma : replicaGameModeAreas) { if (gma.id == id) { if (gma.data && gma.dataUpdated) { Memory::Memcpy(gma.addr, gma.data, gma.size); @@ -6421,7 +6421,7 @@ void sendAcceptPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * ma uint32_t siblingbuflen = 0; // Parent Mode - if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) siblingbuflen = sizeof(SceNetEtherAddr) * (countConnectedPeers(context) - 2); + if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) siblingbuflen = (u32)sizeof(SceNetEtherAddr) * (countConnectedPeers(context) - 2); // Sibling Count int siblingcount = siblingbuflen / sizeof(SceNetEtherAddr); From 267757a5e47c01ae5412b2ca54e971b74cb764c4 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Wed, 16 Mar 2022 05:12:25 +0700 Subject: [PATCH 05/10] Caches IP-specific Port Offset to avoid recalculating them. --- Core/HLE/proAdhoc.cpp | 12 ++++++++++-- Core/HLE/proAdhoc.h | 8 +++++--- Core/HLE/sceNetAdhoc.cpp | 16 ++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 5e892dfc7b..c1e26463b5 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -228,6 +228,8 @@ void addFriend(SceNetAdhocctlConnectPacketS2C * packet) { peer->nickname = packet->name; peer->mac_addr = packet->mac; peer->ip_addr = packet->ip; + // Calculate final IP-specific Port Offset + peer->port_offset = ((isOriPort && !isPrivateIP(peer->ip_addr)) ? 0 : portOffset); // Update TimeStamp peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); } @@ -248,6 +250,9 @@ void addFriend(SceNetAdhocctlConnectPacketS2C * packet) { // Save IP Address peer->ip_addr = packet->ip; + // Calculate final IP-specific Port Offset + peer->port_offset = ((isOriPort && !isPrivateIP(peer->ip_addr)) ? 0 : portOffset); + // TimeStamp peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); @@ -2286,7 +2291,7 @@ bool resolveIP(uint32_t ip, SceNetEtherAddr * mac) { return false; } -bool resolveMAC(SceNetEtherAddr * mac, uint32_t * ip) { +bool resolveMAC(SceNetEtherAddr* mac, uint32_t* ip, u16* port_offset) { // Get Local MAC Address SceNetEtherAddr localMac; getLocalMac(&localMac); @@ -2296,6 +2301,8 @@ bool resolveMAC(SceNetEtherAddr * mac, uint32_t * ip) { sockaddr_in sockAddr; getLocalIp(&sockAddr); *ip = sockAddr.sin_addr.s_addr; + if (port_offset) + *port_offset = portOffset; return true; // return succes } @@ -2311,7 +2318,8 @@ bool resolveMAC(SceNetEtherAddr * mac, uint32_t * ip) { if (isMacMatch(&peer->mac_addr, mac)) { // Copy Data *ip = peer->ip_addr; - + if (port_offset) + *port_offset = peer->port_offset; // Return Success return true; } diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 5b54492f0d..15b37675ba 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -294,11 +294,12 @@ typedef struct SceNetAdhocctlPeerInfo { SceNetAdhocctlPeerInfo * next; SceNetAdhocctlNickname nickname; SceNetEtherAddr mac_addr; - u16_le padding; + u16_le padding; // a copy of the padding(?) from SceNetAdhocctlPeerInfoEmu u32_le flags; u64_le last_recv; // Need to use the same method with sceKernelGetSystemTimeWide (ie. CoreTiming::GetGlobalTimeUsScaled) to prevent timing issue (ie. in game timeout) u32_le ip_addr; // internal use only + u16_le port_offset; // IP-specific port offset (internal use only) } PACK SceNetAdhocctlPeerInfo; // Peer Information with u32 pointers @@ -306,7 +307,7 @@ typedef struct SceNetAdhocctlPeerInfoEmu { u32_le next; // Changed the pointer to u32 SceNetAdhocctlNickname nickname; SceNetEtherAddr mac_addr; - u16_le padding; //00 00 + u16_le padding; //00 00 // Note: Not sure whether this is really padding or reserved/unknown field u32_le flags; //00 04 00 00 on KHBBS and FF FF FF FF on Ys vs. Sora no Kiseki // State of the peer? Or related to sceNetAdhocAuth_CF4D9BED ? u64_le last_recv; // Need to use the same method with sceKernelGetSystemTimeWide (ie. CoreTiming::GetGlobalTimeUsScaled) to prevent timing issue (ie. in game timeout) } PACK SceNetAdhocctlPeerInfoEmu; @@ -1448,9 +1449,10 @@ bool resolveIP(uint32_t ip, SceNetEtherAddr * mac); * Resolve MAC to IP * @param mac Peer MAC Address * @param ip OUT: Peer IP + * @param port_offset OUT: Peer IP-specific Port Offset * @return true on success */ -bool resolveMAC(SceNetEtherAddr * mac, uint32_t * ip); +bool resolveMAC(SceNetEtherAddr* mac, uint32_t* ip, u16* port_offset = nullptr); /** * Check whether Network Name contains only valid symbols diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index a6808fc473..88ef52c724 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -603,7 +603,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_addr.s_addr = peer->ip; - target.sin_port = htons(peer->port + ((isOriPort && !isPrivateIP(peer->ip)) ? 0 : portOffset)); + target.sin_port = htons(peer->port + peer->portOffset); int ret = sendto(pdpsocket.id, (const char*)req.buffer, targetPeers.length, MSG_NOSIGNAL, (struct sockaddr*)&target, sizeof(target)); int sockerr = errno; @@ -1580,11 +1580,11 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_port = htons(dport + portOffset); + u16 finalPortOffset; // Get Peer IP. Some games (ie. Vulcanus Seek and Destroy) seems to try to send to zero-MAC (ie. 00:00:00:00:00:00) first before sending to the actual destination MAC.. So may be sending to zero-MAC has a special meaning? (ie. to peek send buffer availability may be?) - if (resolveMAC((SceNetEtherAddr *)daddr, (uint32_t *)&target.sin_addr.s_addr)) { + if (resolveMAC((SceNetEtherAddr *)daddr, (uint32_t *)&target.sin_addr.s_addr, &finalPortOffset)) { // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho - uint16_t finalPortOffset = ((isOriPort && !isPrivateIP(target.sin_addr.s_addr)) ? 0 : portOffset); target.sin_port = htons(dport + finalPortOffset); // Acquire Network Lock @@ -1604,7 +1604,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int } AdhocSendTargets dest = { len, {}, false }; - dest.peers.push_back({ target.sin_addr.s_addr, dport }); + dest.peers.push_back({ target.sin_addr.s_addr, dport, finalPortOffset }); sendTargetPeers[threadSocketId] = dest; return WaitBlockingAdhocSocket(threadSocketId, PDP_SEND, id, data, nullptr, timeout, nullptr, nullptr, "pdp send"); } @@ -1665,7 +1665,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int if (peer->last_recv == 0) continue; - dest.peers.push_back({ peer->ip_addr, dport }); + dest.peers.push_back({ peer->ip_addr, dport, peer->port_offset }); } // Free Peer Lock peerlock.unlock(); @@ -1690,7 +1690,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int struct sockaddr_in target {}; target.sin_family = AF_INET; target.sin_addr.s_addr = peer.ip; - target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(peer.ip)) ? 0 : portOffset)); + target.sin_port = htons(dport + peer.portOffset); int sent = sendto(pdpsocket.id, (const char*)data, len, MSG_NOSIGNAL, (struct sockaddr*)&target, sizeof(target)); int error = errno; @@ -3665,11 +3665,11 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) // sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_port = htons(ptpsocket.pport + portOffset); + u16 finalPortOffset; // Grab Peer IP - if (resolveMAC(&ptpsocket.paddr, (uint32_t*)&sin.sin_addr.s_addr)) { + if (resolveMAC(&ptpsocket.paddr, (uint32_t*)&sin.sin_addr.s_addr, &finalPortOffset)) { // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho - uint16_t finalPortOffset = ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset); sin.sin_port = htons(ptpsocket.pport + finalPortOffset); // Connect Socket to Peer From 6b0cb78b3b4cc782e9f0875c60339490798ca666 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 17 Mar 2022 06:43:07 +0700 Subject: [PATCH 06/10] =?UTF-8?q?=EF=BB=BFRecreate=20the=20socket=20before?= =?UTF-8?q?=20attempting=20to=20connect=20again=20after=20ECONNREFUSED/ENE?= =?UTF-8?q?TUNREACH=20error,=20since=20reusing=20the=20socket=20only=20wor?= =?UTF-8?q?ks=20on=20Windows.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/HLE/proAdhoc.h | 3 + Core/HLE/sceNetAdhoc.cpp | 116 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 15b37675ba..8eb99d5278 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -61,6 +61,7 @@ #undef ECONNABORTED #undef ECONNRESET #undef ECONNREFUSED +#undef ENETUNREACH #undef ENOTCONN #undef EAGAIN #undef EINPROGRESS @@ -73,6 +74,7 @@ #define ECONNABORTED WSAECONNABORTED #define ECONNRESET WSAECONNRESET #define ECONNREFUSED WSAECONNREFUSED +#define ENETUNREACH WSAENETUNREACH #define ENOTCONN WSAENOTCONN #define EAGAIN WSAEWOULDBLOCK #define EINPROGRESS WSAEWOULDBLOCK @@ -395,6 +397,7 @@ typedef struct AdhocSocket { s32 retry_count; // multiply with retry interval to be used as keepalive timeout s32 attemptCount; // connect/accept attempts u64 lastAttempt; // timestamp to retry again + bool isClient; // true if the game is using local port 0 when creating the socket union { SceNetAdhocPdpStat pdp; SceNetAdhocPtpStat ptp; diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 88ef52c724..b8b5b9fae0 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -116,6 +116,7 @@ void sendBulkDataPacket(SceNetAdhocMatchingContext* context, SceNetEtherAddr* ma int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock); int FlushPtpSocket(int socketId); +int RecreatePtpSocket(int ptpId); int NetAdhocGameMode_DeleteMaster(); int NetAdhocctl_ExitGameMode(); int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect = true); @@ -824,6 +825,12 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSen sock->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; sock->attemptCount = 1; } + // On Windows you can call connect again using the same socket after ECONNREFUSED/ETIMEDOUT/ENETUNREACH error, but on non-Windows you'll need to recreate the socket first + else { + if (RecreatePtpSocket(req.id) < 0) { + WARN_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Failed to Recreate Socket", req.id, ptpsocket.lport); + } + } } // Check if the connection has completed (assuming "connect" has been called before) ret = IsSocketReady(uid, false, true, &sockerr); @@ -1342,6 +1349,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f // Library is initialized SceNetEtherAddr * saddr = (SceNetEtherAddr *)mac; + bool isClient = false; if (netAdhocInited) { // Valid Arguments are supplied if (mac != NULL && bufferSize > 0) { @@ -1352,7 +1360,10 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f } //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this - if (port == 0) port = -static_cast(portOffset); + if (port == 0) { + isClient = true; + port = -static_cast(portOffset); + } // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems :( So we try to fix it here if (saddr != NULL) { getLocalMac(saddr); @@ -1431,6 +1442,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f internal->type = SOCK_PDP; internal->nonblocking = flag; internal->buffer_size = bufferSize; + internal->isClient = isClient; // Fill in Data internal->data.pdp.id = usocket; @@ -3236,6 +3248,92 @@ static int sceNetAdhocGetPtpStat(u32 structSize, u32 structAddr) { } +int RecreatePtpSocket(int ptpId) { + auto sock = adhocSockets[ptpId - 1]; + if (!sock) { + return ERROR_NET_ADHOC_SOCKET_ID_NOT_AVAIL; + } + + // Close old socket + struct linger sl {}; + sl.l_onoff = 1; // non-zero value enables linger option in kernel + sl.l_linger = 0; // timeout interval in seconds + setsockopt(sock->data.ptp.id, SOL_SOCKET, SO_LINGER, (const char*)&sl, sizeof(sl)); + closesocket(sock->data.ptp.id); + + // Create a new socket + int tcpsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + // Valid Socket produced + if (tcpsocket > 0) { + sock->data.ptp.id = tcpsocket; + + // Change socket MSS + setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); + + // Change socket buffer size to be consistent on all platforms. + setSockBufferSize(tcpsocket, SO_SNDBUF, sock->buffer_size * 5); //PSP_ADHOC_PTP_MSS + setSockBufferSize(tcpsocket, SO_RCVBUF, sock->buffer_size * 10); //PSP_ADHOC_PTP_MSS*10 + + // Enable KeepAlive + setSockKeepAlive(tcpsocket, true, sock->retry_interval / 1000000L, sock->retry_count); + + // Ignore SIGPIPE when supported (ie. BSD/MacOS) + setSockNoSIGPIPE(tcpsocket, 1); + + // Enable Port Re-use + setSockReuseAddrPort(tcpsocket); + + // Apply Default Send Timeout Settings to Socket + setSockTimeout(tcpsocket, SO_SNDTIMEO, sock->retry_interval); + + // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? + setSockNoDelay(tcpsocket, 1); + + // Binding Information for local Port + struct sockaddr_in addr {}; + // addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + if (isLocalServer) { + getLocalIp(&addr); + } + uint16_t requestedport = static_cast(sock->data.ptp.lport + static_cast(portOffset)); + // Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0) + if (requestedport == 0 && sock->data.ptp.lport > 0) + requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536 + addr.sin_port = htons(requestedport); + + // Bound Socket to local Port + if (bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + ERROR_LOG(SCENET, "RecreatePtpSocket(%i) - Socket error (%i) when binding port %u", ptpId, errno, ntohs(addr.sin_port)); + } + else { + // Update sport with the port assigned internal->lport = ntohs(local.sin_port) + socklen_t len = sizeof(addr); + if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) { + uint16_t boundport = ntohs(addr.sin_port); + if (sock->data.ptp.lport + static_cast(portOffset) >= 65536 || static_cast(boundport) - static_cast(portOffset) <= 0) + WARN_LOG(SCENET, "RecreatePtpSocket(%id) - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", ptpId, sock->data.ptp.lport, requestedport, boundport, boundport - portOffset); + u16 newlport = boundport - portOffset; + if (newlport != sock->data.ptp.lport) { + WARN_LOG(SCENET, "RecreatePtpSocket(%id) - Old and New LPort is different! The port may need to be reforwarded"); + if (!sock->isClient) + UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? newlport : newlport + portOffset, newlport + portOffset); + } + sock->data.ptp.lport = newlport; + } + } + + // Switch to non-blocking for futher usage + changeBlockingMode(tcpsocket, 1); + } + else + return ERROR_NET_ADHOC_SOCKET_ID_NOT_AVAIL; + + return 0; +} + /** * Adhoc Emulator PTP Active Socket Creator * @param saddr Local MAC (Unused) @@ -3356,11 +3454,12 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, internal->retry_count = rexmt_cnt; internal->nonblocking = flag; internal->buffer_size = bufsize; + internal->isClient = isClient; // Copy Infrastructure Socket ID internal->data.ptp.id = tcpsocket; - // Copy Address Information + // Copy Address & Port Information internal->data.ptp.laddr = *saddr; internal->data.ptp.paddr = *daddr; internal->data.ptp.lport = sport; @@ -3468,6 +3567,8 @@ int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEther internal->attemptCount = 1; // Used to differentiate between closed state of disconnected socket and not connected yet. internal->retry_interval = socket->retry_interval; internal->retry_count = socket->retry_count; + internal->isClient = true; + // Enable KeepAlive setSockKeepAlive(newsocket, true, internal->retry_interval / 1000000L, internal->retry_count); @@ -3708,6 +3809,12 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) socket->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; socket->attemptCount++; } + // On Windows you can call connect again using the same socket after ECONNREFUSED/ETIMEDOUT/ENETUNREACH error, but on non-Windows you'll need to recreate the socket first + else { + if (RecreatePtpSocket(id) < 0) { + WARN_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Failed to Recreate Socket", id, ptpsocket.lport); + } + } socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); // Blocking Mode // Workaround: Forcing first attempt to be blocking to prevent issue related to lobby or high latency networks. (can be useful for GvG Next Plus, Dissidia 012, and Fate Unlimited Codes) @@ -3842,6 +3949,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int } // Library is initialized SceNetEtherAddr * saddr = (SceNetEtherAddr *)srcmac; + bool isClient = false; if (netAdhocInited) { // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems :( So we try to fix it here if (saddr != NULL) { @@ -3857,6 +3965,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Random Port required if (sport == 0) { + isClient = true; //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this sport = -static_cast(portOffset); } @@ -3942,11 +4051,12 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int internal->retry_count = rexmt_cnt; internal->nonblocking = flag; internal->buffer_size = bufsize; + internal->isClient = isClient; // Copy Infrastructure Socket ID internal->data.ptp.id = tcpsocket; - // Copy Address Information + // Copy Address & Port Information internal->data.ptp.laddr = *saddr; internal->data.ptp.lport = sport; From 65a90a2ec04e5a67f4ea7a6ed0e4f8b26d8481c0 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 17 Mar 2022 07:36:40 +0700 Subject: [PATCH 07/10] Use the most recent posix socket id during blocking simulation, since there is a possibility for the socket id to changes after recreated. --- Core/HLE/sceNetAdhoc.cpp | 65 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index b8b5b9fae0..88d8551eb4 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -460,12 +460,13 @@ int StartGameModeScheduler() { return 0; } -int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPdpRecv(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; return 0; } + auto& pdpsocket = sock->data.pdp; if (sock->flags & ADHOC_F_ALERTRECV) { result = ERROR_NET_ADHOC_SOCKET_ALERTED; sock->alerted_flags |= ADHOC_F_ALERTRECV; @@ -482,7 +483,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { memset(&sin, 0, sinlen); // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative - ret = recvfrom(uid, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + ret = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); sockerr = errno; // Discard packets from IP that can't be translated into MAC address to prevent confusing the game, since the sender MAC won't be updated and may contains invalid/undefined value. @@ -494,7 +495,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { // Remove the packet from socket buffer sinlen = sizeof(sin); memset(&sin, 0, sinlen); - recvfrom(uid, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); // Try again later, until timeout reached u64 now = (u64)(time_now_d() * 1000000.0); if (req.timeout != 0 && now - req.startTime > req.timeout) { @@ -514,11 +515,11 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { if (ret >= 0 && ret <= *req.length) { sinlen = sizeof(sin); memset(&sin, 0, sinlen); - ret = recvfrom(uid, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); + ret = recvfrom(pdpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL, (struct sockaddr*)&sin, &sinlen); // UDP can also receives 0 data, while on TCP receiving 0 data = connection gracefully closed, but not sure whether PDP can send/recv 0 data or not tho *req.length = 0; if (ret >= 0) { - DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(uid), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); // Find Peer MAC if (resolveIP(sin.sin_addr.s_addr, &mac)) { @@ -540,7 +541,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { *req.length = ret; *req.remotePort = ntohs(sin.sin_port) - portOffset; - WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", req.id, getLocalPort(uid), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %i bytes from Unknown Peer %s:%u", req.id, getLocalPort(pdpsocket.id), ret, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); } } result = 0; @@ -557,7 +558,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { } // Returning required buffer size when available data in recv buffer is larger than provided buffer size else if (ret > *req.length) { - WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", req.id, getLocalPort(uid), ret, *req.length, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); + WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, *req.length, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port)); *req.length = ret; // Find Peer MAC @@ -584,7 +585,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { +int DoBlockingPdpSend(AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -610,7 +611,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa int sockerr = errno; if (ret >= 0) { - DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](B): Sent %u bytes to %s:%u\n", uid, getLocalPort(pdpsocket.id), ret, ip2str(target.sin_addr).c_str(), ntohs(target.sin_port)); + DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](B): Sent %u bytes to %s:%u\n", req.id, getLocalPort(pdpsocket.id), ret, ip2str(target.sin_addr).c_str(), ntohs(target.sin_port)); // Remove successfully sent to peer to prevent sending the same data again during a retry peer = targetPeers.peers.erase(peer); } @@ -628,7 +629,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa } if (ret == SOCKET_ERROR) - DEBUG_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u](B) [size=%i]", sockerr, uid, getLocalPort(pdpsocket.id), ntohs(target.sin_port), targetPeers.length); + DEBUG_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u](B) [size=%i]", sockerr, req.id, getLocalPort(pdpsocket.id), ntohs(target.sin_port), targetPeers.length); } if (retry) @@ -637,7 +638,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa return 0; } -int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPtpSend(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -651,7 +652,7 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { } // Send Data - int ret = send(uid, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); + int ret = send(ptpsocket.id, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); int sockerr = errno; // Success @@ -690,7 +691,7 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPtpRecv(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -703,7 +704,7 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { return 0; } - int ret = recv(uid, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL); + int ret = recv(ptpsocket.id, (char*)req.buffer, std::max(0, *req.length), MSG_NOSIGNAL); int sockerr = errno; // Received Data. POSIX: May received 0 bytes when the remote peer already closed the connection. @@ -746,7 +747,7 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPtpAccept(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -765,10 +766,10 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { int ret, sockerr; // Check if listening socket is ready to accept - ret = IsSocketReady(uid, true, false, &sockerr); + ret = IsSocketReady(ptpsocket.id, true, false, &sockerr); if (ret > 0) { // Accept Connection - ret = accept(uid, (struct sockaddr*)&sin, &sinlen); + ret = accept(ptpsocket.id, (struct sockaddr*)&sin, &sinlen); sockerr = errno; } @@ -796,7 +797,7 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeer) { +int DoBlockingPtpConnect(AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeer) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -819,7 +820,7 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSen sin.sin_port = htons(ptpsocket.pport + targetPeer.peers[0].portOffset); // Try to Connect - int ret = connect(uid, (struct sockaddr*)&sin, sizeof(sin)); + int ret = connect(ptpsocket.id, (struct sockaddr*)&sin, sizeof(sin)); int sockerr = errno; if (connectInProgress(sockerr)) { sock->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; @@ -833,14 +834,14 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSen } } // Check if the connection has completed (assuming "connect" has been called before) - ret = IsSocketReady(uid, false, true, &sockerr); + ret = IsSocketReady(ptpsocket.id, false, true, &sockerr); // Connection is ready if (ret > 0) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); - ret = getpeername(uid, (struct sockaddr*)&sin, &sinlen); + ret = getpeername(ptpsocket.id, (struct sockaddr*)&sin, &sinlen); if (ret == SOCKET_ERROR) { WARN_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: getpeername error %i", req.id, ptpsocket.lport, errno); } @@ -877,7 +878,7 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result, AdhocSen return 0; } -int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingPtpFlush(AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; if (!sock) { result = ERROR_NET_ADHOC_SOCKET_DELETED; @@ -891,7 +892,7 @@ int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { } // Try Sending Empty Data - int sockerr = FlushPtpSocket(uid); + int sockerr = FlushPtpSocket(ptpsocket.id); result = 0; if (sockerr == EAGAIN || sockerr == EWOULDBLOCK) { @@ -910,7 +911,7 @@ int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { return 0; } -int DoBlockingAdhocPollSocket(int uid, AdhocSocketRequest& req, s64& result) { +int DoBlockingAdhocPollSocket(AdhocSocketRequest& req, s64& result) { SceNetAdhocPollSd* sds = (SceNetAdhocPollSd*)req.buffer; int ret = PollAdhocSocket(sds, req.id, 0, 0); if (ret <= 0) { @@ -972,7 +973,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { result = 0; break; } - if (DoBlockingPdpSend(uid, req, result, sendTargetPeers[userdata])) { + if (DoBlockingPdpSend(req, result, sendTargetPeers[userdata])) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -981,7 +982,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PDP_RECV: - if (DoBlockingPdpRecv(uid, req, result)) { + if (DoBlockingPdpRecv(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -989,7 +990,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_SEND: - if (DoBlockingPtpSend(uid, req, result)) { + if (DoBlockingPtpSend(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -997,7 +998,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_RECV: - if (DoBlockingPtpRecv(uid, req, result)) { + if (DoBlockingPtpRecv(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -1005,7 +1006,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_ACCEPT: - if (DoBlockingPtpAccept(uid, req, result)) { + if (DoBlockingPtpAccept(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -1013,7 +1014,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_CONNECT: - if (DoBlockingPtpConnect(uid, req, result, sendTargetPeers[userdata])) { + if (DoBlockingPtpConnect(req, result, sendTargetPeers[userdata])) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -1021,7 +1022,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case PTP_FLUSH: - if (DoBlockingPtpFlush(uid, req, result)) { + if (DoBlockingPtpFlush(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; @@ -1029,7 +1030,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; case ADHOC_POLL_SOCKET: - if (DoBlockingAdhocPollSocket(uid, req, result)) { + if (DoBlockingAdhocPollSocket(req, result)) { // Try again in another 0.5ms until data available or timedout. CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); return; From 86315fed3de0fa4c9034cb0d196a1d439359fde0 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 24 Mar 2022 07:24:09 +0700 Subject: [PATCH 08/10] Updated getLocalIp function to detects local IP better. --- Core/HLE/proAdhoc.cpp | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index c1e26463b5..697b86818c 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1804,7 +1804,8 @@ int getLocalIp(sockaddr_in* SocketAddress) { localAddr.sin_addr.s_addr = INADDR_ANY; socklen_t addrLen = sizeof(localAddr); int ret = getsockname((int)metasocket, (struct sockaddr*)&localAddr, &addrLen); - if (SOCKET_ERROR != ret) { + // Note: Sometimes metasocket still contains a valid socket fd right after failed to connect to AdhocServer on a different thread, thus ended with 0.0.0.0 here + if (SOCKET_ERROR != ret && localAddr.sin_addr.s_addr != 0) { SocketAddress->sin_addr = localAddr.sin_addr; return 0; } @@ -1812,27 +1813,8 @@ int getLocalIp(sockaddr_in* SocketAddress) { #endif // !PPSSPP_PLATFORM(SWITCH) // Fallback if not connected to AdhocServer -#if defined(_WIN32) - // Get local host name - char szHostName[256] = ""; - - if (::gethostname(szHostName, sizeof(szHostName))) { - // Error handling - } - // Get local network IP addresses (LAN/VPN/loopback) - struct addrinfo hints, * res = 0; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; // AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_ADDRCONFIG; // getaddrinfo with AI_ADDRCONFIG will fail when there is no local network connected? https://github.com/stephane/libmodbus/issues/575 - // Note: getaddrinfo could cause freezes on Android if there is no network https://github.com/hrydgard/ppsspp/issues/13300 - if (getaddrinfo(szHostName, NULL, &hints, &res) == 0 && res != NULL) { - memcpy(&SocketAddress->sin_addr, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(SocketAddress->sin_addr)); - freeaddrinfo(res); - return 0; - } - -#elif defined(getifaddrs) // On Android: Requires __ANDROID_API__ >= 24 +// getifaddrs first appeared in glibc 2.3, On Android officially supported since __ANDROID_API__ >= 24 +#if defined(_IFADDRS_H_) || (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3) || (__ANDROID_API__ >= 24) struct ifaddrs* ifAddrStruct = NULL; struct ifaddrs* ifa = NULL; @@ -1857,15 +1839,16 @@ int getLocalIp(sockaddr_in* SocketAddress) { if (sock != SOCKET_ERROR) { const char* kGoogleDnsIp = "8.8.8.8"; // Needs to be an IP string so it can be resolved as fast as possible to IP, doesn't need to be reachable uint16_t kDnsPort = 53; - struct sockaddr_in serv; - memset(&serv, 0, sizeof(serv)); + struct sockaddr_in serv {}; + u32 ipv4 = INADDR_NONE; // inet_addr(kGoogleDnsIp); // deprecated? + inet_pton(AF_INET, kGoogleDnsIp, &ipv4); serv.sin_family = AF_INET; - serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp); + serv.sin_addr.s_addr = ipv4; serv.sin_port = htons(kDnsPort); - int err = connect(sock, (struct sockaddr*)&serv, sizeof(serv)); + int err = connect(sock, (struct sockaddr*)&serv, sizeof(serv)); // connect should succeed even with SOCK_DGRAM if (err != SOCKET_ERROR) { - struct sockaddr_in name; + struct sockaddr_in name {}; socklen_t namelen = sizeof(name); err = getsockname(sock, (struct sockaddr*)&name, &namelen); if (err != SOCKET_ERROR) { From d5152752d23d90abf516a764de4a286c8a77871f Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 24 Mar 2022 08:12:27 +0700 Subject: [PATCH 09/10] Added a function to get SO_ERROR on a socket. --- Core/HLE/proAdhoc.cpp | 9 +++++++++ Core/HLE/proAdhoc.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 697b86818c..7c56f92e16 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1989,6 +1989,15 @@ int setSockTimeout(int sock, int opt, unsigned long timeout_usec) { // opt = SO_ return setsockopt(sock, SOL_SOCKET, opt, (char*)&optval, sizeof(optval)); } +int getSockError(int sock) { + int result = 0; + socklen_t result_len = sizeof(result); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&result, &result_len) < 0) { + result = errno; + } + return result; +} + int getSockNoDelay(int tcpsock) { int opt = 0; socklen_t optlen = sizeof(opt); diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 8eb99d5278..2e44d893cb 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -1340,6 +1340,11 @@ int setSockMSS(int sock, int size); */ int setSockTimeout(int sock, int opt, unsigned long timeout_usec); +/* + * Get Socket SO_ERROR (Requests and clears pending error information on the socket) + */ +int getSockError(int sock); + /* * Get TCP Socket TCP_NODELAY (Nagle Algo) */ From 1a6c889906643a0e9e9e63e4189e8dbc876eec8a Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Thu, 24 Mar 2022 08:17:15 +0700 Subject: [PATCH 10/10] Increase the timeout when the connection to the AdhocServer is already in progress, avoids cutting off the connection if it took longer than expected but almost completed. --- Core/HLE/proAdhoc.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 7c56f92e16..a1f27f1a7a 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -2192,15 +2192,16 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ // Don't need to connect if AdhocServer DNS was not resolved if (g_adhocServerIP.in.sin_addr.s_addr == INADDR_NONE) - return -1; + return SOCKET_ERROR; // Don't need to connect if AdhocServer IP is the same with this instance localhost IP and having AdhocServer disabled if (g_adhocServerIP.in.sin_addr.s_addr == g_localhostIP.in.sin_addr.s_addr && !g_Config.bEnableAdhocServer) - return -1; + return SOCKET_ERROR; // Connect to Adhoc Server int errorcode = 0; int cnt = 0; + DEBUG_LOG(SCENET, "InitNetwork: Connecting to AdhocServer"); iResult = connect((int)metasocket, &g_adhocServerIP.addr, sizeof(g_adhocServerIP)); errorcode = errno; @@ -2208,8 +2209,10 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ u64 startTime = (u64)(time_now_d() * 1000000.0); while (IsSocketReady((int)metasocket, false, true) <= 0) { u64 now = (u64)(time_now_d() * 1000000.0); - if (coreState == CORE_POWERDOWN) return iResult; - if (now - startTime > adhocDefaultTimeout) break; + if (coreState == CORE_POWERDOWN) + return iResult; + if (now - startTime > (getSockError((int)metasocket) == EINPROGRESS ? adhocDefaultTimeout*2LL: adhocDefaultTimeout)) + break; sleep_ms(10); } if (IsSocketReady((int)metasocket, false, true) <= 0) { @@ -2230,6 +2233,7 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ memcpy(packet.game.data, adhoc_id->data, ADHOCCTL_ADHOCID_LEN); IsSocketReady((int)metasocket, false, true, nullptr, adhocDefaultTimeout); + DEBUG_LOG(SCENET, "InitNetwork: Sending LOGIN OPCODE %d", packet.base.opcode); int sent = send((int)metasocket, (char*)&packet, sizeof(packet), MSG_NOSIGNAL); if (sent > 0) { socklen_t addrLen = sizeof(LocalIP);