From 439171fc563f0161c15934d678fd503a58e2e2f6 Mon Sep 17 00:00:00 2001 From: ANR2ME Date: Tue, 18 Aug 2020 02:01:30 +0700 Subject: [PATCH] An attempt to reduce the lags/stutters during multiplayer caused by improper blocking behavior. --- Core/HLE/proAdhoc.cpp | 26 +- Core/HLE/proAdhoc.h | 11 +- Core/HLE/sceKernelThread.cpp | 1 + Core/HLE/sceKernelThread.h | 1 + Core/HLE/sceNet.cpp | 6 +- Core/HLE/sceNetAdhoc.cpp | 1195 +++++++++++++++++++++++----------- Core/HLE/sceNetAdhoc.h | 34 + 7 files changed, 898 insertions(+), 376 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index b8f57cca83..8458b2c9f1 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -55,6 +55,7 @@ bool friendFinderRunning = false; SceNetAdhocctlPeerInfo * friends = NULL; SceNetAdhocctlScanInfo * networks = NULL; SceNetAdhocctlScanInfo * newnetworks = NULL; +u64 adhocctlStartTime = 0; int adhocctlState = ADHOCCTL_STATE_DISCONNECTED; int adhocConnectionType = ADHOC_CONNECT; @@ -71,6 +72,7 @@ std::thread friendFinderThread; std::recursive_mutex peerlock; SceNetAdhocPdpStat * pdp[255]; SceNetAdhocPtpStat * ptp[255]; +std::map ptpConnectCount; std::vector chatLog; std::string name = ""; std::string incoming = ""; @@ -210,6 +212,15 @@ 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 +} + void changeBlockingMode(int fd, int nonblocking) { unsigned long on = 1; unsigned long off = 0; @@ -321,6 +332,7 @@ void deleteAllPTP() { // Delete Reference ptp[i] = NULL; + ptpConnectCount.erase(i); } } } @@ -1174,7 +1186,9 @@ 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); @@ -1245,7 +1259,9 @@ int friendFinder(){ 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); networkInited = false; @@ -1560,7 +1576,10 @@ int getLocalIp(sockaddr_in* SocketAddress) { struct sockaddr_in localAddr; localAddr.sin_addr.s_addr = INADDR_ANY; socklen_t addrLen = sizeof(localAddr); - if (SOCKET_ERROR != getsockname(metasocket, (struct sockaddr*) & localAddr, &addrLen)) { + 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; } @@ -1905,8 +1924,6 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ sleep_ms(1); cnt++; } - // Switch back to Blocking Behaviour - changeBlockingMode(metasocket, 0); if (iResult == SOCKET_ERROR && errorcode != EISCONN) { 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)); @@ -1924,8 +1941,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); int sent = send(metasocket, (char*)&packet, sizeof(packet), 0); - changeBlockingMode(metasocket, 1); // Change to non-blocking + 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 c3607e4d36..fa5194f17e 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -827,6 +827,7 @@ extern std::thread friendFinderThread; extern std::recursive_mutex peerlock; extern SceNetAdhocPdpStat * pdp[255]; extern SceNetAdhocPtpStat * ptp[255]; +extern std::map ptpConnectCount; union SockAddrIN4 { sockaddr addr; @@ -846,7 +847,8 @@ extern SceNetAdhocMatchingContext * contexts; extern int one; extern bool friendFinderRunning; extern SceNetAdhocctlPeerInfo * friends; -extern SceNetAdhocctlScanInfo * networks; +extern SceNetAdhocctlScanInfo * networks; +extern u64 adhocctlStartTime; extern int adhocctlState; extern int adhocConnectionType; // End of Aux vars @@ -925,6 +927,13 @@ extern int newChat; */ SceNetAdhocctlPeerInfo * findFriend(SceNetEtherAddr * MAC); +/** + * Get the Non-Blocking Mode of the socket + * @param fd File Descriptor of the socket + * @return 1 for non-blocking, 0 for blocking + */ +int getNonBlockingFlag(int fd); + /** * Changes the Blocking Mode of the socket * @param fd File Descriptor of the socket diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 59b3ec3c7e..0e096111c0 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -81,6 +81,7 @@ const WaitTypeNames waitTypeNames[] = { { WAITTYPE_VMEM, "Volatile Mem" }, { WAITTYPE_ASYNCIO, "AsyncIO" }, { WAITTYPE_MICINPUT, "Microphone input"}, + { WAITTYPE_NET, "Network"}, }; const char *getWaitTypeName(WaitType type) diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index d36e12c986..bb08bb3b66 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -107,6 +107,7 @@ enum WaitType : int WAITTYPE_VMEM = 22, WAITTYPE_ASYNCIO = 23, WAITTYPE_MICINPUT = 24, // fake + WAITTYPE_NET = 25, // fake NUM_WAITTYPES }; diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index a7d073ee1c..a34167f275 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -162,7 +162,7 @@ void __NetInit() { SceNetEtherAddr mac; getLocalMac(&mac); - INFO_LOG(SCENET, "LocalHost IP will be %s [%s]", inet_ntoa(g_localhostIP.in.sin_addr), mac2str(&mac).c_str()); + NOTICE_LOG(SCENET, "LocalHost IP will be %s [%s]", inet_ntoa(g_localhostIP.in.sin_addr), mac2str(&mac).c_str()); // TODO: May be we should initialize & cleanup somewhere else than here for PortManager to be used as general purpose for whatever port forwarding PPSSPP needed __UPnPInit(); @@ -250,6 +250,9 @@ void __NetDoState(PointerWrap &p) { netInetInited = cur_netInetInited; netInited = cur_netInited; + // Discard leftover events + apctlEvents.clear(); + // Previously, this wasn't being saved. It needs its own space. if (!apctlThreadHackAddr || (apctlThreadHackAddr && strcmp("apctlThreadHack", kernelMemory.GetBlockTag(apctlThreadHackAddr)) != 0)) { u32 blockSize = sizeof(apctlThreadCode); @@ -582,6 +585,7 @@ static int sceNetInit(u32 poolSize, u32 calloutPri, u32 calloutStack, u32 netini // Clear Socket Translator Memory memset(&pdp, 0, sizeof(pdp)); memset(&ptp, 0, sizeof(ptp)); + ptpConnectCount.clear(); return hleLogSuccessI(SCENET, 0); } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 5aaa6aa0c4..9d62792037 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -15,6 +15,10 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#if defined(_WIN32) +#include "Common/CommonWindows.h" +#endif + #if !defined(_WIN32) #include #endif @@ -46,6 +50,7 @@ #include "Core/HLE/sceNetAdhoc.h" #include "Core/HLE/sceNet.h" #include "Core/HLE/proAdhocServer.h" +#include "Core/HLE/KernelWaitHelpers.h" #include "i18n/i18n.h" // shared in sceNetAdhoc.h since it need to be used from sceNet.cpp also @@ -56,11 +61,11 @@ bool networkInited = false; static bool netAdhocMatchingInited; int netAdhocMatchingStarted = 0; -int adhocDefaultTimeout = 2000; //5000 +int adhocDefaultTimeout = 2000; //5000 ms int adhocExtraPollDelayMS = 10; //10 int adhocEventPollDelayMS = 100; //100 int adhocMatchingEventDelayMS = 30; //30 -int adhocEventDelayMS = 500; //500; This will affect the duration of "Connecting..." dialog/message box in .Hack//Link and Naruto Ultimate Ninja Heroes 3 +int adhocEventDelayMS = 300; //500; This will affect the duration of "Connecting..." dialog/message box in .Hack//Link and Naruto Ultimate Ninja Heroes 3 SceUID threadAdhocID; @@ -71,6 +76,11 @@ std::map adhocctlHandlers; std::vector matchingThreads; int IsAdhocctlInCB = 0; +int adhocctlNotifyEvent = -1; +int adhocSocketNotifyEvent = -1; +std::map adhocSocketRequests; +std::map sendTargetPeers; + u32 dummyThreadHackAddr = 0; u32_le dummyThreadCode[3]; u32 matchingThreadHackAddr = 0; @@ -78,11 +88,9 @@ u32_le matchingThreadCode[3]; int matchingEventThread(int matchingId); int matchingInputThread(int matchingId); +int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); +int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout); -int sceNetAdhocTerm(); -int sceNetAdhocctlTerm(); -int sceNetAdhocMatchingTerm(); -int sceNetAdhocMatchingSetHelloOpt(int matchingId, int optLenAddr, u32 optDataAddr); void __NetAdhocShutdown() { // Kill AdhocServer Thread @@ -112,6 +120,455 @@ void __NetAdhocShutdown() { } } +static void __AdhocctlNotify(u64 userdata, int cyclesLate) { + SceUID threadID = userdata >> 32; + int uid = (int)(userdata & 0xFFFFFFFF); + + s64 result = 0; + u32 error = 0; + + 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? + + 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? + if (adhocctlState != waitVal && error == 0) { + // Detecting Adhocctl Initialization using waitVal < 0 + if (waitVal >= 0 || (waitVal < 0 && (g_Config.bEnableWlan && !networkInited))) { + u64 now = (u64)(real_time_now() * 1000.0); + if (now - adhocctlStartTime <= adhocDefaultTimeout) { + // Try again in another 0.5ms until state matched or timedout. + CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, adhocctlNotifyEvent, userdata); + return; + } + else + result = 0; // ERROR_NET_ADHOCCTL_BUSY + } + else + 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? + DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhocctl - State: %d", waitID, error, (int)result, adhocctlState); +} + +void WaitAdhocctlState(int state, int usec, const char* reason) { + 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); + CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param); + __KernelWaitCurThread(WAITTYPE_NET, uid, state, 0, false, reason); +} + +int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + socklen_t sinlen = sizeof(sin); + + int ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_PEEK, (sockaddr*)&sin, &sinlen); + int sockerr = errno; + + // Note: UDP must not be received partially, otherwise leftover data in socket's buffer will be discarded + if (ret >= 0 && ret <= *req.length) { + ret = recvfrom(uid, (char*)req.buffer, *req.length, 0, (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 + if (ret > 0) { + DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(uid), ret, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + + // Peer MAC + SceNetEtherAddr mac; + + // Find Peer MAC + if (resolveIP(sin.sin_addr.s_addr, &mac)) { + // Provide Sender Information + *req.remoteMAC = mac; + *req.remotePort = ntohs(sin.sin_port) - portOffset; + + // Save Length + *req.length = ret; + + // Update last recv timestamp + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + } + // Unknown Peer + else { + *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, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + } + } + 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 == ETIMEDOUT) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + + return -1; + } + else + result = ERROR_NET_ADHOC_TIMEOUT; + } + else + result = ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED + + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { + SceNetAdhocPdpStat* pdpsocket = pdp[req.id - 1]; + + result = 0; + bool retry = false; + for (auto peer = targetPeers.peers.begin(); peer != targetPeers.peers.end(); ) { + // Fill in Target Structure + 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)); + + int ret = sendto(pdpsocket->id, (const char*)req.buffer, targetPeers.length, 0, (sockaddr*)&target, sizeof(target)); + 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, inet_ntoa(target.sin_addr), ntohs(target.sin_port)); + // Remove successfully sent to peer to prevent sending the same data again during a retry + peer = targetPeers.peers.erase(peer); + } + else { + if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ETIMEDOUT)) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + retry = true; + } + else + // FIXME: Does Broadcast always success? even with timeout/blocking? + result = ERROR_NET_ADHOC_TIMEOUT; + } + ++peer; + } + + 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); + } + + if (retry) + return -1; + + return 0; +} + +int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { + SceNetAdhocPtpStat* ptpsocket = ptp[req.id - 1]; + + // Send Data + int ret = send(uid, (const char*)req.buffer, *req.length, 0); + int sockerr = errno; + + // Success + if (ret > 0) { + // Save Length + *req.length = ret; + + DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket->lport, ret, mac2str(&ptpsocket->paddr).c_str(), ptpsocket->pport); + + // Return Success + result = 0; + } + + // Non-Critical Error + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ETIMEDOUT)) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + + return -1; + } + else + result = ERROR_NET_ADHOC_TIMEOUT; + } + + // Change Socket State + ptpsocket->state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + result = ERROR_NET_ADHOC_DISCONNECTED; + + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { + SceNetAdhocPtpStat* ptpsocket = ptp[req.id - 1]; + + int ret = recv(uid, (char*)req.buffer, *req.length, 0); + int sockerr = errno; + + // Received Data + if (ret > 0) { + DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, ptpsocket->lport, ret, mac2str(&ptpsocket->paddr).c_str(), ptpsocket->pport); + // Save Length + *req.length = ret; + + // Update last recv timestamp + peerlock.lock(); + auto peer = findFriend(&ptpsocket->paddr); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + + result = 0; + } + // Non-Critical Error + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ETIMEDOUT)) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else + result = ERROR_NET_ADHOC_TIMEOUT; + } + else { + // Change Socket State + ptpsocket->state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + result = ERROR_NET_ADHOC_DISCONNECTED; // ERROR_NET_ADHOC_INVALID_ARG + } + + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + socklen_t sinlen = sizeof(sin); + + // Accept Connection + int ret = accept(uid, (sockaddr*)&sin, &sinlen); + int sockerr = errno; + + // Accepted New Connection + if (ret > 0) { + int newid = AcceptPtpSocket(req.id, ret, sin, req.remoteMAC, req.remotePort); + if (newid > 0) + result = newid; + } + else if (ret == SOCKET_ERROR && connectInProgress(sockerr)) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else + result = ERROR_NET_ADHOC_TIMEOUT; + } + else + result = ERROR_NET_ADHOC_INVALID_ARG; //ERROR_NET_ADHOC_TIMEOUT + + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +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; + + // Wait for Connection (assuming "connect" has been called before) + int ret = select(uid + 1, &readfds, &writefds, NULL, &tval); + int sockerr = errno; + + // Connection is ready + if (ret > 0) { + SceNetAdhocPtpStat* ptpsocket = ptp[req.id - 1]; + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + socklen_t sinlen = sizeof(sin); + getpeername(uid, (sockaddr*)&sin, &sinlen); + + // Set Connected State + ptpsocket->state = ADHOC_PTP_STATE_ESTABLISHED; + + INFO_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Established (%s:%u)", req.id, ptpsocket->lport, inet_ntoa(sin.sin_addr), ptpsocket->pport); + + // Success + result = 0; + } + // Timeout + else if (ret == 0) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else + result = ERROR_NET_ADHOC_TIMEOUT; + } + else + result = ERROR_NET_ADHOC_CONNECTION_REFUSED; // ERROR_NET_ADHOC_TIMEOUT; + + if (ret == SOCKET_ERROR) + DEBUG_LOG(SCENET, "sceNetAdhocPtpConnect[%i]: Socket Error (%i)", req.id, sockerr); + + return 0; +} + +int DoBlockingAdhocPollSocket(int uid, AdhocSocketRequest& req, s64& result) { + SceNetAdhocPollSd* sds = (SceNetAdhocPollSd*)req.buffer; + int ret = PollAdhocSocket(sds, req.id, 0); + if (ret <= 0) { + u64 now = (u64)(real_time_now() * 1000000.0); + if (req.timeout == 0 || now - req.startTime <= req.timeout) { + return -1; + } + else if (ret == 0) + result = ERROR_NET_ADHOC_TIMEOUT; + else + result = ERROR_NET_ADHOC_EXCEPTION_EVENT; + } + result = ret; + + return 0; +} + +static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { + SceUID threadID = userdata >> 32; + int uid = (int)(userdata & 0xFFFFFFFF); // fd/socket id + + s64 result = -1; + u32 error = 0; + int delayUS = 500; + + SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); + if (waitID == 0 || error != 0) + return; + + // Socket not found?! Should never happened! but if it ever happen should we just exit here or need to wake the thread first? + if (adhocSocketRequests.find(uid) == adhocSocketRequests.end()) { + WARN_LOG(SCENET, "sceNetAdhoc Socket WaitID(%i) not found!", uid); + //__KernelResumeThreadFromWait(threadID, ERROR_NET_ADHOC_TIMEOUT); + return; + } + + AdhocSocketRequest req = adhocSocketRequests[uid]; + + switch (req.type) { + case PDP_SEND: + if (sendTargetPeers.find(uid) == sendTargetPeers.end()) { + // No destination peers? + result = 0; + break; + } + if (DoBlockingPdpSend(uid, req, result, sendTargetPeers[uid])) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + sendTargetPeers.erase(uid); + break; + + case PDP_RECV: + if (DoBlockingPdpRecv(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + break; + + case PTP_SEND: + if (DoBlockingPtpSend(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + break; + + case PTP_RECV: + if (DoBlockingPtpRecv(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + break; + + case PTP_ACCEPT: + if (DoBlockingPtpAccept(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + break; + + case PTP_CONNECT: + if (DoBlockingPtpConnect(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + break; + + case ADHOC_POLL_SOCKET: + if (DoBlockingAdhocPollSocket(uid, req, result)) { + // Try again in another 0.5ms until data available or timedout. + CoreTiming::ScheduleEvent(usToCycles(delayUS) - cyclesLate, adhocSocketNotifyEvent, userdata); + return; + } + 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); +} + +int WaitBlockingAdhocSocket(int socketId, int type, int pspSocketId, void* buffer, s32_le* len, u32 timeoutUS, SceNetEtherAddr* remoteMAC, u16_le* remotePort, const char* reason) { + if (adhocSocketRequests.find(socketId) != adhocSocketRequests.end()) { + WARN_LOG(SCENET, "sceNetAdhoc - WaitID[%d] already existed, Socket[%d] is busy!", socketId, pspSocketId); + return ERROR_NET_ADHOC_BUSY; + } + + if (adhocSocketNotifyEvent < 0) + adhocSocketNotifyEvent = CoreTiming::RegisterEvent("__AdhocSocketNotify", __AdhocSocketNotify); + + if (getNonBlockingFlag(socketId) == 0) + changeBlockingMode(socketId, 1); + + u32 tmout = timeoutUS; + if (tmout > 0) + tmout = std::max(tmout, minSocketTimeoutUS); + + u64 param = ((u64)__KernelGetCurThread()) << 32 | socketId; + u64 startTime = (u64)(real_time_now() * 1000000.0); + adhocSocketRequests[socketId] = { type, pspSocketId, buffer, len, tmout, startTime, remoteMAC, remotePort }; + // Some games (ie. Power Stone Collection) are using as small as 100 usec timeout + CoreTiming::ScheduleEvent(usToCycles(100), adhocSocketNotifyEvent, param); + __KernelWaitCurThread(WAITTYPE_NET, socketId, 0, 0, false, reason); + + // Fallback return value + return ERROR_NET_ADHOC_TIMEOUT; +} + void __NetAdhocDoState(PointerWrap &p) { auto s = p.Section("sceNetAdhoc", 1, 5); if (!s) @@ -159,10 +616,16 @@ void __NetAdhocDoState(PointerWrap &p) { if (s >= 5) { Do(p, adhocConnectionType); Do(p, adhocctlState); + Do(p, adhocctlNotifyEvent); + CoreTiming::RestoreRegisterEvent(adhocctlNotifyEvent, "__AdhocctlNotify", __AdhocctlNotify); + Do(p, adhocSocketNotifyEvent); + CoreTiming::RestoreRegisterEvent(adhocSocketNotifyEvent, "__AdhocSocketNotify", __AdhocSocketNotify); } else { adhocConnectionType = ADHOC_CONNECT; adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + adhocctlNotifyEvent = -1; + adhocSocketNotifyEvent = -1; } if (p.mode == p.MODE_READ) { @@ -179,6 +642,12 @@ void __NetAdhocDoState(PointerWrap &p) { // Restore dummy Loop MIPS code to prevent crashes after loading from SaveState if (dummyThreadHackAddr) Memory::Memcpy(dummyThreadHackAddr, dummyThreadCode, sizeof(dummyThreadCode)); if (matchingThreadHackAddr) Memory::Memcpy(matchingThreadHackAddr, matchingThreadCode, sizeof(matchingThreadCode)); + + // Discard leftover events + adhocctlEvents.clear(); + matchingEvents.clear(); + adhocSocketRequests.clear(); + sendTargetPeers.clear(); // Let's not change "Inited" value when Loading SaveState to prevent memory & port leaks netAdhocMatchingInited = cur_netAdhocMatchingInited; @@ -197,15 +666,6 @@ void __UpdateMatchingHandler(MatchingArgs ArgsPtr) { matchingEvents.push_back(ArgsPtr); } -static int getBlockingFlag(int id) { -#ifdef _WIN32 - return 0; -#else - int sockflag = fcntl(id, F_GETFL, O_NONBLOCK); - return sockflag & O_NONBLOCK; -#endif -} - u32_le __CreateHLELoop(u32_le *loopAddr, const char *sceFuncName, const char *hleFuncName, const char *tagName) { if (loopAddr == NULL || sceFuncName == NULL || hleFuncName == NULL) return 0; @@ -219,12 +679,21 @@ u32_le __CreateHLELoop(u32_le *loopAddr, const char *sceFuncName, const char *hl return dummyThreadHackAddr; } +void __AdhocNotifInit() { + adhocctlNotifyEvent = CoreTiming::RegisterEvent("__AdhocctlNotify", __AdhocctlNotify); + adhocSocketNotifyEvent = CoreTiming::RegisterEvent("__AdhocSocketNotify", __AdhocSocketNotify); + + adhocSocketRequests.clear(); + sendTargetPeers.clear(); +} + void __NetAdhocInit() { friendFinderRunning = false; netAdhocInited = false; netAdhocctlInited = false; netAdhocMatchingInited = false; adhocctlHandlers.clear(); + __AdhocNotifInit(); __AdhocServerInit(); // Create built-in AdhocServer Thread @@ -273,13 +742,14 @@ 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 cnt = 0; - while (g_Config.bEnableWlan && !networkInited && (cnt < adhocDefaultTimeout)) { - sleep_ms(1); - cnt++; + int us = adhocExtraPollDelayMS * 1000; + if (g_Config.bEnableWlan && !networkInited) { + WaitAdhocctlState(-1, us, "adhoc 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 + hleDelayResult(0, "give some time", us); - //hleDelayResult(0, "give some time", adhocEventPollDelayMS * 1000); // 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? return 0; } @@ -539,13 +1009,27 @@ 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, flag); + changeBlockingMode(socket->id, 1); int sent = sendto(socket->id, (const char *)data, len, 0, (sockaddr *)&target, sizeof(target)); int error = errno; + if (sent == SOCKET_ERROR) { + // Simulate blocking behaviour with non-blocking socket + if (!flag && (error == EAGAIN || error == EWOULDBLOCK || error == ETIMEDOUT)) { + if (sendTargetPeers.find(socket->id) != sendTargetPeers.end()) { + DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Socket(%d) is Busy!", id, getLocalPort(socket->id), socket->id); + return ERROR_NET_ADHOC_BUSY; + } + + AdhocSendTargets dest = { len, {}, false }; + dest.peers.push_back({ target.sin_addr.s_addr, dport }); + sendTargetPeers[socket->id] = dest; + return WaitBlockingAdhocSocket(socket->id, PDP_SEND, id, data, nullptr, timeout, nullptr, nullptr, "pdp send"); + } + DEBUG_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u] (size=%i)", error, id, getLocalPort(socket->id), ntohs(target.sin_port), len); } - changeBlockingMode(socket->id, 0); + //changeBlockingMode(socket->id, 0); // Free Network Lock //_freeNetworkLock(); @@ -558,13 +1042,12 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int return 0; // sent; // MotorStorm will try to resend if return value is not 0 } - // Blocking Situation - if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; + // Non-Blocking + if (flag) + return ERROR_NET_ADHOC_WOULD_BLOCK; - // Timeout - /*if (timeout > 0) - return ERROR_NET_ADHOC_TIMEOUT;*/ - return ERROR_NET_ADHOC_INVALID_ADDR; // There is no concept of Timeout when sending UDP due to no ACK, not sure about PDP since some games did use the timeout arg + // Does PDP can Timeout? There is no concept of Timeout when sending UDP due to no ACK, but might happen if the socket buffer is full, not sure about PDP since some games did use the timeout arg + return ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ADDR; } //VERBOSE_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Unknown Target Peer %s:%u\n", id, getLocalPort(socket->id), mac2str(daddr, tmpmac), ntohs(target.sin_port)); } @@ -590,38 +1073,55 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Acquire Peer Lock peerlock.lock(); - - // Send Data - changeBlockingMode(socket->id, flag); // Do we need to switched to blocking-mode to make sure the data are fully sent? - + AdhocSendTargets dest = { len, {}, true }; // Iterate Peers - SceNetAdhocctlPeerInfo * peer = friends; + SceNetAdhocctlPeerInfo* peer = friends; for (; peer != NULL; peer = peer->next) { // Does Skipping sending to timed out friends could cause desync when players moving group at the time MP game started? - if (peer->last_recv == 0) + if (peer->last_recv == 0) continue; - // Fill in Target Structure - sockaddr_in target; - target.sin_family = AF_INET; - target.sin_addr.s_addr = peer->ip_addr; - target.sin_port = htons(dport + ((isOriPort && !isPrivateIP(peer->ip_addr)) ? 0 : portOffset)); - - int sent = sendto(socket->id, (const char *)data, len, 0, (sockaddr *)&target, sizeof(target)); - int error = errno; - if (sent == SOCKET_ERROR) { - DEBUG_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u](BC) [size=%i]", error, id, getLocalPort(socket->id), ntohs(target.sin_port), len); + dest.peers.push_back({ peer->ip_addr, dport }); + } + // Free Peer Lock + 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()) { + DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](BC): Socket(%d) is Busy!", id, getLocalPort(socket->id), socket->id); + return ERROR_NET_ADHOC_BUSY; } - - if (sent >= 0) { - DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](BC): Sent %u bytes to %s:%u\n", id, getLocalPort(socket->id), sent, inet_ntoa(target.sin_addr), ntohs(target.sin_port)); + + sendTargetPeers[socket->id] = dest; + return WaitBlockingAdhocSocket(socket->id, PDP_SEND, id, data, nullptr, timeout, nullptr, nullptr, "pdp send broadcast"); + } + // Non-blocking + else { + // Iterate Peers + for (auto peer : dest.peers) { + // Fill in Target Structure + 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)); + + int sent = sendto(socket->id, (const char*)data, len, 0, (sockaddr*)&target, sizeof(target)); + int error = errno; + if (sent == SOCKET_ERROR) { + DEBUG_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpSend[%i:%u->%u](BC) [size=%i]", error, id, getLocalPort(socket->id), ntohs(target.sin_port), len); + } + + if (sent >= 0) { + DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](BC): Sent %u bytes to %s:%u\n", id, getLocalPort(socket->id), sent, inet_ntoa(target.sin_addr), ntohs(target.sin_port)); + } } } - changeBlockingMode(socket->id, 0); - - // Free Peer Lock - peerlock.unlock(); + //changeBlockingMode(socket->id, 0); // Free Network Lock //_freeNetworkLock(); @@ -764,23 +1264,18 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * received = recvfrom(socket->id, (char*)buf, *len, 0, (sockaddr*)&sin, &sinlen); error = errno; - if (flag == 0) { - // Simulate blocking behaviour with non-blocking socket - uint32_t starttime = (uint32_t)(real_time_now() * 1000000.0); - // Wait for Connection. 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 - while ((timeout == 0 || ((uint32_t)(real_time_now() * 1000000.0) - starttime) < (uint32_t)timeout) && (received == SOCKET_ERROR) && (connectInProgress(error) || error == ECONNRESET)) { - received = recvfrom(socket->id, (char*)buf, *len, 0, (sockaddr*)&sin, &sinlen); - error = errno; - // Wait 1ms - sleep_ms(1); - } - } - if (received == SOCKET_ERROR) { + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + return WaitBlockingAdhocSocket(socket->id, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); + } + 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; // Received Data. UDP can also receives 0 data, while on TCP 0 data = connection gracefully closed, but not sure about PDP tho if (received > 0) { @@ -864,6 +1359,57 @@ int sceNetAdhocSetSocketAlert(int id, int flag) { return 0; //Dummy Result } +int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout) { + //WSAPoll only available for Vista or newer, so we'll use an alternative way for XP since Windows doesn't have poll function like *NIX + fd_set readfds, writefds, exceptfds; + int fd; + int maxfd = 0; + FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); + // TODO: PDP and PTP should share the same indexing to prevent identical PDP & PTP socket id + for (int i = 0; i < count; i++) { + sds[i].revents = 0; + // Fill in Socket ID + if (sds[i].id <= 255 && ptp[sds[i].id - 1] != NULL) { + fd = ptp[sds[i].id - 1]->id; + if (ptp[sds[i].id - 1]->state == ADHOC_PTP_STATE_LISTEN) sds[i].revents |= ADHOC_EV_ACCEPT; + else + if (ptp[sds[i].id - 1]->state == ADHOC_PTP_STATE_CLOSED) sds[i].revents |= ADHOC_EV_CONNECT; + } + else { + fd = pdp[sds[i].id - 256]->id; + } + if (fd > maxfd) maxfd = fd; + if (sds[i].events & ADHOC_EV_RECV) FD_SET(fd, &readfds); + if (sds[i].events & ADHOC_EV_SEND) FD_SET(fd, &writefds); // Does Data can always be sent regardless of events bitmask? + //if (sds[i].events & ADHOC_EV_ALERT) + FD_SET(fd, &exceptfds); // Does Alert can be raised on revents regardless of events bitmask? + } + timeval tmout; + tmout.tv_sec = timeout / 1000000; // seconds + tmout.tv_usec = (timeout % 1000000); // microseconds + int affectedsockets = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tmout); + if (affectedsockets > 0) { + affectedsockets = 0; + for (int i = 0; i < count; i++) { + if (sds[i].id <= 255 && ptp[sds[i].id - 1] != NULL) { + fd = ptp[sds[i].id - 1]->id; + } + else { + fd = pdp[sds[i].id - 256]->id; + } + if (FD_ISSET(fd, &readfds)) + sds[i].revents |= ADHOC_EV_RECV; + if (FD_ISSET(fd, &writefds)) + sds[i].revents |= ADHOC_EV_SEND; // Does Data can always be sent regardless of events bitmask? + sds[i].revents &= sds[i].events; + if (FD_ISSET(fd, &exceptfds)) + sds[i].revents |= ADHOC_EV_ALERT; // Does Alert can be raised on revents regardless of events bitmask? + if (sds[i].revents) affectedsockets++; + } + } + return affectedsockets; +} + int sceNetAdhocPollSocket(u32 socketStructAddr, int count, int timeout, int nonblock) { // timeout in microseconds // Library is initialized if (netAdhocInited) @@ -883,68 +1429,31 @@ int sceNetAdhocPollSocket(u32 socketStructAddr, int count, int timeout, int nonb } // Nonblocking Mode - if (nonblock) timeout = 0; + if (nonblock) + timeout = 0; - // Prevent Nonblocking Mode - else - if (timeout == 0) timeout = 1; + // Blocking Mode + else + // Does timeout = 0 means undefinite on PSP? + if (timeout == 0) + timeout = adhocDefaultTimeout * 1000; // minSocketTimeoutUS; - int affectedsockets = 0; - if (count > (int)FD_SETSIZE) count = FD_SETSIZE; // return affectedsockets; + if (count > (int)FD_SETSIZE) + count = FD_SETSIZE; // return 0; //ERROR_NET_ADHOC_INVALID_ARG // Acquire Network Lock //acquireNetworkLock(); // Poll Sockets //int affectedsockets = sceNetInetPoll(isds, count, timeout); - - //WSAPoll only available for Vista or newer, so we'll use an alternative way for XP since Windows doesn't have poll function like *NIX - fd_set readfds, writefds, exceptfds; - int fd; - int maxfd = 0; - FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); - // TODO: PDP and PTP should share the same indexing to prevent identical PDP & PTP socket id - for (int i = 0; i < count; i++) { - sds[i].revents = 0; - // Fill in Socket ID - if (sds[i].id <= 255 && ptp[sds[i].id - 1] != NULL) { - fd = ptp[sds[i].id - 1]->id; - if (ptp[sds[i].id - 1]->state == ADHOC_PTP_STATE_LISTEN) sds[i].revents |= ADHOC_EV_ACCEPT; - else - if (ptp[sds[i].id - 1]->state == ADHOC_PTP_STATE_CLOSED) sds[i].revents |= ADHOC_EV_CONNECT; - } - else { - fd = pdp[sds[i].id - 256]->id; - } - if (fd > maxfd) maxfd = fd; - if (sds[i].events & ADHOC_EV_RECV) FD_SET(fd, &readfds); - if (sds[i].events & ADHOC_EV_SEND) FD_SET(fd, &writefds); // Does Data can always be sent regardless of events bitmask? - //if (sds[i].events & ADHOC_EV_ALERT) - FD_SET(fd, &exceptfds); // Does Alert can be raised on revents regardless of events bitmask? - } - timeval tmout; - tmout.tv_sec = timeout / 1000000; // seconds - tmout.tv_usec = (timeout % 1000000); // microseconds - affectedsockets = select(maxfd + 1, &readfds, &writefds, &exceptfds, &tmout); - if (affectedsockets > 0) { - affectedsockets = 0; - for (int i = 0; i < count; i++) { - if (sds[i].id <= 255 && ptp[sds[i].id - 1] != NULL) { - fd = ptp[sds[i].id - 1]->id; - } - else { - fd = pdp[sds[i].id - 256]->id; - } - if (FD_ISSET(fd, &readfds)) - sds[i].revents |= ADHOC_EV_RECV; - if (FD_ISSET(fd, &writefds)) - sds[i].revents |= ADHOC_EV_SEND; // Does Data can always be sent regardless of events bitmask? - sds[i].revents &= sds[i].events; - if (FD_ISSET(fd, &exceptfds)) - sds[i].revents |= ADHOC_EV_ALERT; // Does Alert can be raised on revents regardless of events bitmask? - if (sds[i].revents) affectedsockets++; - } - } + int affectedsockets = 0; + if (nonblock) + affectedsockets = PollAdhocSocket(sds, count, timeout); + else + // Simulate blocking behaviour with non-blocking socket + // Borrowing some arguments to pass some parameters. The dummy WaitID(count+1) might not be unique thus have duplicate possibilities if there are multiple thread trying to poll the same numbers of socket at the same time + return WaitBlockingAdhocSocket(count+1, ADHOC_POLL_SOCKET, count, sds, nullptr, timeout, nullptr, nullptr, "adhoc pollsocket"); + // Free Network Lock //freeNetworkLock(); @@ -980,6 +1489,7 @@ int NetAdhocPdp_Delete(int id, int unknown) { // Valid Socket if (sock != NULL) { // Close Connection + shutdown(sock->id, SD_BOTH); closesocket(sock->id); // Remove Port Forward from Router @@ -1052,20 +1562,13 @@ static int sceNetAdhocctlGetAdhocId(u32 productStructAddr) { return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; } +// FIXME: Scan probably not a blocking function since there is ADHOCCTL_STATE_SCANNING state that can be polled by the game, right? int sceNetAdhocctlScan() { INFO_LOG(SCENET, "sceNetAdhocctlScan() at %08x", currentMIPS->pc); // Library initialized if (netAdhocctlInited) { - // Wait until Not connected - if (friendFinderRunning) { - int cnt = 0; - while ((adhocctlState != ADHOCCTL_STATE_DISCONNECTED) && (cnt < adhocDefaultTimeout)) { - sleep_ms(1); - cnt++; - } - } - + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { adhocctlState = ADHOCCTL_STATE_SCANNING; @@ -1079,32 +1582,18 @@ 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) { - int error = errno; 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 } - // Does Connected Event's mipscall need be executed after returning from sceNetAdhocctlScan ? - //notifyAdhocctlHandlers(ADHOCCTL_EVENT_SCAN, 0); - //hleCheckCurrentCallbacks(); - - // Wait for Status to be connected to prevent Ford Street Racing from Failed to find game session - // TODO: Do this async while Delaying HLE Result - /*if (friendFinderRunning) { - int cnt = 0; - while ((adhocctlState == ADHOCCTL_STATE_SCANNING) && (cnt < adhocDefaultTimeout)) { - sleep_ms(1); - cnt++; - } - }*/ - - //sceKernelDelayThread(adhocEventPollDelayMS * 1000); - //hleDelayResult(0, "give time to init/cleanup", adhocEventPollDelayMS * 1000); - // Return Success return 0; } @@ -1267,14 +1756,16 @@ 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); if (iResult == SOCKET_ERROR) { - ERROR_LOG(SCENET, "Socket error (%i) when sending", errno); + ERROR_LOG(SCENET, "Socket error (%i) when sending", error); + // Set Disconnected State + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; } - // Set Disconnected State - adhocctlState = ADHOCCTL_STATE_DISCONNECTED; - // Free Network Lock //_freeNetworkLock(); } @@ -1300,9 +1791,16 @@ u32 NetAdhocctl_Disconnect() { // Notify Event Handlers (even if we weren't connected, not doing this will freeze games like God Eater, which expect this behaviour) notifyAdhocctlHandlers(ADHOCCTL_EVENT_DISCONNECT, 0); - hleCheckCurrentCallbacks(); + //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) + hleDelayResult(0, "give time to init/cleanup", us); + return 0; } @@ -1517,15 +2015,6 @@ int NetAdhocctl_Create(const char* groupName) { if (netAdhocctlInited) { // Valid Argument if (validNetworkName(groupNameStruct)) { - // Wait until Not connected - if (friendFinderRunning) { - int cnt = 0; - while ((adhocctlState != ADHOCCTL_STATE_DISCONNECTED && adhocctlState != ADHOCCTL_STATE_SCANNING) && (cnt < adhocDefaultTimeout)) { - sleep_ms(1); - cnt++; - } - } - // Disconnected State, may also need to check for Scanning state to prevent some games from failing to host a game session if ((adhocctlState == ADHOCCTL_STATE_DISCONNECTED) || (adhocctlState == ADHOCCTL_STATE_SCANNING)) { // Set Network Name @@ -1549,15 +2038,18 @@ 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", errno); + ERROR_LOG(SCENET, "Socket error (%i) when sending", error); //return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // ERROR_NET_ADHOCCTL_DISCONNECTED; // ERROR_NET_ADHOCCTL_BUSY; //Faking success, to prevent Full Auto 2 from freezing while Initializing Network adhocctlState = ADHOCCTL_STATE_CONNECTED; // Notify Event Handlers, Needed for the Nickname to be shown on the screen when success is faked - // Might be better not to notify the game when faking success (failed to connect to adhoc server), at least the player will know that it failed to connect - //__UpdateAdhocctlHandlers(ADHOCCTL_EVENT_CONNECT, 0); //CoreTiming::ScheduleEvent_Threadsafe_Immediate(eventAdhocctlHandlerUpdate, join32(ADHOCCTL_EVENT_CONNECT, 0)); + // Connected Event's mipscall need be executed before returning from sceNetAdhocctlCreate (or before the next sceNet function?) + __UpdateAdhocctlHandlers(ADHOCCTL_EVENT_CONNECT, 0); //CoreTiming::ScheduleEvent_Threadsafe_Immediate(eventAdhocctlHandlerUpdate, join32(ADHOCCTL_EVENT_CONNECT, 0)); } // Free Network Lock @@ -1565,22 +2057,14 @@ int NetAdhocctl_Create(const char* groupName) { // Set HUD Connection Status //setConnectionStatus(1); - // Connected Event's mipscall need be executed before returning from sceNetAdhocctlCreate (or before the next sceNet function?) - //notifyAdhocctlHandlers(ADHOCCTL_EVENT_CONNECT, 0); - // Wait for Status to be connected to prevent Ford Street Racing from Failed to create game session - // TODO: Do this async while Delaying HLE Result - /*if (friendFinderRunning) { // This is thread-unsafe - int cnt = 0; - while ((adhocctlState != ADHOCCTL_STATE_CONNECTED) && (cnt < adhocDefaultTimeout)) { // This is thread-unsafe - sleep_ms(1); - cnt++; - } - }*/ - - //sceKernelDelayThreadCB(adhocEventDelayMS * 1000); - //hleCheckCurrentCallbacks(); - hleDelayResult(0, "give time to init/cleanup", adhocEventDelayMS * 1000); + int us = adhocEventDelayMS * 1000; + if (adhocctlState != ADHOCCTL_STATE_CONNECTED && friendFinderRunning) { + WaitAdhocctlState(ADHOCCTL_STATE_CONNECTED, us, "adhoc 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) + hleDelayResult(0, "give time to init/cleanup", us); // Return Success return 0; @@ -2006,6 +2490,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Link PTP Socket ptp[i] = internal; + ptpConnectCount[i] = 0; // Add Port Forward to Router. We may not even need to forward this local port, since PtpOpen usually have port 0 (any port) as source port and followed by PtpConnect (which mean acting as Client), right? //sceNetPortOpen("TCP", sport); @@ -2049,6 +2534,93 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, return 0; } +int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port) { + // Cast Socket + SceNetAdhocPtpStat* socket = ptp[ptpId - 1]; + + // Enable Port Re-use + setsockopt(newsocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one)); + + // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? + if (g_Config.bTCPNoDelay) setSockNoDelay(newsocket, 1); + + // Local Address Information + sockaddr_in local; + memset(&local, 0, sizeof(local)); + socklen_t locallen = sizeof(local); + + // Grab Local Address + if (getsockname(newsocket, (sockaddr*)&local, &locallen) == 0) { + // Peer MAC + SceNetEtherAddr mac; + + // Find Peer MAC + if (resolveIP(peeraddr.sin_addr.s_addr, &mac)) { + // Allocate Memory + SceNetAdhocPtpStat* internal = (SceNetAdhocPtpStat*)malloc(sizeof(SceNetAdhocPtpStat)); + + // Allocated Memory + if (internal != NULL) { + // Find Free Translator ID + int i = 0; + for (; i < 255; i++) if (ptp[i] == NULL) break; + + // Found Free Translator ID + if (i < 255) { + // Clear Memory + memset(internal, 0, sizeof(SceNetAdhocPtpStat)); + + // Copy Socket Descriptor to Structure + internal->id = newsocket; + + // Set Buffer Size + if (getSockBufferSize(newsocket, SO_RCVBUF) < socket->rcv_sb_cc) setSockBufferSize(newsocket, SO_RCVBUF, socket->rcv_sb_cc); + if (getSockBufferSize(newsocket, SO_SNDBUF) < socket->snd_sb_cc) setSockBufferSize(newsocket, SO_SNDBUF, socket->snd_sb_cc); + internal->rcv_sb_cc = socket->rcv_sb_cc; + internal->snd_sb_cc = socket->snd_sb_cc; + + // Copy Local Address Data to Structure + getLocalMac(&internal->laddr); + internal->lport = ntohs(local.sin_port) - portOffset; + + // Copy Peer Address Data to Structure + internal->paddr = mac; + internal->pport = ntohs(peeraddr.sin_port) - portOffset; + + // Set Connected State + internal->state = ADHOC_PTP_STATE_ESTABLISHED; + + // Return Peer Address Information + *addr = internal->paddr; + if (port != NULL) *port = internal->pport; + + // Link PTP Socket + ptp[i] = internal; + ptpConnectCount[i] = 0; + + // Add Port Forward to Router. Or may be doesn't need to be forwarded since local port already accessible from outside if others were able to connect & get accepted at this point, right? + //sceNetPortOpen("TCP", internal->lport); + //g_PortManager.Add(IP_PROTOCOL_TCP, internal->lport + portOffset); + + INFO_LOG(SCENET, "sceNetAdhocPtpAccept[%i->%i:%u]: Established (%s:%u)", ptpId, i + 1, internal->lport, inet_ntoa(peeraddr.sin_addr), internal->pport); + + // Return Socket + return i + 1; + } + + // Free Memory + free(internal); + } + } + } + + // Close Socket + closesocket(newsocket); + + ERROR_LOG(SCENET, "sceNetAdhocPtpAccept[%i]: Failed (Socket Closed)", ptpId); + return -1; +} + /** * Adhoc Emulator PTP Connection Acceptor * @param id Socket File Descriptor @@ -2092,13 +2664,9 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int sockaddr_in peeraddr; memset(&peeraddr, 0, sizeof(peeraddr)); socklen_t peeraddrlen = sizeof(peeraddr); - // Local Address Information - sockaddr_in local; - memset(&local, 0, sizeof(local)); - socklen_t locallen = sizeof(local); // Grab Nonblocking Flag - uint32_t nbio = getBlockingFlag(socket->id); + uint32_t nbio = getNonBlockingFlag(socket->id); // Switch to Nonblocking Behaviour if (nbio == 0) { // Overwrite Socket Option @@ -2109,27 +2677,13 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int // Accept Connection int newsocket = accept(socket->id, (sockaddr *)&peeraddr, &peeraddrlen); int error = errno; - - // Blocking Behaviour - if (!flag && newsocket == SOCKET_ERROR) { - // Get Start Time - uint32_t starttime = (uint32_t)(real_time_now()*1000000.0); - - // Retry until Timeout hits - while ((timeout == 0 || ((uint32_t)(real_time_now()*1000000.0) - starttime) < (uint32_t)timeout) && (newsocket == SOCKET_ERROR) && connectInProgress(error)) { - // Accept Connection - newsocket = accept(socket->id, (sockaddr *)&peeraddr, &peeraddrlen); - error = errno; - - // Wait a bit... - sleep_ms(1); - } - } if (newsocket == SOCKET_ERROR) { - if (flag == 0) { // Prevent spamming Debug Log with retries of non-bocking socket - DEBUG_LOG(SCENET, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", id, error); + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + return WaitBlockingAdhocSocket(socket->id, PTP_ACCEPT, id, nullptr, nullptr, timeout, addr, port, "ptp accept"); } + // Prevent spamming Debug Log with retries of non-bocking socket else { VERBOSE_LOG(SCENET, "sceNetAdhocPtpAccept[%i]: Socket Error (%i)", id, error); } @@ -2143,80 +2697,9 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int // Accepted New Connection if (newsocket > 0) { - // Enable Port Re-use - setsockopt(newsocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one)); - - // Disable Nagle Algo to send immediately. Or may be we shouldn't disable Nagle since there is PtpFlush function? - if (g_Config.bTCPNoDelay) setSockNoDelay(newsocket, 1); - - // Grab Local Address - if (getsockname(newsocket, (sockaddr *)&local, &locallen) == 0) { - // Peer MAC - SceNetEtherAddr mac; - - // Find Peer MAC - if (resolveIP(peeraddr.sin_addr.s_addr, &mac)) { - // Allocate Memory - SceNetAdhocPtpStat * internal = (SceNetAdhocPtpStat *)malloc(sizeof(SceNetAdhocPtpStat)); - - // Allocated Memory - if (internal != NULL) { - // Find Free Translator ID - int i = 0; - for (; i < 255; i++) if (ptp[i] == NULL) break; - - // Found Free Translator ID - if (i < 255) { - // Clear Memory - memset(internal, 0, sizeof(SceNetAdhocPtpStat)); - - // Copy Socket Descriptor to Structure - internal->id = newsocket; - - // Set Buffer Size - if (getSockBufferSize(newsocket, SO_RCVBUF) < socket->rcv_sb_cc) setSockBufferSize(newsocket, SO_RCVBUF, socket->rcv_sb_cc); - if (getSockBufferSize(newsocket, SO_SNDBUF) < socket->snd_sb_cc) setSockBufferSize(newsocket, SO_SNDBUF, socket->snd_sb_cc); - internal->rcv_sb_cc = socket->rcv_sb_cc; - internal->snd_sb_cc = socket->snd_sb_cc; - - // Copy Local Address Data to Structure - getLocalMac(&internal->laddr); - internal->lport = ntohs(local.sin_port) - portOffset; - - // Copy Peer Address Data to Structure - internal->paddr = mac; - internal->pport = ntohs(peeraddr.sin_port) - portOffset; - - // Set Connected State - internal->state = ADHOC_PTP_STATE_ESTABLISHED; - - // Return Peer Address Information - *addr = internal->paddr; - if (port != NULL) *port = internal->pport; - - // Link PTP Socket - ptp[i] = internal; - - // Add Port Forward to Router. Or may be doesn't need to be forwarded since local port already accessible from outside if others were able to connect & get accepted at this point, right? - //sceNetPortOpen("TCP", internal->lport); - //g_PortManager.Add(IP_PROTOCOL_TCP, internal->lport + portOffset); - - INFO_LOG(SCENET, "sceNetAdhocPtpAccept[%i->%i:%u]: Established (%s:%u)", id, i+1, internal->lport, inet_ntoa(peeraddr.sin_addr), internal->pport); - - // Return Socket - return i + 1; - } - - // Free Memory - free(internal); - } - } - } - - // Close Socket - closesocket(newsocket); - - ERROR_LOG(SCENET, "sceNetAdhocPtpAccept[%i]: Failed (Socket Closed)", id); + int newid = AcceptPtpSocket(id, newsocket, peeraddr, addr, port); + if (newid >= 0) + return newid; } // Action would block @@ -2284,17 +2767,15 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { sin.sin_port = htons(socket->pport + ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset)); // Grab Nonblocking Flag - uint32_t nbio = getBlockingFlag(socket->id); + //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 :( - /*if (nbio == 0) - { - // Overwrite Socket Option - changeBlockingMode(socket->id, 1); - }*/ + // We are using non-blocking to simulate blocking + changeBlockingMode(socket->id, 1); - // Connect Socket to Peer (Nonblocking) + // 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) int connectresult = connect(socket->id, (sockaddr *)&sin, sizeof(sin)); // Grab Error Code @@ -2303,16 +2784,10 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { if (connectresult == SOCKET_ERROR) { ERROR_LOG(SCENET, "sceNetAdhocPtpConnect[%i]: Socket Error (%i) to %s:%u", id, errorcode, inet_ntoa(sin.sin_addr), socket->pport); } - - // Restore Blocking Behaviour - if (nbio == 0) - { - // Restore Socket Option - changeBlockingMode(socket->id, 0); - } - + // Instant Connection (Lucky!) - if (connectresult == 0 || (connectresult == SOCKET_ERROR && (errorcode == EISCONN))) { + if (connectresult != SOCKET_ERROR || errorcode == EISCONN) { + ptpConnectCount[id - 1]++; // Set Connected State socket->state = ADHOC_PTP_STATE_ESTABLISHED; @@ -2323,39 +2798,16 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { // Connection in Progress else if (connectresult == SOCKET_ERROR && connectInProgress(errorcode)) { - // Nonblocking Mode - if (flag) { + ptpConnectCount[id - 1]++; + // Nonblocking Mode. First attempt need to be blocking for GvG Next Plus to work, even though it used non-blocking flag but only try to connect once per socket, which mean treating it just like blocking socket instead of non-blocking :( + if (flag && ptpConnectCount[id - 1] > 1) { //if (errorcode == EALREADY) return ERROR_NET_ADHOC_BUSY; return ERROR_NET_ADHOC_WOULD_BLOCK; } // Blocking Mode else { - // Grab Connection Start Time - uint32_t starttime = (uint32_t)(real_time_now()*1000000.0); - - // Peer Information (for Connection-Polling) - sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); - socklen_t peerlen = sizeof(peer); - // Wait for Connection - while ((timeout == 0 || ( (uint32_t)(real_time_now()*1000000.0) - starttime) < (uint32_t)timeout) && getpeername(socket->id, (sockaddr *)&peer, &peerlen) != 0) { - // Wait 1ms - sleep_ms(1); - } - - // Connected in Time - if (sin.sin_addr.s_addr == peer.sin_addr.s_addr/* && sin.sin_port == peer.sin_port*/) { - // Set Connected State - socket->state = ADHOC_PTP_STATE_ESTABLISHED; - - INFO_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Established (%s:%u)", id, socket->lport, inet_ntoa(peer.sin_addr), socket->pport); - - // Success - return 0; - } - - // Timeout occured - return ERROR_NET_ADHOC_CONNECTION_REFUSED; // ERROR_NET_ADHOC_TIMEOUT; + // Simulate blocking behaviour with non-blocking socket + return WaitBlockingAdhocSocket(socket->id, PTP_CONNECT, id, nullptr, nullptr, timeout, nullptr, nullptr, "ptp connect"); } } } @@ -2387,6 +2839,7 @@ int NetAdhocPtp_Close(int id, int unknown) { SceNetAdhocPtpStat* socket = ptp[id - 1]; // Close Connection + shutdown(socket->id, SD_BOTH); closesocket(socket->id); // Remove Port Forward from Router @@ -2398,6 +2851,7 @@ int NetAdhocPtp_Close(int id, int unknown) { // Free Reference ptp[id - 1] = NULL; + ptpConnectCount.erase(id - 1); // Success return 0; @@ -2538,6 +2992,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Link PTP Socket ptp[i] = internal; + ptpConnectCount[i] = 0; // Add Port Forward to Router //sceNetPortOpen("TCP", sport); @@ -2627,10 +3082,9 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // _acquireNetworkLock(); // Send Data - //changeBlockingMode(socket->id, flag); + changeBlockingMode(socket->id, 1); int sent = send(socket->id, data, *len, 0); int error = errno; - changeBlockingMode(socket->id, 0); // Free Network Lock // _freeNetworkLock(); @@ -2640,7 +3094,7 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Save Length *len = sent; - DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u", id, socket->lport, sent, mac2str(&socket->paddr).c_str(), socket->pport); + DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, socket->lport, sent, mac2str(&socket->paddr).c_str(), socket->pport); // Return Success return 0; @@ -2648,11 +3102,12 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Non-Critical Error else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ETIMEDOUT)) { - // Blocking Situation - if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; + // Non-Blocking + if (flag) + return ERROR_NET_ADHOC_WOULD_BLOCK; - // Timeout - return ERROR_NET_ADHOC_TIMEOUT; + // Simulate blocking behaviour with non-blocking socket + return WaitBlockingAdhocSocket(socket->id, PTP_SEND, id, (void*)data, len, timeout, nullptr, nullptr, "ptp send"); } // Change Socket State @@ -2722,25 +3177,21 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou received = recv(socket->id, (char*)buf, *len, 0); error = errno; - if (flag == 0) { - // Simulate blocking behaviour with non-blocking socket - uint32_t starttime = (uint32_t)(real_time_now() * 1000000.0); - // Wait for Connection - while ((timeout == 0 || ((uint32_t)(real_time_now() * 1000000.0) - starttime) < (uint32_t)timeout) && (received == SOCKET_ERROR) && connectInProgress(error)) { - received = recv(socket->id, (char*)buf, *len, 0); - error = errno; - // Wait 1ms - sleep_ms(1); - } - } - if (received == SOCKET_ERROR) { + if (flag == 0) { + // Simulate blocking behaviour with non-blocking socket + return WaitBlockingAdhocSocket(socket->id, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv"); + } + 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(); + + // Should we set output length to 0 on Error? + *len = 0; // Received Data if (received > 0) { @@ -2753,7 +3204,7 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); peerlock.unlock(); - DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u", id, socket->lport, received, mac2str(&socket->paddr).c_str(), socket->pport); + DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, socket->lport, received, mac2str(&socket->paddr).c_str(), socket->pport); // Return Success return 0; @@ -2765,14 +3216,15 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; // Timeout - return ERROR_NET_ADHOC_TIMEOUT; + return hleLogError(SCENET, ERROR_NET_ADHOC_TIMEOUT, "ptp recv timeout"); } - + DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, socket->lport, received, error); + // Change Socket State socket->state = ADHOC_PTP_STATE_CLOSED; // Disconnected - return ERROR_NET_ADHOC_DISCONNECTED; + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "ptp recv disconnected"); } // Invalid Arguments @@ -2820,10 +3272,10 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { setSockNoDelay(socket->id, 1); // Send Empty Data just to trigger Nagle on/off effect to flush the send buffer, Do we need to trigger this at all or is it automatically flushed? - changeBlockingMode(socket->id, nonblock); + //changeBlockingMode(socket->id, nonblock); int sent = send(socket->id, 0, 0, 0); int error = errno; - changeBlockingMode(socket->id, 0); + //changeBlockingMode(socket->id, 1); // Restore/Enable Nagle Algo setSockNoDelay(socket->id, n); @@ -2982,6 +3434,14 @@ int NetAdhocMatching_Delete(int matchingId) { free(item); item = NULL; + // Making sure there are no leftover matching events from this session which could cause a crash on the next session + for (auto it = matchingEvents.begin(); it != matchingEvents.end(); ) { + if (it->data[0] == matchingId) + it = matchingEvents.erase(it); + else + ++it; + } + // Stop Search break; } @@ -3045,7 +3505,7 @@ int NetAdhocMatching_Term() { } int sceNetAdhocMatchingTerm() { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingTerm()"); + WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingTerm() at %08x", currentMIPS->pc); // Should we cleanup all created matching contexts first? just in case there are games that doesn't delete them before calling this NetAdhocMatching_Term(); @@ -3072,14 +3532,6 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle if (rxbuflen >= 1) { //1024 //200 on DBZ Shin Budokai 2 // Valid Arguments if (mode >= 1 && mode <= 3) { - // Wait until Adhoc is fully connected (mipscall of ADHOCCTL_EVENT_CONNECT event is fully executed ?) - /*if (friendFinderRunning) { - int cnt = 0; - while ((threadStatus != ADHOCCTL_STATE_CONNECTED) && (cnt < adhocDefaultTimeout)) { - sleep_ms(1); - cnt++; - } - }*/ // Iterate Matching Contexts SceNetAdhocMatchingContext * item = contexts; @@ -3248,8 +3700,9 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int // Multithreading Unlock peerlock.unlock(); - sleep_ms(adhocMatchingEventDelayMS); - //hleDelayResult(0, "give some time", adhocMatchingEventDelayMS * 1000); // Give a little time to make sure matching Threads are ready before the game use the next sceNet functions, should've checked for status instead of guessing the time? + // Give a little time to make sure matching Threads are ready before the game use the next sceNet functions, should've checked for status instead of guessing the time? + //sleep_ms(adhocMatchingEventDelayMS); + hleDelayResult(0, "give some time", adhocMatchingEventDelayMS * 1000); return 0; } @@ -4127,20 +4580,16 @@ void __NetMatchingCallbacks() //(int matchingId) u32_le* args = (u32_le*)&(*params); //auto context = findMatchingContext(args[0]); - //if (/*__KernelGetCurThread() == context->matching_thid &&*/ (!__IsInInterrupt() && __KernelIsDispatchEnabled() && !__KernelInCallback()) /*&& !IsMatchingInCallback(context)*/) - { - if (actionAfterMatchingMipsCall < 0) { - actionAfterMatchingMipsCall = __KernelRegisterActionType(AfterMatchingMipsCall::Create); - } - DEBUG_LOG(SCENET, "AdhocMatchingCallback: [ID=%i][EVENT=%i][%s]", args[0], args[1], mac2str((SceNetEtherAddr*)Memory::GetPointer(args[2])).c_str()); - AfterMatchingMipsCall* after = (AfterMatchingMipsCall*)__KernelCreateAction(actionAfterMatchingMipsCall); - after->SetData(args[0], args[1], args[2]); - //SetMatchingInCallback(context, true); - //__KernelDirectMipsCall(args[5], after, args, 5, true); - hleEnqueueCall(args[5], 5, args, after); - matchingEvents.pop_front(); - delayus = (adhocMatchingEventDelayMS + 2*adhocExtraPollDelayMS) * 1000; // Added an extra delay to prevent I/O Timing method from causing disconnection + if (actionAfterMatchingMipsCall < 0) { + actionAfterMatchingMipsCall = __KernelRegisterActionType(AfterMatchingMipsCall::Create); } + + DEBUG_LOG(SCENET, "AdhocMatchingCallback: [ID=%i][EVENT=%i][%s]", args[0], args[1], mac2str((SceNetEtherAddr*)Memory::GetPointer(args[2])).c_str()); + AfterMatchingMipsCall* after = (AfterMatchingMipsCall*)__KernelCreateAction(actionAfterMatchingMipsCall); + after->SetData(args[0], args[1], args[2]); + hleEnqueueCall(args[5], 5, args, after); + matchingEvents.pop_front(); + delayus = (adhocMatchingEventDelayMS + 2 * adhocExtraPollDelayMS) * 1000; // Added an extra delay to prevent I/O Timing method from causing disconnection } // Must be delayed long enough whenever there is a pending callback. Should it be 10-100ms for Matching Events? or Not Less than the delays on sceNetAdhocMatching HLE? @@ -5586,7 +6035,7 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi // Multithreading Lock peerlock.lock(); // Cast Context - SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); + SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); // Multithreading Unlock peerlock.unlock(); @@ -5598,6 +6047,10 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi u64_le now; + static SceNetEtherAddr sendermac; + static uint16_t senderport; + static int rxbuflen; + // Log Startup INFO_LOG(SCENET, "InputLoop: Begin of InputLoop[%i] Thread", matchingId); @@ -5690,9 +6143,8 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi } // Receive PDP Datagram - SceNetEtherAddr sendermac; - uint16_t senderport; - int rxbuflen = context->rxbuflen; + rxbuflen = context->rxbuflen; + senderport = 0; context->socketlock->lock(); int recvresult = sceNetAdhocPdpRecv(context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); @@ -5714,6 +6166,9 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi DEBUG_LOG(SCENET, "Timestamp Delta: %llu (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); if (/*context->rxbuf[0] > 0 &&*/ peer->last_recv != 0) peer->last_recv = now - 1; // - context->keepalive_int; // May need to deduce by ping interval to prevent Dissidia 012 unable to see other players (ie. disappearing issue) } + else { + WARN_LOG(SCENET, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen); + } peerlock.unlock(); // Ping Packet diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index bcd3ad6c73..08583433de 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -17,6 +17,7 @@ #pragma once +#include #include "Core/HLE/proAdhoc.h" #ifdef _MSC_VER @@ -29,6 +30,39 @@ typedef struct MatchingArgs { #pragma pack(pop) #endif +struct AdhocSendTarget { + u32 ip; + u16 port; // original port +}; + +struct AdhocSendTargets { + int length; + std::deque peers; + bool isBroadcast; +}; + +struct AdhocSocketRequest { + int type; + int id; // PDP/PTP socket id + void* buffer; + s32_le* length; + u32 timeout; + u64 startTime; + struct SceNetEtherAddr* remoteMAC; + u16_le* remotePort; +}; + +enum AdhocSocketRequestType : int +{ + PTP_CONNECT = 0, + PTP_ACCEPT = 1, + PTP_SEND = 2, + PTP_RECV = 3, + PDP_SEND = 4, + PDP_RECV = 5, + ADHOC_POLL_SOCKET = 6, +}; + class PointerWrap; void Register_sceNetAdhoc();