From a05da1a8c516a1bc6809e40f947d38633be7fdc8 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Wed, 26 Aug 2020 17:17:31 +0700 Subject: [PATCH] Fix communication issue with Adhoc Server where ping to the Adhoc Server sometimes getting socket error 10053 and disconnected from Adhoc Server --- Core/HLE/proAdhoc.cpp | 153 ++++++++++++++++++------------- Core/HLE/proAdhoc.h | 8 +- Core/HLE/sceNetAdhoc.cpp | 192 ++++++++++++++++++++++++--------------- Core/HLE/sceNetAdhoc.h | 7 +- 4 files changed, 219 insertions(+), 141 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 8458b2c9f1..a96fce131f 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -43,6 +43,7 @@ #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelMemory.h" +#include "Core/HLE/sceNetAdhoc.h" #include "Core/Instance.h" #include "proAdhoc.h" @@ -212,13 +213,26 @@ SceNetAdhocctlPeerInfo * findFriend(SceNetEtherAddr * MAC) { return peer; } -int getNonBlockingFlag(int fd) { -#ifdef _WIN32 - return 0; -#else - int sockflag = fcntl(fd, F_GETFL, O_NONBLOCK); - return sockflag & O_NONBLOCK; -#endif +int IsSocketReady(int fd, bool readfd, bool writefd, int* errorcode, int timeoutUS) { + fd_set readfds, writefds; + timeval tval; + + FD_ZERO(&readfds); + writefds = readfds; + if (readfd) { + FD_SET(fd, &readfds); + } + if (writefd) { + FD_SET(fd, &writefds); + } + tval.tv_sec = timeoutUS / 1000000; + tval.tv_usec = timeoutUS % 1000000; + + int ret = select(fd + 1, &readfds, &writefds, nullptr, &tval); + if (errorcode != nullptr) + *errorcode = errno; + + return ret; } void changeBlockingMode(int fd, int nonblocking) { @@ -1186,14 +1200,14 @@ void sendChat(std::string chatString) { message = chatString.substr(0, 60); // 64 return chat variable corrupted is it out of memory? strcpy(chat.message, message.c_str()); //Send Chat Messages - changeBlockingMode(metasocket, 0); - int chatResult = send(metasocket, (const char *)&chat, sizeof(chat), 0); - changeBlockingMode(metasocket, 1); - NOTICE_LOG(SCENET, "Send Chat %s to Adhoc Server", chat.message); - name = g_Config.sNickName.c_str(); - chatLog.push_back(name.substr(0, 8) + ": " + chat.message); - if (chatScreenVisible) { - updateChatScreen = true; + if (IsSocketReady(metasocket, false, true) > 0) { + int chatResult = send(metasocket, (const char*)&chat, sizeof(chat), 0); + NOTICE_LOG(SCENET, "Send Chat %s to Adhoc Server", chat.message); + name = g_Config.sNickName.c_str(); + chatLog.push_back(name.substr(0, 8) + ": " + chat.message); + if (chatScreenVisible) { + updateChatScreen = true; + } } } } @@ -1242,49 +1256,66 @@ int friendFinder(){ // Reconnect when disconnected while Adhocctl is still inited if (metasocket == (int)INVALID_SOCKET && netAdhocctlInited) { - if (g_Config.bEnableWlan && initNetwork(&product_code) == 0) { - networkInited = true; - } - } - - if (networkInited) { - // Ping Server - now = real_time_now() * 1000000.0; // Use real_time_now()*1000000.0 instead of CoreTiming::GetGlobalTimeUsScaled() if the game gets disconnected from AdhocServer too soon when FPS wasn't stable - if (now - lastping >= PSP_ADHOCCTL_PING_TIMEOUT) { // We may need to use lower interval to prevent getting timeout at Pro Adhoc Server through internet - // original code : ((sceKernelGetSystemTimeWide() - lastping) >= ADHOCCTL_PING_TIMEOUT) - // Update Ping Time - lastping = now; - - // Prepare Packet - uint8_t opcode = OPCODE_PING; - - // Send Ping to Server, may failed with socket error 10054/10053 if someone else with the same IP already connected to AdHoc Server (the server might need to be modified to differentiate MAC instead of IP) - changeBlockingMode(metasocket, 0); - int iResult = send(metasocket, (const char*)&opcode, 1, 0); - changeBlockingMode(metasocket, 1); - if (iResult == SOCKET_ERROR) { - ERROR_LOG(SCENET, "FriendFinder: Socket Error (%i) when sending OPCODE_PING", errno); + if (g_Config.bEnableWlan) { + if (initNetwork(&product_code) == 0) { + networkInited = true; + INFO_LOG(SCENET, "FriendFinder: Network [RE]Initialized"); + } + else { networkInited = false; shutdown(metasocket, SD_BOTH); closesocket(metasocket); metasocket = (int)INVALID_SOCKET; } } + } - // Wait for Incoming Data - int received = recv(metasocket, (char*)(rx + rxpos), sizeof(rx) - rxpos, 0); + if (networkInited) { + // Ping Server + now = real_time_now() * 1000000.0; // Use real_time_now()*1000000.0 instead of CoreTiming::GetGlobalTimeUsScaled() if the game gets disconnected from AdhocServer too soon when FPS wasn't stable + // original code : ((sceKernelGetSystemTimeWide() - lastping) >= ADHOCCTL_PING_TIMEOUT) + if (now - lastping >= PSP_ADHOCCTL_PING_TIMEOUT) { // We may need to use lower interval to prevent getting timeout at Pro Adhoc Server through internet + // Prepare Packet + uint8_t opcode = OPCODE_PING; - // Free Network Lock - //_freeNetworkLock(); + // Send Ping to Server, may failed with socket error 10054/10053 if someone else with the same IP already connected to AdHoc Server (the server might need to be modified to differentiate MAC instead of IP) + if (IsSocketReady(metasocket, false, true) > 0) { + int iResult = send(metasocket, (const char*)&opcode, 1, 0); + int error = errno; + // KHBBS seems to be getting error 10053 often + if (iResult == SOCKET_ERROR) { + ERROR_LOG(SCENET, "FriendFinder: Socket Error (%i) when sending OPCODE_PING", error); + if (error != EAGAIN && error != EWOULDBLOCK) { + networkInited = false; + shutdown(metasocket, SD_BOTH); + closesocket(metasocket); + metasocket = (int)INVALID_SOCKET; + } + } + else { + // Update Ping Time + lastping = now; + DEBUG_LOG(SCENET, "FriendFinder: Sending OPCODE_PING (%llu)", now); + } + } + } - // Received Data - if (received > 0) { - // Fix Position - rxpos += received; + // Check for Incoming Data + if (IsSocketReady(metasocket, true, false) > 0) { + int received = recv(metasocket, (char*)(rx + rxpos), sizeof(rx) - rxpos, 0); - // Log Incoming Traffic - //printf("Received %d Bytes of Data from Server\n", received); - INFO_LOG(SCENET, "Received %d Bytes of Data from Adhoc Server", received); + // Free Network Lock + //_freeNetworkLock(); + + // Received Data + if (received > 0) { + // Fix Position + rxpos += received; + + // Log Incoming Traffic + //printf("Received %d Bytes of Data from Server\n", received); + INFO_LOG(SCENET, "Received %d Bytes of Data from Adhoc Server", received); + } } // Handle Packets @@ -1297,6 +1328,9 @@ int friendFinder(){ SceNetAdhocctlConnectBSSIDPacketS2C* packet = (SceNetAdhocctlConnectBSSIDPacketS2C*)rx; INFO_LOG(SCENET, "FriendFinder: Incoming OPCODE_CONNECT_BSSID [%s]", mac2str(&packet->mac).c_str()); + // Update User BSSID + parameter.bssid.mac_addr = packet->mac; // This packet seems to contains Adhoc Group Creator's BSSID (similar to AP's BSSID) so it shouldn't get mixed up with local MAC address + // From JPCSP: Some games have problems when the PSP_ADHOCCTL_EVENT_CONNECTED is sent too quickly after connecting to a network. The connection will be set CONNECTED with a small delay (200ms or 200us?) /*if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { setState(ADHOCCTL_STATE_GAMEMODE); @@ -1307,8 +1341,6 @@ int friendFinder(){ notifyAdhocctlHandlers(ADHOCCTL_EVENT_CONNECT, 0); }*/ - // Update User BSSID - parameter.bssid.mac_addr = packet->mac; // This packet seems to contains Adhoc Group Creator's BSSID (similar to AP's BSSID) so it shouldn't get mixed up with local MAC address // Notify Event Handlers notifyAdhocctlHandlers(ADHOCCTL_EVENT_CONNECT, 0); // Change State @@ -1576,9 +1608,7 @@ int getLocalIp(sockaddr_in* SocketAddress) { struct sockaddr_in localAddr; localAddr.sin_addr.s_addr = INADDR_ANY; socklen_t addrLen = sizeof(localAddr); - changeBlockingMode(metasocket, 0); int ret = getsockname(metasocket, (struct sockaddr*)&localAddr, &addrLen); - changeBlockingMode(metasocket, 1); if (SOCKET_ERROR != ret) { if (isLocalServer) { localAddr.sin_addr = g_localhostIP.in.sin_addr; @@ -1858,6 +1888,10 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ return SOCKET_ERROR; } setSockKeepAlive(metasocket, true); + // Disable Nagle Algo to prevent delaying small packets + setSockNoDelay(metasocket, 1); + // Switch to Nonblocking Behaviour + changeBlockingMode(metasocket, 1); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; @@ -1914,17 +1948,14 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ product_code.type = adhoc_id->type; memcpy(product_code.data, adhoc_id->data, ADHOCCTL_ADHOCID_LEN); - // Switch to Nonblocking Behaviour - changeBlockingMode(metasocket, 1); // Connect to Adhoc Server server_addr.sin_addr = serverIp; int errorcode = 0; int cnt = 0; - while ((iResult = connect(metasocket, (sockaddr*)&server_addr, sizeof(server_addr))) == SOCKET_ERROR && (errorcode = errno) != EISCONN && cnt < adhocDefaultTimeout) { - sleep_ms(1); - cnt++; - } - if (iResult == SOCKET_ERROR && errorcode != EISCONN) { + iResult = connect(metasocket, (sockaddr*)&server_addr, sizeof(server_addr)); + errorcode = errno; + + if (iResult == SOCKET_ERROR && errorcode != EISCONN && (IsSocketReady(metasocket, true, true, nullptr, adhocDefaultTimeout * 1000) <= 0)) { char buffer[512]; snprintf(buffer, sizeof(buffer), "Socket error (%i) when connecting to AdhocServer [%s/%s:%u]", errorcode, g_Config.proAdhocServer.c_str(), inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port)); ERROR_LOG(SCENET, "%s", buffer); @@ -1941,9 +1972,9 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ strncpy((char *)&packet.name.data, g_Config.sNickName.c_str(), ADHOCCTL_NICKNAME_LEN); packet.name.data[ADHOCCTL_NICKNAME_LEN - 1] = 0; memcpy(packet.game.data, adhoc_id->data, ADHOCCTL_ADHOCID_LEN); - changeBlockingMode(metasocket, 0); + + IsSocketReady(metasocket, false, true, nullptr, adhocDefaultTimeout * 1000); int sent = send(metasocket, (char*)&packet, sizeof(packet), 0); - changeBlockingMode(metasocket, 1); if (sent > 0) { socklen_t addrLen = sizeof(LocalIP); memset(&LocalIP, 0, addrLen); diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index fa5194f17e..8c88d471fc 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -49,7 +49,6 @@ #include "Core/MemMap.h" #include "Core/HLE/HLE.h" #include "Core/HLE/HLEHelperThread.h" -#include "Core/HLE/sceNetAdhoc.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernel.h" #include "Core/HLE/sceKernelMutex.h" @@ -928,11 +927,12 @@ extern int newChat; SceNetAdhocctlPeerInfo * findFriend(SceNetEtherAddr * MAC); /** - * Get the Non-Blocking Mode of the socket + * Get the Readability(ie. recv) and/or Writability(ie. send) of a socket * @param fd File Descriptor of the socket - * @return 1 for non-blocking, 0 for blocking + * @param timeout in usec (micro seconds), 0 = non-blocking + * @return > 0 = ready, 0 = timeout, -1 = error (errorcode only represent error of select and doesn't represent error of the socket) */ -int getNonBlockingFlag(int fd); +int IsSocketReady(int fd, bool readfd, bool writefd, int* errorcode = nullptr, int timeoutUS = 0); /** * Changes the Blocking Mode of the socket diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 9d62792037..7b9644e4e6 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -46,7 +46,6 @@ #include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelInterrupt.h" -#include "Core/HLE/proAdhoc.h" #include "Core/HLE/sceNetAdhoc.h" #include "Core/HLE/sceNet.h" #include "Core/HLE/proAdhocServer.h" @@ -63,7 +62,7 @@ static bool netAdhocMatchingInited; int netAdhocMatchingStarted = 0; int adhocDefaultTimeout = 2000; //5000 ms int adhocExtraPollDelayMS = 10; //10 -int adhocEventPollDelayMS = 100; //100 +int adhocEventPollDelayMS = 100; //100; Seems to be the same with PSP_ADHOCCTL_RECV_TIMEOUT int adhocMatchingEventDelayMS = 30; //30 int adhocEventDelayMS = 300; //500; This will affect the duration of "Connecting..." dialog/message box in .Hack//Link and Naruto Ultimate Ninja Heroes 3 @@ -78,6 +77,7 @@ int IsAdhocctlInCB = 0; int adhocctlNotifyEvent = -1; int adhocSocketNotifyEvent = -1; +std::map adhocctlRequests; std::map adhocSocketRequests; std::map sendTargetPeers; @@ -129,9 +129,51 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) { SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); if (waitID == 0 || error != 0) - return; // FIXME: Is it safe to exit here like this without re-scheduling the event? Will this event be triggered again? What will happen to the result i might want to change if exited here? + return; - int waitVal = __KernelGetWaitValue(threadID, error); // FIXME: Is this value going to be a valid value if waitID == 0? or it's a value belonged to other event? + // Socket not found?! Should never happened! but if it ever happen should we just exit here or need to wake the thread first? + if (adhocctlRequests.find(uid) == adhocctlRequests.end()) { + WARN_LOG(SCENET, "sceNetAdhocctl Socket WaitID(%i) not found!", uid); + //__KernelResumeThreadFromWait(threadID, ERROR_NET_ADHOCCTL_BUSY); + return; + } + + AdhocctlRequest& req = adhocctlRequests[uid]; + int len = 0; + + SceNetAdhocctlConnectPacketC2S packet; + memset(&packet, 0, sizeof(packet)); + packet.base.opcode = req.opcode; + packet.group = req.group; + + switch (req.opcode) + { + case OPCODE_CONNECT: + len = sizeof(packet); + break; + case OPCODE_SCAN: + case OPCODE_DISCONNECT: + len = 1; + break; + } + + // Send Packet if it wasn't succesfully sent before + if (len > 0) { + if (IsSocketReady(metasocket, false, true) > 0) { + int ret = send(metasocket, (const char*)&packet, len, 0); + int sockerr = errno; + + if (ret > 0 || (ret == SOCKET_ERROR && sockerr != EAGAIN && sockerr != EWOULDBLOCK)) { + // Prevent from sending again + req.opcode = 0; + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocctl[%i]: Socket Error (%i)", uid, sockerr); + } + } + } + + // Now we just need to wait for replies from adhoc server and the change of state + int waitVal = __KernelGetWaitValue(threadID, error); if (adhocctlState != waitVal && error == 0) { // Detecting Adhocctl Initialization using waitVal < 0 if (waitVal >= 0 || (waitVal < 0 && (g_Config.bEnableWlan && !networkInited))) { @@ -148,20 +190,32 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) { result = 0; // Faking successfully connected to adhoc server } - //HLEKernel::ResumeFromWait(threadID, WAITTYPE_NET, uid, result); // FIXME: This won't do anything if waitID == 0, not sure what kind of value returned from the HLE which i might want to change here. - __KernelResumeThreadFromWait(threadID, result); // FIXME: Forcing to change the result, will it cause an issue if waitID == 0? + __KernelResumeThreadFromWait(threadID, result); DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhocctl - State: %d", waitID, error, (int)result, adhocctlState); + + // We are done with this request + adhocctlRequests.erase(uid); } -void WaitAdhocctlState(int state, int usec, const char* reason) { +int WaitAdhocctlState(AdhocctlRequest request, int state, int usec, const char* reason) { + int uid = (state < 0) ? 1 : metasocket; + + if (adhocctlRequests.find(uid) != adhocctlRequests.end()) { + WARN_LOG(SCENET, "sceNetAdhocctl - WaitID[%d] already existed, Socket is busy!", uid); + return ERROR_NET_ADHOCCTL_BUSY; + } + if (adhocctlNotifyEvent < 0) adhocctlNotifyEvent = CoreTiming::RegisterEvent("__AdhocctlNotify", __AdhocctlNotify); - int uid = (state < 0)? 1: metasocket; u64 param = ((u64)__KernelGetCurThread()) << 32 | uid; adhocctlStartTime = (u64)(real_time_now() * 1000.0); + adhocctlRequests[uid] = request; CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param); __KernelWaitCurThread(WAITTYPE_NET, uid, state, 0, false, reason); + + // faked success + return 0; } int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { @@ -386,17 +440,10 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { } int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { - fd_set readfds, writefds; - timeval tval; - FD_ZERO(&readfds); - FD_SET(uid, &readfds); - writefds = readfds; - tval.tv_sec = 0; - tval.tv_usec = 0; + int sockerr; // Wait for Connection (assuming "connect" has been called before) - int ret = select(uid + 1, &readfds, &writefds, NULL, &tval); - int sockerr = errno; + int ret = IsSocketReady(uid, true, true, &sockerr); // Connection is ready if (ret > 0) { @@ -535,11 +582,11 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { break; } - // We are done with this socket - adhocSocketRequests.erase(uid); - __KernelResumeThreadFromWait(threadID, result); DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhoc - SocketID: %d", waitID, error, (int)result, req.id); + + // We are done with this socket + adhocSocketRequests.erase(uid); } int WaitBlockingAdhocSocket(int socketId, int type, int pspSocketId, void* buffer, s32_le* len, u32 timeoutUS, SceNetEtherAddr* remoteMAC, u16_le* remotePort, const char* reason) { @@ -551,8 +598,7 @@ int WaitBlockingAdhocSocket(int socketId, int type, int pspSocketId, void* buffe if (adhocSocketNotifyEvent < 0) adhocSocketNotifyEvent = CoreTiming::RegisterEvent("__AdhocSocketNotify", __AdhocSocketNotify); - if (getNonBlockingFlag(socketId) == 0) - changeBlockingMode(socketId, 1); + //changeBlockingMode(socketId, 1); u32 tmout = timeoutUS; if (tmout > 0) @@ -646,6 +692,7 @@ void __NetAdhocDoState(PointerWrap &p) { // Discard leftover events adhocctlEvents.clear(); matchingEvents.clear(); + adhocctlRequests.clear(); adhocSocketRequests.clear(); sendTargetPeers.clear(); @@ -683,6 +730,7 @@ void __AdhocNotifInit() { adhocctlNotifyEvent = CoreTiming::RegisterEvent("__AdhocctlNotify", __AdhocctlNotify); adhocSocketNotifyEvent = CoreTiming::RegisterEvent("__AdhocSocketNotify", __AdhocSocketNotify); + adhocctlRequests.clear(); adhocSocketRequests.clear(); sendTargetPeers.clear(); } @@ -744,7 +792,8 @@ static u32 sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) { // Need to make sure to be connected to adhoc server before returning to prevent GTA VCS failed to create/join a group and unable to see any game room int us = adhocExtraPollDelayMS * 1000; if (g_Config.bEnableWlan && !networkInited) { - WaitAdhocctlState(-1, us, "adhoc init"); + AdhocctlRequest dummyreq = { OPCODE_LOGIN, {0} }; + return WaitAdhocctlState(dummyreq, -1, us, "adhocctl init"); } // Give a little time for friendFinder thread to be ready before the game use the next sceNet functions, should've checked for friendFinderRunning status instead of guessing the time? else @@ -871,6 +920,9 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 u //sceNetPortOpen("UDP", port); UPnP_Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); // g_PortManager.Add(IP_PROTOCOL_UDP, isOriPort ? port : port + portOffset, port + portOffset); + // Switch to non-blocking for futher usage + changeBlockingMode(usocket, 1); + // Success return i + 256; } @@ -1009,7 +1061,6 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int //_acquireNetworkLock(); // Send Data. UDP are guaranteed to be sent as a whole or nothing(failed if len > SO_MAX_MSG_SIZE), and never be partially sent/recv - changeBlockingMode(socket->id, 1); int sent = sendto(socket->id, (const char *)data, len, 0, (sockaddr *)&target, sizeof(target)); int error = errno; @@ -1087,8 +1138,6 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int peerlock.unlock(); // Send Data - changeBlockingMode(socket->id, 1); // Do we need to switched to blocking-mode to make sure the data are fully sent? - // Simulate blocking behaviour with non-blocking socket if (!flag) { if (sendTargetPeers.find(socket->id) != sendTargetPeers.end()) { @@ -1232,11 +1281,9 @@ 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. - changeBlockingMode(socket->id, 1); // 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 received = recvfrom(socket->id, (char*)buf, *len, MSG_PEEK, (sockaddr*)&sin, &sinlen); if (received != SOCKET_ERROR && *len < received) { - changeBlockingMode(socket->id, 0); WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(socket->id), received, *len, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); *len = received; @@ -1272,7 +1319,6 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * VERBOSE_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpRecv[%i:%u] [size=%i]", error, id, socket->lport, *len); } - changeBlockingMode(socket->id, 0); // Should we set output length to 0 on Error? *len = 0; @@ -1571,6 +1617,7 @@ int sceNetAdhocctlScan() { if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { adhocctlState = ADHOCCTL_STATE_SCANNING; + int us = adhocEventPollDelayMS * 1000; // Reset Networks/Group list to prevent other threads from using these soon to be replaced networks peerlock.lock(); @@ -1582,17 +1629,23 @@ int sceNetAdhocctlScan() { uint8_t opcode = OPCODE_SCAN; // Send Scan Request Packet, may failed with socket error 10054/10053 if someone else with the same IP already connected to AdHoc Server (the server might need to be modified to differentiate MAC instead of IP) - changeBlockingMode(metasocket, 0); int iResult = send(metasocket, (char *)&opcode, 1, 0); int error = errno; - changeBlockingMode(metasocket, 1); + if (iResult == SOCKET_ERROR) { - ERROR_LOG(SCENET, "Socket error (%i) when sending", error); - adhocctlState = ADHOCCTL_STATE_DISCONNECTED; - //notifyAdhocctlHandlers(ADHOCCTL_EVENT_SCAN, 0); - //if (error == ECONNABORTED || error == ECONNRESET || error == ENOTCONN) return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // A case where it need to reconnect to AdhocServer - return ERROR_NET_ADHOCCTL_DISCONNECTED; // ERROR_NET_ADHOCCTL_BUSY + if (error != EAGAIN && error != EWOULDBLOCK) { + ERROR_LOG(SCENET, "Socket error (%i) when sending", error); + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + //if (error == ECONNABORTED || error == ECONNRESET || error == ENOTCONN) return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // A case where it need to reconnect to AdhocServer + return ERROR_NET_ADHOCCTL_BUSY; + } + else if (friendFinderRunning) { + AdhocctlRequest req = { OPCODE_SCAN, {0} }; + return WaitAdhocctlState(req, ADHOCCTL_STATE_DISCONNECTED, us, "adhocctl scan"); + } } + else + hleDelayResult(0, "give a little time to be ready to receive the callback", us); // Not delaying here may cause Naruto Shippuden Ultimate Ninja Heroes 3 to get disconnected when the mission started // Return Success return 0; @@ -1741,6 +1794,8 @@ static u32 sceNetAdhocctlAddHandler(u32 handlerPtr, u32 handlerArg) { u32 NetAdhocctl_Disconnect() { // Library initialized if (netAdhocctlInited) { + int iResult, error; + int us = adhocEventPollDelayMS * 1000; // Connected State (Adhoc Mode) if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { // (threadStatus == ADHOCCTL_STATE_CONNECTED) // Clear Network Name @@ -1756,14 +1811,20 @@ u32 NetAdhocctl_Disconnect() { //_acquireNetworkLock(); // Send Disconnect Request Packet - changeBlockingMode(metasocket, 0); - int iResult = send(metasocket, (const char*)&opcode, 1, 0); - int error = errno; - changeBlockingMode(metasocket, 1); + iResult = send(metasocket, (const char*)&opcode, 1, 0); + error = errno; + + // Sending may get socket error 10053 if the AdhocServer is already shutted down if (iResult == SOCKET_ERROR) { - ERROR_LOG(SCENET, "Socket error (%i) when sending", error); - // Set Disconnected State - adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + if (error != EAGAIN && error != EWOULDBLOCK) { + ERROR_LOG(SCENET, "Socket error (%i) when sending", error); + // Set Disconnected State + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + } + else if (friendFinderRunning) { + AdhocctlRequest req = { OPCODE_DISCONNECT, {0} }; + WaitAdhocctlState(req, ADHOCCTL_STATE_DISCONNECTED, us, "adhocctl disconnect"); + } } // Free Network Lock @@ -1793,12 +1854,8 @@ u32 NetAdhocctl_Disconnect() { notifyAdhocctlHandlers(ADHOCCTL_EVENT_DISCONNECT, 0); //hleCheckCurrentCallbacks(); - int us = adhocEventPollDelayMS * 1000; - if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED && friendFinderRunning) { - WaitAdhocctlState(ADHOCCTL_STATE_DISCONNECTED, us, "adhoc disconnect"); - } // Return Success, some games might ignore returned value and always treat it as success, otherwise repeatedly calling this function - else if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED || iResult != SOCKET_ERROR) hleDelayResult(0, "give time to init/cleanup", us); return 0; @@ -2038,10 +2095,9 @@ int NetAdhocctl_Create(const char* groupName) { // Acquire Network Lock // Send Packet - changeBlockingMode(metasocket, 0); int iResult = send(metasocket, (const char*)&packet, sizeof(packet), 0); int error = errno; - changeBlockingMode(metasocket, 1); + if (iResult == SOCKET_ERROR) { ERROR_LOG(SCENET, "Socket error (%i) when sending", error); //return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // ERROR_NET_ADHOCCTL_DISCONNECTED; // ERROR_NET_ADHOCCTL_BUSY; @@ -2059,11 +2115,13 @@ int NetAdhocctl_Create(const char* groupName) { // Wait for Status to be connected to prevent Ford Street Racing from Failed to create game session int us = adhocEventDelayMS * 1000; - if (adhocctlState != ADHOCCTL_STATE_CONNECTED && friendFinderRunning) { - WaitAdhocctlState(ADHOCCTL_STATE_CONNECTED, us, "adhoc connect"); + if (adhocctlState != ADHOCCTL_STATE_CONNECTED && iResult == SOCKET_ERROR && friendFinderRunning) { + AdhocctlRequest req = { OPCODE_CONNECT, {0} }; + if (groupNameStruct != NULL) req.group = *groupNameStruct; + return WaitAdhocctlState(req, ADHOCCTL_STATE_CONNECTED, us, "adhocctl connect"); } // Giving time for Naruto Shippuden Ninja Heroes 3 to close down the "Connecting..." dialog, otherwise the dialog will stuck there. - else if (adhocctlState == ADHOCCTL_STATE_CONNECTED) + else if (adhocctlState == ADHOCCTL_STATE_CONNECTED || iResult != SOCKET_ERROR) hleDelayResult(0, "give time to init/cleanup", us); // Return Success @@ -2497,6 +2555,9 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, if (!isClient) UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); // g_PortManager.Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); + // Switch to non-blocking for futher usage + changeBlockingMode(tcpsocket, 1); + // Return PTP Socket Pointer return i + 1; } @@ -2665,15 +2726,6 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int memset(&peeraddr, 0, sizeof(peeraddr)); socklen_t peeraddrlen = sizeof(peeraddr); - // Grab Nonblocking Flag - uint32_t nbio = getNonBlockingFlag(socket->id); - // Switch to Nonblocking Behaviour - if (nbio == 0) { - // Overwrite Socket Option - changeBlockingMode(socket->id, 1); - } - - // TODO: Use a different thread (similar to sceIo) for recvfrom, recv & accept to prevent blocking-socket from blocking emulation // Accept Connection int newsocket = accept(socket->id, (sockaddr *)&peeraddr, &peeraddrlen); int error = errno; @@ -2689,12 +2741,6 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int } } - // Restore Blocking Behaviour - if (nbio == 0) { - // Restore Socket Option - changeBlockingMode(socket->id, 0); - } - // Accepted New Connection if (newsocket > 0) { int newid = AcceptPtpSocket(id, newsocket, peeraddr, addr, port); @@ -2769,10 +2815,6 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { // Grab Nonblocking Flag //uint32_t nbio = getNonBlockingFlag(socket->id); - // Switch to Nonblocking Behaviour. Forcing blocking behaviour on the first connect may fix connection issue on GvG Next Plus, But i don't like using blocking socket with infinite timeout if the game it self were asking for non-blocking behaviour :( - // We are using non-blocking to simulate blocking - changeBlockingMode(socket->id, 1); - // 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? // TODO: Keep track number of Connect attempts so we can simulate blocking on first attempt (getNonBlockingFlag can't be used to get non-blocking flag on Windows thus can't be used to keep track) @@ -2998,6 +3040,9 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int //sceNetPortOpen("TCP", sport); UPnP_Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); // g_PortManager.Add(IP_PROTOCOL_TCP, isOriPort ? sport : sport + portOffset, sport + portOffset); + // Switch to non-blocking for futher usage + changeBlockingMode(tcpsocket, 1); + // Return PTP Socket Pointer return i + 1; } @@ -3082,7 +3127,6 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // _acquireNetworkLock(); // Send Data - changeBlockingMode(socket->id, 1); int sent = send(socket->id, data, *len, 0); int error = errno; @@ -3173,7 +3217,6 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou int error = 0; // Receive Data - changeBlockingMode(socket->id, 1); received = recv(socket->id, (char*)buf, *len, 0); error = errno; @@ -3185,7 +3228,6 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou VERBOSE_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPtpRecv[%i:%u] [size=%i]", error, id, socket->lport, *len); } - changeBlockingMode(socket->id, 0); // Free Network Lock // _freeNetworkLock(); diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index 08583433de..6a12e19c97 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -30,6 +30,11 @@ typedef struct MatchingArgs { #pragma pack(pop) #endif +struct AdhocctlRequest { + u8 opcode; + SceNetAdhocctlGroupName group; +}; + struct AdhocSendTarget { u32 ip; u16 port; // original port @@ -48,7 +53,7 @@ struct AdhocSocketRequest { s32_le* length; u32 timeout; u64 startTime; - struct SceNetEtherAddr* remoteMAC; + SceNetEtherAddr* remoteMAC; u16_le* remotePort; };