diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index c408aa8b52..416716561a 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -21,6 +21,10 @@ // This is a direct port of Coldbird's code from http://code.google.com/p/aemu/ // All credit goes to him! +#if defined(_WIN32) +#include "Common/CommonWindows.h" +#endif + #if !defined(_WIN32) #include #include @@ -62,6 +66,7 @@ SceNetAdhocctlPeerInfo * friends = NULL; SceNetAdhocctlScanInfo * networks = NULL; SceNetAdhocctlScanInfo * newnetworks = NULL; u64 adhocctlStartTime = 0; +bool isAdhocctlBusy = false; int adhocctlState = ADHOCCTL_STATE_DISCONNECTED; int adhocctlCurrentMode = ADHOCCTL_MODE_NONE; int adhocConnectionType = ADHOC_CONNECT; @@ -315,7 +320,7 @@ void changeBlockingMode(int fd, int nonblocking) { #endif } -int countAvailableNetworks() { +int countAvailableNetworks(const bool excludeSelf) { // Network Count int count = 0; @@ -323,7 +328,7 @@ int countAvailableNetworks() { SceNetAdhocctlScanInfo * group = networks; // Count Groups - for (; group != NULL; group = group->next) count++; + for (; group != NULL && (!excludeSelf || !isLocalMAC(&group->bssid.mac_addr)); group = group->next) count++; // Return Network Count return count; @@ -1165,6 +1170,7 @@ void AfterAdhocMipsCall::run(MipsCall& call) { u32 v0 = currentMIPS->r[MIPS_REG_V0]; if (__IsInInterrupt()) ERROR_LOG(SCENET, "AfterAdhocMipsCall::run [ID=%i][Event=%d] is Returning Inside an Interrupt!", HandlerID, EventID); SetAdhocctlInCallback(false); + isAdhocctlBusy = false; DEBUG_LOG(SCENET, "AfterAdhocMipsCall::run [ID=%i][Event=%d] [cbId: %u][retV0: %08x]", HandlerID, EventID, call.cbId, v0); //call.setReturnValue(v0); } @@ -1334,6 +1340,10 @@ int friendFinder(){ if (initNetwork(&product_code) == 0) { networkInited = true; INFO_LOG(SCENET, "FriendFinder: Network [RE]Initialized"); + // At this point we are most-likely not in a Group within the Adhoc Server, so we should probably reset AdhocctlState + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + netAdhocGameModeEntered = false; + isAdhocctlBusy = false; } else { networkInited = false; @@ -1395,6 +1405,12 @@ int friendFinder(){ } } + // Calculate EnterGameMode Timeout to prevent waiting forever for disconnected players + if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered && static_cast(now - adhocctlStartTime) > netAdhocEnterGameModeTimeout) { + netAdhocGameModeEntered = false; + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOC_TIMEOUT); + } + // Handle Packets if (rxpos > 0) { // BSSID Packet @@ -1419,14 +1435,14 @@ int friendFinder(){ }) == gameModeMacs.end()) { // Arrange the order to be consistent on all players (Host on top), Starting from our self the rest of new players will be added to the back gameModeMacs.push_back(localMac); + + // FIXME: OPCODE_CONNECT_BSSID only triggered once, but the timing of ADHOCCTL_EVENT_GAME notification could be too soon, since there could be more players that need to join before the event should be notified + if (netAdhocGameModeEntered && gameModeMacs.size() >= requiredGameModeMacs.size()) { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); + } } else WARN_LOG(SCENET, "GameMode SelfMember [%s] Already Existed!", mac2str(&localMac).c_str()); - - if (gameModeMacs.size() >= requiredGameModeMacs.size()) { - //adhocctlState = ADHOCCTL_STATE_GAMEMODE; - notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); - } } else { //adhocctlState = ADHOCCTL_STATE_CONNECTED; @@ -1521,17 +1537,16 @@ int friendFinder(){ it = gameModeMacs.begin() + 1; gameModeMacs.insert(it, packet->mac); } + + // From JPCSP: Join complete when all the required MACs have joined + if (netAdhocGameModeEntered && requiredGameModeMacs.size() > 0 && gameModeMacs.size() == requiredGameModeMacs.size()) { + // TODO: Should we replace gameModeMacs contents with requiredGameModeMacs contents to make sure they are in the same order with macs from sceNetAdhocctlCreateEnterGameMode? But may not be consistent with the list on client side! + //gameModeMacs = requiredGameModeMacs; + notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); + } } else WARN_LOG(SCENET, "GameMode Member [%s] Already Existed!", mac2str(&packet->mac).c_str()); - - // From JPCSP: Join complete when all the required MACs have joined - if (requiredGameModeMacs.size() > 0 && gameModeMacs.size() >= requiredGameModeMacs.size()) { - // TODO: Should we replace gameModeMacs contents with requiredGameModeMacs contents to make sure they are in the same order with macs from sceNetAdhocctlCreateEnterGameMode? But may not be consistent with the list on client side! - //gameModeMacs = requiredGameModeMacs; - //adhocctlState = ADHOCCTL_STATE_GAMEMODE; - notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); - } } // Update HUD User Count @@ -1572,13 +1587,14 @@ int friendFinder(){ // Log Incoming Peer Delete Request INFO_LOG(SCENET, "FriendFinder: Incoming Peer Data Delete Request..."); - /*if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { + if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { auto peer = findFriendByIP(packet->ip); - gameModeMacs.erase(std::remove_if(gameModeMacs.begin(), gameModeMacs.end(), - [peer](SceNetEtherAddr const& e) { - return isMacMatch(&e, &peer->mac_addr); - }), gameModeMacs.end()); - }*/ + for (auto& gma : replicaGameModeAreas) + if (isMacMatch(&gma.mac, &peer->mac_addr)) { + gma.updateTimestamp = 0; + break; + } + } // Delete User by IP, should delete by MAC since IP can be shared (behind NAT) isn't? deleteFriendByIP(packet->ip); @@ -1633,7 +1649,8 @@ int friendFinder(){ // Set group parameters // Since 0 is not a valid active channel we fake the channel for Automatic Channel (JPCSP use 11 as default). Ridge Racer 2 will ignore any groups with channel 0 or that doesn't matched with channel value returned from sceUtilityGetSystemParamInt (which mean sceUtilityGetSystemParamInt must not return channel 0 when connected to a network?) group->channel = parameter.channel; //(parameter.channel == PSP_SYSTEMPARAM_ADHOC_CHANNEL_AUTOMATIC) ? defaultWlanChannel : parameter.channel; - group->mode = adhocctlCurrentMode; + // This Mode should be a valid mode (>=0), probably should be sent by AdhocServer since there are 2 possibilities (Normal and GameMode). Air Conflicts - Aces Of World War 2 (which use GameMode) seems to relies on this Mode value. + group->mode = std::max(ADHOCCTL_MODE_NORMAL, adhocctlCurrentMode); // default to ADHOCCTL_MODE_NORMAL // Link into Group List newnetworks = group; diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 9edc7d89e2..c90a3f9eea 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -114,9 +114,15 @@ inline bool isDisconnected(int errcode) { return (errcode == EPIPE || errcode == #define ADHOC_GAMEMODE_PORT 31000 #define GAMEMODE_UPDATE_INTERVAL 500 // 12000 usec on JPCSP, but lower value works better on BattleZone (in order to get full speed 60 FPS) #define GAMEMODE_INIT_DELAY 10000 +#define GAMEMODE_SYNC_TIMEOUT 250000 + +// GameMode Type +#define ADHOCCTL_GAMETYPE_1A 1 +#define ADHOCCTL_GAMETYPE_1B 2 +#define ADHOCCTL_GAMETYPE_2A 3 // psp strutcs and definitions -#define ADHOCCTL_MODE_NONE -1 +#define ADHOCCTL_MODE_NONE -1 // We only use this internally as initial value before attempting to create/connect/join/scan any group #define ADHOCCTL_MODE_NORMAL 0 // ADHOCCTL_MODE_ADHOC #define ADHOCCTL_MODE_GAMEMODE 1 @@ -141,7 +147,7 @@ inline bool isDisconnected(int errcode) { return (errcode == EPIPE || errcode == // ProductType ( extracted from SSID along with ProductId & GroupName, Pattern = "PSP_([AXS])(.........)_([LG])_(.*)" ) #define PSP_ADHOCCTL_TYPE_COMMERCIAL 0 #define PSP_ADHOCCTL_TYPE_DEBUG 1 -#define PSP_ADHOCCTL_TYPE_SYSTEM 2 +#define PSP_ADHOCCTL_TYPE_SYSTEM 2 // Used for GameSharing? // Kernel Utility Netconf Adhoc Types #define UTILITY_NETCONF_TYPE_CONNECT_ADHOC 2 @@ -321,6 +327,7 @@ typedef struct GameModeArea { //int socket; // PDP socket? u64 updateTimestamp; int dataUpdated; + int dataSent; SceNetEtherAddr mac; u8* data; // upto "size" bytes started from "addr" ? } PACK GameModeArea; @@ -393,8 +400,8 @@ typedef struct SceNetAdhocGameModeBufferStat { // Adhoc ID (Game Product Key) #define ADHOCCTL_ADHOCID_LEN 9 typedef struct SceNetAdhocctlAdhocId { - s32_le type; - uint8_t data[ADHOCCTL_ADHOCID_LEN]; + s32_le type; // Air Conflicts - Aces Of World War 2 is using 2 for GameSharing? + uint8_t data[ADHOCCTL_ADHOCID_LEN]; // Air Conflicts - Aces Of World War 2 is using "000000001" for GameSharing? uint8_t padding[3]; } PACK SceNetAdhocctlAdhocId; // should this be packed? #ifdef _MSC_VER @@ -913,6 +920,7 @@ extern bool friendFinderRunning; extern SceNetAdhocctlPeerInfo * friends; extern SceNetAdhocctlScanInfo * networks; extern u64 adhocctlStartTime; +extern bool isAdhocctlBusy; extern int adhocctlState; extern int adhocctlCurrentMode; extern int adhocConnectionType; @@ -1025,7 +1033,7 @@ void changeBlockingMode(int fd, int nonblocking); * Count Virtual Networks by analyzing the Friend List * @return Number of Virtual Networks */ -int countAvailableNetworks(); +int countAvailableNetworks(const bool excludeSelf = false); /* * Find an existing group in networks diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 15e5179d5e..448b0ba66e 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -676,8 +676,6 @@ static u32 sceWlanGetEtherAddr(u32 addrAddr) { return hleLogError(SCENET, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "illegal address"); } - // FIXME: Return 0x80410180 (pspnet[_core] error code?) when Adhocctl not connected to a group (ie. ADHOCCTL_STATE_DISCONNECTED)? - u8 *addr = Memory::GetPointer(addrAddr); if (PPSSPP_ID > 1) { Memory::Memset(addrAddr, PPSSPP_ID, 6); @@ -697,6 +695,10 @@ static u32 sceWlanGetEtherAddr(u32 addrAddr) { } static u32 sceNetGetLocalEtherAddr(u32 addrAddr) { + // FIXME: Return 0x80410180 (pspnet[_core] error code?) before successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode == ADHOCCTL_MODE_NONE) + if (adhocctlCurrentMode == ADHOCCTL_MODE_NONE) + return hleLogDebug(SCENET, 0x80410180, "address not available?"); + return sceWlanGetEtherAddr(addrAddr); } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 0ec8c85ecf..ccfff8378a 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -65,7 +65,8 @@ bool netAdhocInited; bool netAdhocctlInited; bool networkInited = false; -bool netAdhocGameModeEntered; +bool netAdhocGameModeEntered = false; +int netAdhocEnterGameModeTimeout = 15000000; // 15 sec as default timeout, to wait for all players to join bool netAdhocMatchingInited; int netAdhocMatchingStarted = 0; @@ -74,7 +75,7 @@ int adhocDefaultDelay = 10000; //10000 int adhocExtraDelay = 20000; //20000 int adhocEventPollDelay = 100000; //100000; // Same timings with PSP_ADHOCCTL_RECV_TIMEOUT ? int adhocMatchingEventDelay = 30000; //30000 -int adhocEventDelay = 1000000; //1000000 +int adhocEventDelay = 2000000; //2000000 on real PSP ? u32 defaultLastRecvDelta = 10000; //10000 usec worked well for games published by Falcom (ie. Ys vs Sora Kiseki, Vantage Master Portable) SceUID threadAdhocID; @@ -105,6 +106,7 @@ 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 nonblock); int FlushPtpSocket(int socketId); +int NetAdhocGameMode_DeleteMaster(); int NetAdhocctl_ExitGameMode(); static int sceNetAdhocPdpSend(int id, const char* mac, u32 port, void* data, int len, int timeout, int flag); static int sceNetAdhocPdpRecv(int id, void* addr, void* port, void* buf, void* dataLength, u32 timeout, int flag); @@ -146,37 +148,79 @@ static void __GameModeNotify(u64 userdata, int cyclesLate) { SceUID threadID = userdata >> 32; int uid = (int)(userdata & 0xFFFFFFFF); - // TODO: May be we should check for timeout too? since EnterGammeMode have timeout arg if (IsGameModeActive()) { auto sock = adhocSockets[gameModeSocket - 1]; - // Send Master data - if (masterGameModeArea.dataUpdated) { - for (auto& gma : replicaGameModeAreas) { - if (IsGameModeActive() && IsSocketReady(sock->data.pdp.id, false, true) > 0) { - int sent = sceNetAdhocPdpSend(gameModeSocket, (const char*)&gma.mac, ADHOC_GAMEMODE_PORT, masterGameModeArea.data, masterGameModeArea.size, 0, ADHOC_F_NONBLOCK); - if (sent >= 0) { - masterGameModeArea.dataUpdated = 0; - DEBUG_LOG(SCENET, "GameMode Sent %d bytes to %s", masterGameModeArea.size, mac2str(&gma.mac).c_str()); + // Need to make sure all replicas have been created before we start syncing data + if (replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + // Send Master data + if (masterGameModeArea.dataUpdated) { + int sentcount = 0; + for (auto& gma : replicaGameModeAreas) { + if (!gma.dataSent && IsSocketReady(sock->data.pdp.id, false, true) > 0) { + int sent = sceNetAdhocPdpSend(gameModeSocket, (const char*)&gma.mac, ADHOC_GAMEMODE_PORT, masterGameModeArea.data, masterGameModeArea.size, 0, ADHOC_F_NONBLOCK); + if (sent != ERROR_NET_ADHOC_WOULD_BLOCK) { + gma.dataSent = 1; + DEBUG_LOG(SCENET, "GameMode: Master data Sent %d bytes to Area #%d [%s]", masterGameModeArea.size, gma.id, mac2str(&gma.mac).c_str()); + sentcount++; + } + } + else if (gma.dataSent) sentcount++; + } + if (sentcount == replicaGameModeAreas.size()) + masterGameModeArea.dataUpdated = 0; + } + // Need to sync (send + recv) all players initial data (data from CreateMaster) after Master + All Replicas are created, and before the first UpdateMaster / UpdateReplica is called for Star Wars The Force Unleashed to show the correct players color on minimap (also prevent Starting issue on other GameMode games) + else { + u32 error; + SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); + if (error == 0 && waitID == sock->data.pdp.id) { + // Resume thread after all replicas data have been received + int recvd = 0; + for (auto& gma : replicaGameModeAreas) { + // Either replicas new data has been received or that player has been disconnected + if (gma.dataUpdated || gma.updateTimestamp == 0) + recvd++; + } + // Resume blocked thread + u64 now = CoreTiming::GetGlobalTimeUsScaled(); + if (recvd == replicaGameModeAreas.size()) { + u32 waitVal = __KernelGetWaitValue(threadID, error); + if (error == 0) { + DEBUG_LOG(SCENET, "GameMode: Resuming Thread %d after Master data Synced (Result = %08x)", threadID, waitVal); + __KernelResumeThreadFromWait(threadID, waitVal); + } + else + ERROR_LOG(SCENET, "GameMode: Error (%08x) on WaitValue %d ThreadID %d", error, waitVal, threadID); + } + // Attempt to Re-Send initial Master data (in case previous packets were lost) + else if (static_cast(now - masterGameModeArea.updateTimestamp) > GAMEMODE_SYNC_TIMEOUT) { + DEBUG_LOG(SCENET, "GameMode: Attempt to Re-Send Master data after Sync Timeout (%d us)", GAMEMODE_SYNC_TIMEOUT); + // Reset Sent marker on players who haven't replied yet (except disconnected players) + for (auto& gma : replicaGameModeAreas) + if (!gma.dataUpdated && gma.updateTimestamp != 0) + gma.dataSent = 0; + masterGameModeArea.updateTimestamp = now; + masterGameModeArea.dataUpdated = 1; } } } - } - // Recv new Replica data. We shouldn't do too much activity on a single event and just try again later after a short delay (1ms or lower, to prevent long sync with max players) - if (IsGameModeActive() && IsSocketReady(sock->data.pdp.id, true, false) > 0) { - SceNetEtherAddr sendermac; - s32_le senderport = ADHOC_GAMEMODE_PORT; - s32_le bufsz = uid; // GAMEMODE_BUFFER_SIZE; - int ret = sceNetAdhocPdpRecv(gameModeSocket, &sendermac, &senderport, gameModeBuffer, &bufsz, 0, ADHOC_F_NONBLOCK); - if (ret >= 0 && bufsz > 0) { - for (auto& gma : replicaGameModeAreas) { - if (IsMatch(gma.mac, sendermac)) { - DEBUG_LOG(SCENET, "GameMode Received %d bytes of new Data for Area [%s]", bufsz, mac2str(&sendermac).c_str()); - memcpy(gma.data, gameModeBuffer, std::min(gma.size, bufsz)); - gma.dataUpdated = 1; - gma.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); - break; + // Recv new Replica data when available + if (IsSocketReady(sock->data.pdp.id, true, false) > 0) { + SceNetEtherAddr sendermac; + s32_le senderport = ADHOC_GAMEMODE_PORT; + s32_le bufsz = uid; // GAMEMODE_BUFFER_SIZE; + int ret = sceNetAdhocPdpRecv(gameModeSocket, &sendermac, &senderport, gameModeBuffer, &bufsz, 0, ADHOC_F_NONBLOCK); + if (ret >= 0 && bufsz > 0) { + for (auto& gma : replicaGameModeAreas) { + if (IsMatch(gma.mac, sendermac)) { + DEBUG_LOG(SCENET, "GameMode: Replica data Received %d bytes for Area #%d [%s]", bufsz, gma.id, mac2str(&sendermac).c_str()); + memcpy(gma.data, gameModeBuffer, std::min(gma.size, bufsz)); + gma.dataUpdated = 1; + gma.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); + break; + } } } } @@ -286,6 +330,12 @@ static void __AdhocctlState(u64 userdata, int cyclesLate) { u32 waitVal = __KernelGetWaitValue(threadID, error); if (error == 0) { adhocctlState = waitVal; + // FIXME: It seems Adhocctl is still busy within the Adhocctl Handler function (ie. during callbacks), + // so we should probably set isAdhocctlBusy to false after mispscall are fully executed (ie. in afterAction). + // But since Adhocctl Handler is optional, there might be cases where there are no handler thus no callback/mipcall being triggered, + // so we should probably need to set isAdhocctlBusy to false here too as a workaround (or may be there is internal handler by default?) + if (adhocctlHandlers.empty()) + isAdhocctlBusy = false; } __KernelResumeThreadFromWait(threadID, result); @@ -343,6 +393,13 @@ int StartGameModeScheduler(int bufSize) { } int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { + auto sock = adhocSockets[req.id - 1]; + if (sock->flags & ADHOC_F_ALERTRECV) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTRECV; + return 0; + } + sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); @@ -354,7 +411,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { if (ret >= 0 && ret <= *req.length) { ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_NOSIGNAL, (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) { + 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 @@ -388,14 +445,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { // 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) { u64 now = (u64)(time_now_d() * 1000000.0); - auto sock = adhocSockets[req.id - 1]; - if (sock->flags & ADHOC_F_ALERTRECV) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTRECV; - sock->alerted_flags |= ADHOC_F_ALERTRECV; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { // Try again later return -1; } @@ -403,7 +453,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { result = ERROR_NET_ADHOC_TIMEOUT; } else - result = ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED + result = ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED if (ret == SOCKET_ERROR) DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, sockerr); @@ -414,6 +464,11 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { auto sock = adhocSockets[req.id - 1]; auto& pdpsocket = sock->data.pdp; + if (sock->flags & ADHOC_F_ALERTSEND) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTSEND; + return 0; + } result = 0; bool retry = false; @@ -435,14 +490,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa else { if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTSEND) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTSEND; - sock->alerted_flags |= ADHOC_F_ALERTSEND; - break; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { retry = true; } else @@ -465,6 +513,11 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTSEND) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTSEND; + return 0; + } // Send Data int ret = send(uid, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); @@ -482,13 +535,7 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTSEND) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTSEND; - sock->alerted_flags |= ADHOC_F_ALERTSEND; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else @@ -511,6 +558,11 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTRECV) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTRECV; + return 0; + } int ret = recv(uid, (char*)req.buffer, *req.length, MSG_NOSIGNAL); int sockerr = errno; @@ -531,13 +583,7 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTRECV) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTRECV; - sock->alerted_flags |= ADHOC_F_ALERTRECV; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else @@ -560,6 +606,12 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTACCEPT) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTACCEPT; + return 0; + } + sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); @@ -581,13 +633,7 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == 0 || (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK))) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTACCEPT) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTACCEPT; - sock->alerted_flags |= ADHOC_F_ALERTACCEPT; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else { @@ -606,8 +652,13 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; - int sockerr; + if (sock->flags & ADHOC_F_ALERTCONNECT) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTCONNECT; + return 0; + } + int sockerr; // Wait for Connection (assuming "connect" has been called before) int ret = IsSocketReady(uid, false, true, &sockerr); @@ -629,13 +680,7 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { // Timeout else if (ret == 0) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTCONNECT) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTCONNECT; - sock->alerted_flags |= ADHOC_F_ALERTCONNECT; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else { @@ -658,6 +703,11 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTFLUSH) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTFLUSH; + return 0; + } // Try Sending Empty Data int sockerr = FlushPtpSocket(uid); @@ -665,13 +715,7 @@ int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { if (sockerr == EAGAIN || sockerr == EWOULDBLOCK) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTFLUSH) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTFLUSH; - sock->alerted_flags |= ADHOC_F_ALERTFLUSH; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else @@ -854,6 +898,19 @@ int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void* return ERROR_NET_ADHOC_TIMEOUT; } +// Using matchingId = -1 to delete all matching events +void deleteMatchingEvents(const int matchingId = -1) { + for (auto it = matchingEvents.begin(); it != matchingEvents.end(); ) { + if (matchingId < 0 || it->data[0] == matchingId) { + if (Memory::IsValidAddress(it->data[2])) + userMemory.Free(it->data[2]); + it = matchingEvents.erase(it); + } + else + ++it; + } +} + void netAdhocValidateLoopMemory() { // Allocate Memory if it wasn't valid/allocated after loaded from old SaveState if (!dummyThreadHackAddr || (dummyThreadHackAddr && strcmp("dummythreadhack", kernelMemory.GetBlockTag(dummyThreadHackAddr)) != 0)) { @@ -869,7 +926,7 @@ void netAdhocValidateLoopMemory() { } void __NetAdhocDoState(PointerWrap &p) { - auto s = p.Section("sceNetAdhoc", 1, 7); + auto s = p.Section("sceNetAdhoc", 1, 8); if (!s) return; @@ -952,14 +1009,25 @@ void __NetAdhocDoState(PointerWrap &p) { else { adhocctlStateEvent = -1; } + if (s >= 8) { + Do(p, isAdhocctlBusy); + Do(p, netAdhocGameModeEntered); + Do(p, netAdhocEnterGameModeTimeout); + } + else { + isAdhocctlBusy = false; + netAdhocGameModeEntered = false; + netAdhocEnterGameModeTimeout = 15000000; + } if (p.mode == p.MODE_READ) { // Discard leftover events adhocctlEvents.clear(); - matchingEvents.clear(); adhocctlRequests.clear(); adhocSocketRequests.clear(); sendTargetPeers.clear(); + deleteAllAdhocSockets(); + deleteMatchingEvents(); // Let's not change "Inited" value when Loading SaveState to prevent memory & port leaks netAdhocMatchingInited = cur_netAdhocMatchingInited; @@ -1022,6 +1090,12 @@ u32 sceNetAdhocInit() { if (!netAdhocInited) { // Library initialized netAdhocInited = true; + isAdhocctlBusy = false; + + // FIXME: It seems official prx is using sceNetAdhocGameModeDeleteMaster in here? + NetAdhocGameMode_DeleteMaster(); + // Since we are deleting GameMode Master here, we should probably need to make sure GameMode resources all cleared too. + deleteAllGMB(); // Return Success return hleLogSuccessInfoI(SCENET, 0, "at %08x", currentMIPS->pc); @@ -1114,9 +1188,10 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f // Valid Arguments are supplied if (mac != NULL && bufferSize > 0) { // Port is in use by another PDP Socket. - // TODO: Need to test whether using the same PDP port is allowed or not, since PDP/UDP is connectionless it might be allowed. - if (isPDPPortInUse(port)) + if (isPDPPortInUse(port)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this if (port == 0) port = -static_cast(portOffset); @@ -1124,8 +1199,8 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f if (saddr != NULL) { getLocalMac(saddr); } - // Valid MAC supplied - if (isLocalMAC(saddr)) { + // Valid MAC supplied. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && isLocalMAC(saddr)) { // Create Internet UDP Socket int usocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Valid Socket produced @@ -1169,6 +1244,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f // Allocated Memory if (internal != NULL) { // Find Free Translator Index + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -1318,6 +1394,12 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int if (timeout > 0) setSockTimeout(pdpsocket.id, SO_SNDTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTSEND) { + socket->alerted_flags |= ADHOC_F_ALERTSEND; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Single Target if (!isBroadcastMAC(daddr)) { // Fill in Target Structure @@ -1343,7 +1425,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; if (sendTargetPeers.find(threadSocketId) != sendTargetPeers.end()) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Socket(%d) is Busy!", id, getLocalPort(pdpsocket.id), pdpsocket.id); - return ERROR_NET_ADHOC_BUSY; + return hleLogError(SCENET, ERROR_NET_ADHOC_BUSY, "busy?"); } AdhocSendTargets dest = { len, {}, false }; @@ -1359,6 +1441,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Free Network Lock //_freeNetworkLock(); + hleEatMicro(1000); // Can be longer than 1ms tho // Sent Data if (sent >= 0) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, getLocalPort(pdpsocket.id), sent, inet_ntoa(target.sin_addr), ntohs(target.sin_port)); @@ -1369,10 +1452,10 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Non-Blocking if (flag) - return ERROR_NET_ADHOC_WOULD_BLOCK; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); // 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; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout?"); // ERROR_NET_ADHOC_INVALID_ADDR; } //VERBOSE_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Unknown Target Peer %s:%u\n", id, getLocalPort(pdpsocket.id), mac2str(daddr, tmpmac), ntohs(target.sin_port)); } @@ -1417,7 +1500,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; if (sendTargetPeers.find(threadSocketId) != sendTargetPeers.end()) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](BC): Socket(%d) is Busy!", id, getLocalPort(pdpsocket.id), pdpsocket.id); - return ERROR_NET_ADHOC_BUSY; + return hleLogError(SCENET, ERROR_NET_ADHOC_BUSY, "busy?"); } sendTargetPeers[threadSocketId] = dest; @@ -1450,34 +1533,34 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Free Network Lock //_freeNetworkLock(); + hleEatMicro(1000); // Success, Broadcast never fails! return 0; // len; } } // Invalid Destination Address - return ERROR_NET_ADHOC_INVALID_ADDR; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ADDR, "invalid address"); } // Invalid Argument - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Invalid Socket ID - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Invalid Data Length - return ERROR_NET_ADHOC_INVALID_DATALEN; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_DATALEN, "invalid data length"); } // Invalid Destination Port - return ERROR_NET_ADHOC_INVALID_PORT; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_PORT, "invalid port"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; - + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } /** @@ -1542,6 +1625,12 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * if (timeout > 0) setSockTimeout(pdpsocket.id, SO_RCVTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTRECV) { + socket->alerted_flags |= ADHOC_F_ALERTRECV; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Sender Address sockaddr_in sin; @@ -1582,23 +1671,25 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * // Free Network Lock //_freeNetworkLock(); - return ERROR_NET_ADHOC_NOT_ENOUGH_SPACE; //received; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_ENOUGH_SPACE, "not enough space"); //received; } received = recvfrom(pdpsocket.id, (char*)buf, *len, MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); error = errno; - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK)) { + // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); } - VERBOSE_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpRecv[%i:%u] [size=%i]", error, id, pdpsocket.lport, *len); + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); } + hleEatMicro(1000); // 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) { + if (received >= 0) { DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); // Peer MAC @@ -1647,29 +1738,22 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * if (wouldblock) flag = 1; #endif - // Nothing received. On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET || error == ETIMEDOUT)) { - // Blocking Situation - if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; + DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Result:%i (Error:%i)", id, pdpsocket.lport, received, error); - // Timeout - return ERROR_NET_ADHOC_TIMEOUT; - } - - // Disconnected - return ERROR_NET_ADHOC_DISCONNECTED; + // Timeout? + return hleLogError(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout"); } // Invalid Argument - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Invalid Socket ID - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } int NetAdhoc_SetSocketAlert(int id, s32_le flag) { @@ -1682,7 +1766,7 @@ int NetAdhoc_SetSocketAlert(int id, s32_le flag) { adhocSockets[id - 1]->flags = flg; adhocSockets[id - 1]->alerted_flags = 0; - return 0; + return hleDelayResult(0, "set socket alert delay", 1000); } // Flags seems to be bitmasks of ADHOC_F_ALERT... (need more games to test this) @@ -1761,8 +1845,6 @@ int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock } if (sock->flags & ADHOC_F_ALERTPOLL) { - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTPOLL; sock->alerted_flags |= ADHOC_F_ALERTPOLL; return ERROR_NET_ADHOC_SOCKET_ALERTED; @@ -1953,11 +2035,20 @@ int sceNetAdhocctlScan() { // Library initialized if (netAdhocctlInited) { - int us = adhocDefaultDelay * 2; + int us = adhocDefaultDelay; + // FIXME: When tested with JPCSP + official prx files it seems when adhocctl in a connected state (ie. joined to a group) attempting to create/connect/join/scan will return a success (without doing anything?) + if ((adhocctlState == ADHOCCTL_STATE_CONNECTED) || (adhocctlState == ADHOCCTL_STATE_GAMEMODE)) { + // TODO: Valhalla Knights 2 need handler notification, but need to test this on games that doesn't use Adhocctl Handler too (not sure if there are games like that tho) + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOCCTL_ALREADY_CONNECTED); + hleEatMicro(500); + return 0; + } // Only scan when in Disconnected state, otherwise AdhocServer will kick you out - if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED && !isAdhocctlBusy) { + isAdhocctlBusy = true; adhocctlState = ADHOCCTL_STATE_SCANNING; + adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL; // Reset Networks/Group list to prevent other threads from using these soon to be replaced networks peerlock.lock(); @@ -1977,33 +2068,27 @@ int sceNetAdhocctlScan() { 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; + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_BUSY, "busy"); } else if (friendFinderRunning) { AdhocctlRequest req = { OPCODE_SCAN, {0} }; return WaitBlockingAdhocctlSocket(req, us, "adhocctl scan"); } } - else { - // Return Success and let friendFinder thread to notify the handler when scan completed - // Not delaying here may cause Naruto Shippuden Ultimate Ninja Heroes 3 to get disconnected when the mission started - hleEatMicro(us); - return 0; - } - } - else if (adhocctlState == ADHOCCTL_STATE_SCANNING) - return ERROR_NET_ADHOCCTL_BUSY; - // Already connected to a group. Should we fake a success? - // We need to notify the handler on success, even if it was faked. Using flag = 0/ADHOCCTL_EVENT_ERROR for error? - notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOCCTL_ALREADY_CONNECTED); - // FIXME: returning ERROR_NET_ADHOCCTL_BUSY may trigger the game (ie. Ford Street Racing) to call sceNetAdhocctlDisconnect, But Not returning a Success(0) will cause Valhalla Knights 2 not working properly - hleEatMicro(us); - return 0; + // Return Success and let friendFinder thread to notify the handler when scan completed + // Not delaying here may cause Naruto Shippuden Ultimate Ninja Heroes 3 to get disconnected when the mission started + hleEatMicro(us); + // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlScan switching to a different thread for at least 100ms after returning success and before executing the next line? + return hleDelayResult(0, "scan delay", adhocEventPollDelay); + } + + // FIXME: Returning BUSY when previous adhocctl handler's callback is not fully executed yet, But returning success and notifying handler's callback with error (ie. ALREADY_CONNECTED) when previous adhocctl handler's callback is fully executed? Is there a case where error = BUSY sent through handler's callback? + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_BUSY, "busy"); } // Library uninitialized - return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_NOT_INITIALIZED, "not initialized"); } int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { @@ -2012,7 +2097,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { SceNetAdhocctlScanInfoEmu *buf = NULL; if (Memory::IsValidAddress(bufAddr)) buf = (SceNetAdhocctlScanInfoEmu *)Memory::GetPointer(bufAddr); - INFO_LOG(SCENET, "sceNetAdhocctlGetScanInfo([%08x]=%i, %08x)", sizeAddr, Memory::Read_U32(sizeAddr), bufAddr); + INFO_LOG(SCENET, "sceNetAdhocctlGetScanInfo([%08x]=%i, %08x) at %08x", sizeAddr, Memory::Read_U32(sizeAddr), bufAddr, currentMIPS->pc); if (!g_Config.bEnableWlan) { return 0; } @@ -2024,12 +2109,20 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { // Minimum Argument Requirements if (buflen != NULL) { + // FIXME: Do we need to exclude Groups created by this device it's self? + bool excludeSelf = false; + // Multithreading Lock peerlock.lock(); + // FIXME: When already connected to a group GetScanInfo will return size = 0 ? or may be only hides the group created by it's self? + if (adhocctlState == ADHOCCTL_STATE_CONNECTED || adhocctlState == ADHOCCTL_STATE_GAMEMODE) { + *buflen = 0; + DEBUG_LOG(SCENET, "NetworkList [Available: 0] Already in a Group"); + } // Length Returner Mode - if (buf == NULL) { - int availNetworks = countAvailableNetworks(); + else if (buf == NULL) { + int availNetworks = countAvailableNetworks(excludeSelf); *buflen = availNetworks * sizeof(SceNetAdhocctlScanInfoEmu); DEBUG_LOG(SCENET, "NetworkList [Available: %i]", availNetworks); } @@ -2050,7 +2143,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { SceNetAdhocctlScanInfo * group = networks; // Iterate Group List - for (; group != NULL && discovered < requestcount; group = group->next) { + for (; group != NULL && (!excludeSelf || !isLocalMAC(&group->bssid.mac_addr)) && discovered < requestcount; group = group->next) { // Copy Group Information //buf[discovered] = *group; buf[discovered].group_name = group->group_name; @@ -2085,6 +2178,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { // Multithreading Unlock peerlock.unlock(); + hleEatMicro(2000); // Return Success return 0; } @@ -2143,9 +2237,17 @@ u32 NetAdhocctl_Disconnect() { // Library initialized if (netAdhocctlInited) { int iResult, error; - int us = adhocDefaultDelay * 5; - // Connected State (Adhoc Mode) - if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { // (threadStatus == ADHOCCTL_STATE_CONNECTED) + int us = adhocDefaultDelay * 3; + hleEatMicro(1000); + + if (isAdhocctlBusy) { + return ERROR_NET_ADHOCCTL_BUSY; + } + + // Connected State (Adhoc Mode). Attempting to leave a group while not in a group will be kicked out by Adhoc Server (ie. some games tries to disconnect more than once within a short time) + if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { + isAdhocctlBusy = true; + // Clear Network Name memset(¶meter.group_name, 0, sizeof(parameter.group_name)); @@ -2175,7 +2277,6 @@ u32 NetAdhocctl_Disconnect() { } else { // Set Disconnected State - //adhocctlState = ADHOCCTL_STATE_DISCONNECTED; return ERROR_NET_ADHOCCTL_BUSY; } } @@ -2205,12 +2306,16 @@ u32 NetAdhocctl_Disconnect() { adhocctlCurrentMode = ADHOCCTL_MODE_NONE; // 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(); + // FIXME: When there are no handler the state will immediately became ADHOCCTL_STATE_DISCONNECTED ? + if (adhocctlHandlers.empty()) { + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + } + else { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_DISCONNECT, 0); + } - hleEatMicro(us); // Return Success, some games might ignore returned value and always treat it as success, otherwise repeatedly calling this function - return 0; + return hleDelayResult(0, "disconnect delay", us); } // Library uninitialized @@ -2256,6 +2361,10 @@ int NetAdhocctl_Term() { friendFinderThread.join(); } + // Clear GameMode resources + NetAdhocGameMode_DeleteMaster(); + deleteAllGMB(); + // Clear Peer List int32_t peercount = 0; freeFriendsRecursive(friends, &peercount); @@ -2277,6 +2386,7 @@ int NetAdhocctl_Term() { } threadAdhocID = 0; adhocctlCurrentMode = ADHOCCTL_MODE_NONE; + isAdhocctlBusy = false; netAdhocctlInited = false; } @@ -2420,6 +2530,7 @@ int sceNetAdhocctlGetPeerInfo(const char *mac, int size, u32 peerInfoAddr) { // Multithreading Unlock peerlock.unlock(); } + hleEatMicro(1000); return retval; } @@ -2433,8 +2544,18 @@ int NetAdhocctl_Create(const char* groupName) { if (netAdhocctlInited) { // Valid Argument if (validNetworkName(groupNameStruct)) { - // 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)) { + // FIXME: When tested with JPCSP + official prx files it seems when adhocctl in a connected state (ie. joined to a group) attempting to create/connect/join/scan will return a success (without doing anything?) + if ((adhocctlState == ADHOCCTL_STATE_CONNECTED) || (adhocctlState == ADHOCCTL_STATE_GAMEMODE)) { + // TODO: Need to test this on games that doesn't use Adhocctl Handler too (not sure if there are games like that tho) + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOCCTL_ALREADY_CONNECTED); + hleEatMicro(500); + return 0; + } + + // Disconnected State + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED && !isAdhocctlBusy) { + isAdhocctlBusy = true; + // Set Network Name if (groupNameStruct != NULL) parameter.group_name = *groupNameStruct; @@ -2458,10 +2579,11 @@ int NetAdhocctl_Create(const char* groupName) { // Send Packet int iResult = send(metasocket, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL); int error = errno; + adhocctlStartTime = (u64)(time_now_d() * 1000000.0); if (iResult == SOCKET_ERROR && error != EAGAIN && error != EWOULDBLOCK) { 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 if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { adhocctlState = ADHOCCTL_STATE_GAMEMODE; @@ -2482,7 +2604,7 @@ 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 = adhocDefaultDelay; - if (adhocctlState != ADHOCCTL_STATE_CONNECTED && iResult == SOCKET_ERROR && friendFinderRunning) { + if (adhocctlState != ADHOCCTL_STATE_CONNECTED && adhocctlState != ADHOCCTL_STATE_GAMEMODE && iResult == SOCKET_ERROR && friendFinderRunning) { AdhocctlRequest req = { OPCODE_CONNECT, {0} }; if (groupNameStruct != NULL) req.group = *groupNameStruct; return WaitBlockingAdhocctlSocket(req, us, "adhocctl connect"); @@ -2490,7 +2612,8 @@ int NetAdhocctl_Create(const char* groupName) { hleEatMicro(us); // Return Success - return 0; + // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlCreate switching to a different thread for at least 100ms after returning success and before executing the next line. + return hleDelayResult(0, "create/connect/join delay", adhocEventPollDelay); } // Connected State @@ -2576,12 +2699,12 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n if (!Memory::IsValidAddress(membersAddr)) return ERROR_NET_ADHOCCTL_INVALID_ARG; - if (game_type <= 0 || game_type > 3 || num_members < 2 || num_members > 16 || (game_type == 1 && num_members > 4)) + if (game_type < ADHOCCTL_GAMETYPE_1A || game_type > ADHOCCTL_GAMETYPE_2A || num_members < 2 || num_members > 16 || (game_type == ADHOCCTL_GAMETYPE_1A && num_members > 4)) return ERROR_NET_ADHOCCTL_INVALID_ARG; + deleteAllGMB(); + SceNetEtherAddr* addrs = PSPPointer::Create(membersAddr); // List of participating MAC addresses (started from host) - gameModeMacs.clear(); - requiredGameModeMacs.clear(); for (int i = 0; i < num_members; i++) { requiredGameModeMacs.push_back(*addrs); DEBUG_LOG(SCENET, "GameMode macAddress#%d=%s", i, mac2str(addrs).c_str()); @@ -2592,10 +2715,19 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n getLocalMac(&localMac); gameModeMacs.push_back(localMac); + // FIXME: There seems to be an internal Adhocctl Handler on official prx (running on "SceNetAdhocctl" thread) that will try to sync GameMode timings, by using blocking PTP socket: + // 1). PtpListen (srcMacAddress=0x09F20CB4, srcPort=0x8001, bufSize=0x2000, retryDelay=0x30D40, retryCount=0x33, queue=0x1, unk1=0x0) + // 2). PtpAccpet (peerMacAddr=0x09FE2020, peerPortAddr=0x09FE2010, timeout=0x765BB0, nonblock=0x0) - probably for each clients + // 3). PtpSend (data=0x09F20E18, dataSizeAddr=0x09FE2094, timeout=0x627EDA, nonblock=0x0) - not sure what kind of data nor the size (more than 6 bytes) + // 4). PtpFlush (timeout=0x2DC6C0, nonblock=0x0) + // 5 & 6). PtpClose (accepted socket & listen socket) + // When timeout reached, notify user-defined Adhocctl Handlers with ERROR event (ERROR_NET_ADHOC_TIMEOUT) instead of GAMEMODE event + // We have to wait for all the MACs to have joined to go into CONNECTED state adhocctlCurrentMode = ADHOCCTL_MODE_GAMEMODE; adhocConnectionType = ADHOC_CREATE; netAdhocGameModeEntered = true; + netAdhocEnterGameModeTimeout = timeout; return NetAdhocctl_Create(group_name); } @@ -2642,12 +2774,22 @@ static int sceNetAdhocctlJoinEnterGameMode(const char * group_name, const char * if (!hostMac) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + deleteAllGMB(); + // Add host mac first gameModeMacs.push_back(*(SceNetEtherAddr*)hostMac); + // FIXME: There seems to be an internal Adhocctl Handler on official prx (running on "SceNetAdhocctl" thread) that will try to sync GameMode timings, by using blocking PTP socket: + // 1). PtpOpen (srcMacAddress=0x09FE2080, srcPort=0x8001, destMacAddress=0x09F20CB4, destPort=0x8001, bufSize=0x2000, retryDelay=0x30D40, retryCount=0x33, unk1=0x0) + // 2). PtpConnect (timeout=0x874CAC, nonblock=0x0) - to host/creator + // 3). PtpRecv (data=0x09F20E18, dataSizeAddr=0x09FE2044, timeout=0x647553, nonblock=0x0) - repeated until data fully received with data address/offset adjusted (increased) and timeout adjusted (decreased), probably also adjusted data size (decreased) on each call + // 4). PtpClose + // When timeout reached, notify user-defined Adhocctl Handlers with ERROR event (ERROR_NET_ADHOC_TIMEOUT) instead of GAMEMODE event + adhocctlCurrentMode = ADHOCCTL_MODE_GAMEMODE; adhocConnectionType = ADHOC_JOIN; netAdhocGameModeEntered = true; + netAdhocEnterGameModeTimeout = timeout; return hleLogDebug(SCENET, NetAdhocctl_Create(group_name), ""); } @@ -2680,12 +2822,12 @@ int NetAdhoc_Term() { // Library is initialized if (netAdhocInited) { - // Delete Adhoc Sockets - deleteAllAdhocSockets(); - // Delete GameMode Buffers deleteAllGMB(); + // Delete Adhoc Sockets + deleteAllAdhocSockets(); + // Terminate Internet Library //sceNetInetTerm(); @@ -2912,11 +3054,13 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, if (saddr != NULL) { getLocalMac(saddr); } - // Valid Addresses - if (saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr) && !isZeroMAC(daddr)) { + // Valid Addresses. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr) && !isZeroMAC(daddr)) { // Dissidia 012 will try to reOpen the port without Closing the old one first when PtpConnect failed to try again. - if (isPTPPortInUse(sport, false)) + if (isPTPPortInUse(sport, false)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } // Random Port required if (sport == 0) { @@ -2976,6 +3120,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3079,6 +3224,7 @@ int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEther // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3188,6 +3334,12 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int auto& ptpsocket = socket->data.ptp; socket->nonblocking = flag; + if (socket->flags & ADHOC_F_ALERTACCEPT) { + socket->alerted_flags |= ADHOC_F_ALERTACCEPT; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Listener Socket if (ptpsocket.state == ADHOC_PTP_STATE_LISTEN) { hleEatMicro(500); @@ -3271,6 +3423,12 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { auto& ptpsocket = socket->data.ptp; socket->nonblocking = flag; + if (socket->flags & ADHOC_F_ALERTCONNECT) { + socket->alerted_flags |= ADHOC_F_ALERTCONNECT; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Phantasy Star Portable 2 will try to reconnect even when previous connect already success, so we should return success too if it's already connected if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) return 0; @@ -3438,11 +3596,13 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int if (saddr != NULL) { getLocalMac(saddr); } - // Valid Address - if (saddr != NULL && isLocalMAC(saddr)) { + // Valid Address. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && saddr != NULL && isLocalMAC(saddr)) { // It's allowed to Listen and Open the same PTP port, But it's not allowed to Listen or Open the same PTP port twice. - if (isPTPPortInUse(sport, true)) + if (isPTPPortInUse(sport, true)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } // Random Port required if (sport == 0) { @@ -3503,6 +3663,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3613,6 +3774,12 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Apply Send Timeout Settings to Socket if (timeout > 0) setSockTimeout(ptpsocket.id, SO_SNDTIMEO, timeout); + + if (socket->flags & ADHOC_F_ALERTSEND) { + socket->alerted_flags |= ADHOC_F_ALERTSEND; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } // Acquire Network Lock // _acquireNetworkLock(); @@ -3705,6 +3872,12 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou if (timeout > 0) setSockTimeout(ptpsocket.id, SO_RCVTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTRECV) { + socket->alerted_flags |= ADHOC_F_ALERTRECV; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Acquire Network Lock // _acquireNetworkLock(); @@ -3750,6 +3923,9 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); + if (*len == 0) + return 0; + // Change Socket State ptpsocket.state = ADHOC_PTP_STATE_CLOSED; @@ -3810,6 +3986,12 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { auto& ptpsocket = socket->data.ptp; socket->nonblocking = nonblock; + if (socket->flags & ADHOC_F_ALERTFLUSH) { + socket->alerted_flags |= ADHOC_F_ALERTFLUSH; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Connected Socket if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) { hleEatMicro(1000); @@ -3873,6 +4055,7 @@ static int sceNetAdhocGameModeCreateMaster(u32 dataAddr, int size) { if (size < 0 || !Memory::IsValidAddress(dataAddr)) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + hleEatMicro(1000); SceNetEtherAddr localMac; getLocalMac(&localMac); u8* buf = (u8*)realloc(gameModeBuffer, size); @@ -3882,13 +4065,21 @@ static int sceNetAdhocGameModeCreateMaster(u32 dataAddr, int size) { u8* data = (u8*)malloc(size); if (data) { Memory::Memcpy(data, dataAddr, size); - masterGameModeArea = { 0, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 1, localMac, data }; + masterGameModeArea = { 0, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 1, 0, localMac, data }; // Socket's buffer size should fit the largest size from master/replicas, should we waited until master & all replicas to be created first before creating the socket? (ie. the first time UpdateMaster being called?) gameModeSocket = sceNetAdhocPdpCreate((const char*)&localMac, ADHOC_GAMEMODE_PORT, size, 0); StartGameModeScheduler(size); - } - return 0; // returned an id just like CreateReplica? always return 0? + // Block current thread to sync initial master data + if (gameModeSocket > 0 && replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + auto sock = adhocSockets[gameModeSocket - 1]; + __KernelWaitCurThread(WAITTYPE_NET, sock->data.pdp.id, 0, 0, false, "syncing master data"); + DEBUG_LOG(SCENET, "GameMode: Blocking Thread %d to Sync initial Master data", __KernelGetCurThread()); + } + return hleLogDebug(SCENET, 0, "success"); // returned an id just like CreateReplica? always return 0? + } + + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); } /** @@ -3914,6 +4105,7 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s if (mac == nullptr || size < 0 || !Memory::IsValidAddress(dataAddr)) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + hleEatMicro(1000); int maxid = 0; auto it = std::find_if(replicaGameModeAreas.begin(), replicaGameModeAreas.end(), [mac, &maxid](GameModeArea const& e) { @@ -3923,7 +4115,7 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s // MAC address already existed! if (it != replicaGameModeAreas.end()) { WARN_LOG(SCENET, "sceNetAdhocGameModeCreateReplica - [%s] is already existed (id: %d)", mac2str((SceNetEtherAddr*)mac).c_str(), it->id); - return it->id; + return it->id; // ERROR_NET_ADHOC_ALREADY_CREATED } int ret = 0; @@ -3931,11 +4123,20 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s if (data) { Memory::Memcpy(data, dataAddr, size); //int sock = sceNetAdhocPdpCreate(mac, ADHOC_GAMEMODE_PORT, size, 0); - GameModeArea gma = { maxid + 1, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 0, *(SceNetEtherAddr*)mac, data }; + GameModeArea gma = { maxid + 1, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 0, 0, *(SceNetEtherAddr*)mac, data }; replicaGameModeAreas.push_back(gma); - ret = gma.id; + ret = gma.id; // Valid id for replica is higher than 0? + + // Block current thread to sync initial master data + if (gameModeSocket > 0 && replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + auto sock = adhocSockets[gameModeSocket - 1]; + __KernelWaitCurThread(WAITTYPE_NET, sock->data.pdp.id, ret, 0, false, "syncing master data"); + DEBUG_LOG(SCENET, "GameMode: Blocking Thread %d to Sync initial Master data", __KernelGetCurThread()); + } + return hleLogSuccessInfoI(SCENET, ret, "success"); } - return hleLogSuccessInfoI(SCENET, ret, "success"); // valid id for replica started from 1? + + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); } static int sceNetAdhocGameModeUpdateMaster() { @@ -3953,28 +4154,36 @@ static int sceNetAdhocGameModeUpdateMaster() { Memory::Memcpy(masterGameModeArea.data, masterGameModeArea.addr, masterGameModeArea.size); masterGameModeArea.dataUpdated = 1; masterGameModeArea.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); + // Reset sent marker + for (auto& gma : replicaGameModeAreas) + gma.dataSent = 0; + } + + hleEatMicro(1000); + return 0; +} + +int NetAdhocGameMode_DeleteMaster() { + if (masterGameModeArea.data) { + free(masterGameModeArea.data); + } + //NetAdhocPdp_Delete(masterGameModeArea.socket, 0); + masterGameModeArea = { 0 }; + + if (replicaGameModeAreas.size() <= 0) { + NetAdhocPdp_Delete(gameModeSocket, 0); + gameModeSocket = (int)INVALID_SOCKET; } return 0; } static int sceNetAdhocGameModeDeleteMaster() { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteMaster()"); + WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteMaster() at %08x", currentMIPS->pc); if (isZeroMAC(&masterGameModeArea.mac)) return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); - if (masterGameModeArea.data) { - free(masterGameModeArea.data); - } - //sceNetAdhocPdpDelete(masterGameModeArea.socket, 0); - masterGameModeArea = { 0 }; - - if (replicaGameModeAreas.size() <= 0) { - sceNetAdhocPdpDelete(gameModeSocket, 0); - gameModeSocket = (int)INVALID_SOCKET; - } - - return 0; + return NetAdhocGameMode_DeleteMaster(); } static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { @@ -4002,10 +4211,10 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { GameModeUpdateInfo* gmuinfo = (GameModeUpdateInfo*)Memory::GetPointer(infoAddr); gmuinfo->length = sizeof(GameModeUpdateInfo); if (gma.data && gma.dataUpdated) { - memcpy(Memory::GetPointer(gma.addr), gma.data, gma.size); + Memory::Memcpy(gma.addr, gma.data, gma.size); gma.dataUpdated = 0; gmuinfo->updated = 1; - gmuinfo->timeStamp = std::max(gma.updateTimestamp, CoreTiming::GetGlobalTimeUsScaled() - 1000); + gmuinfo->timeStamp = std::max(gma.updateTimestamp, CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta); } else { gmuinfo->updated = 0; @@ -4015,11 +4224,12 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { } } + hleEatMicro(1000); return 0; } static int sceNetAdhocGameModeDeleteReplica(int id) { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteReplica(%i)", id); + WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteReplica(%i) at %08x", id, currentMIPS->pc); auto it = std::find_if(replicaGameModeAreas.begin(), replicaGameModeAreas.end(), [id](GameModeArea const& e) { return e.id == id; @@ -4082,6 +4292,12 @@ int NetAdhocMatching_Stop(int matchingId) { } matchingThreads[item->matching_thid] = 0; + // Make sure nobody locking/using the socket + item->socketlock->lock(); + // Delete the socket + NetAdhocPdp_Delete(item->socket, 0); // item->connected = (sceNetAdhocPdpDelete(item->socket, 0) < 0); + item->socketlock->unlock(); + // Multithreading Lock peerlock.lock(); @@ -4132,11 +4348,7 @@ int NetAdhocMatching_Delete(int matchingId) { // Delete the Fake PSP Thread //__KernelDeleteThread(item->matching_thid, SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); //delete item->matchingThread; - // Make sure nobody locking/using the socket - item->socketlock->lock(); - // Delete the socket - NetAdhocPdp_Delete(item->socket, 0); // item->connected = (sceNetAdhocPdpDelete(item->socket, 0) < 0); - item->socketlock->unlock(); + // Free allocated memories free(item->hello); free(item->rxbuf); @@ -4156,12 +4368,7 @@ int NetAdhocMatching_Delete(int matchingId) { 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; - } + deleteMatchingEvents(matchingId); // Stop Search break; @@ -4200,7 +4407,7 @@ int sceNetAdhocMatchingInit(u32 memsize) { fakePoolSize = memsize; // Initialize Library - matchingEvents.clear(); + deleteMatchingEvents(); netAdhocMatchingInited = true; // Return Success @@ -4258,7 +4465,8 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle SceNetAdhocMatchingContext * item = contexts; for (; item != NULL; item = item->next) { // Port Match found - if (item->port == port) return ERROR_NET_ADHOC_MATCHING_PORT_IN_USE; + if (item->port == port) + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_PORT_IN_USE, "adhoc matching port in use"); } // Allocate Context Memory @@ -4269,95 +4477,85 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle // Create PDP Socket SceNetEtherAddr localmac; getLocalMac(&localmac); - int socket = sceNetAdhocPdpCreate((const char*)&localmac, (uint32_t)port, rxbuflen, 0); - // Created PDP Socket - if (socket > 0) { + + // Clear Memory + memset(context, 0, sizeof(SceNetAdhocMatchingContext)); + + // Allocate Receive Buffer + context->rxbuf = (uint8_t*)malloc(rxbuflen); + + // Allocated Memory + if (context->rxbuf != NULL) { // Clear Memory - memset(context, 0, sizeof(SceNetAdhocMatchingContext)); + memset(context->rxbuf, 0, rxbuflen); - // Allocate Receive Buffer - context->rxbuf = (uint8_t *)malloc(rxbuflen); + // Fill in Context Data + context->id = findFreeMatchingID(); + context->mode = mode; + context->maxpeers = maxnum; + context->port = port; + context->rxbuflen = rxbuflen; + context->resendcounter = init_count; + context->resend_int = rexmt_int; // used as ack timeout on lost packet (ie. not receiving anything after sending)? + context->hello_int = hello_int; // client might set this to 0 + if (keepalive_int < 1) context->keepalive_int = PSP_ADHOCCTL_PING_TIMEOUT; else context->keepalive_int = keepalive_int; // client might set this to 0 + context->keepalivecounter = init_count; // used to multiply keepalive_int as timeout + context->timeout = (((u64)(keepalive_int)+(u64)rexmt_int) * (u64)init_count); + context->timeout += adhocDefaultTimeout; // For internet play we need higher timeout than what the game wanted + context->handler = handler; - // Allocated Memory - if (context->rxbuf != NULL) { - // Clear Memory - memset(context->rxbuf, 0, rxbuflen); + // Fill in Selfpeer + context->mac = localmac; - // Fill in Context Data - context->id = findFreeMatchingID(); - context->mode = mode; - context->maxpeers = maxnum; - context->port = port; - context->socket = socket; - context->rxbuflen = rxbuflen; - context->resendcounter = init_count; - context->resend_int = rexmt_int; // used as ack timeout on lost packet (ie. not receiving anything after sending)? - context->hello_int = hello_int; // client might set this to 0 - if (keepalive_int < 1) context->keepalive_int = PSP_ADHOCCTL_PING_TIMEOUT; else context->keepalive_int = keepalive_int; // client might set this to 0 - context->keepalivecounter = init_count; // used to multiply keepalive_int as timeout - context->timeout = (((u64)(keepalive_int) + (u64)rexmt_int) * (u64)init_count); - context->timeout += adhocDefaultTimeout; // For internet play we need higher timeout than what the game wanted - context->handler = handler; + // Create locks + context->socketlock = new std::recursive_mutex; + context->eventlock = new std::recursive_mutex; + context->inputlock = new std::recursive_mutex; - // Fill in Selfpeer - context->mac = localmac; + // Multithreading Lock + peerlock.lock(); //contextlock.lock(); - // Create locks - context->socketlock = new std::recursive_mutex; - context->eventlock = new std::recursive_mutex; - context->inputlock = new std::recursive_mutex; + // Add Callback Handler + context->handler.entryPoint = callbackAddr; + context->matching_thid = static_cast(matchingThreads.size()); + matchingThreads.push_back(0); - // Multithreading Lock - peerlock.lock(); //contextlock.lock(); - - // Add Callback Handler - context->handler.entryPoint = callbackAddr; - context->matching_thid = static_cast(matchingThreads.size()); - matchingThreads.push_back(0); + // Link Context + //context->connected = true; + context->next = contexts; + contexts = context; - // Link Context - //context->connected = true; - context->next = contexts; - contexts = context; + // Multithreading UnLock + peerlock.unlock(); //contextlock.unlock(); - // Multithreading UnLock - peerlock.unlock(); //contextlock.unlock(); + // Just to make sure Adhoc is already connected + //hleDelayResult(context->id, "give time to init/cleanup", adhocEventDelayMS * 1000); - // Just to make sure Adhoc is already connected - //hleDelayResult(context->id, "give time to init/cleanup", adhocEventDelayMS * 1000); - - // Return Matching ID - return context->id; - } - - // Close PDP Socket - sceNetAdhocPdpDelete(socket, 0); // context->connected = (sceNetAdhocPdpDelete(socket, 0) < 0); + // Return Matching ID + return hleLogDebug(SCENET, context->id, "success"); } // Free Memory free(context); - - // Port in use - if (socket < 1) return ERROR_NET_ADHOC_MATCHING_PORT_IN_USE; // ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED; // -1; // ERROR_NET_ADHOC_MATCHING_NOT_ESTABLISHED; } // Out of Memory - return ERROR_NET_ADHOC_MATCHING_NO_SPACE; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NO_SPACE, "adhoc matching no space"); } // InvalidERROR_NET_Arguments - return ERROR_NET_ADHOC_MATCHING_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_INVALID_ARG, "adhoc matching invalid arg"); } // Invalid Receive Buffer Size - return ERROR_NET_ADHOC_MATCHING_RXBUF_TOO_SHORT; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_RXBUF_TOO_SHORT, "adhoc matching rxbuf too short"); } // Invalid Member Limit - return ERROR_NET_ADHOC_MATCHING_INVALID_MAXNUM; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_INVALID_MAXNUM, "adhoc matching invalid maxnum"); } // Uninitialized Library - return ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhoc matching not initialized"); } int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int evthStack, int inthPri, int inthPartitionId, int inthStack, int optLen, u32 optDataAddr) { @@ -4381,18 +4579,11 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int } //else return ERROR_NET_ADHOC_MATCHING_INVALID_ARG; // ERROR_NET_ADHOC_MATCHING_INVALID_OPTLEN; // Returning Not Success will cause GTA:VC stuck unable to choose host/join menu - //Add your own MAC as a member (only if it's empty?) - /*SceNetAdhocMatchingMemberInternal * peer = addMember(item, &item->mac); - switch (item->mode) { - case PSP_ADHOC_MATCHING_MODE_PARENT: - peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; - break; - case PSP_ADHOC_MATCHING_MODE_CHILD: - peer->state = PSP_ADHOC_MATCHING_PEER_CHILD; - break; - case PSP_ADHOC_MATCHING_MODE_P2P: - peer->state = PSP_ADHOC_MATCHING_PEER_P2P; - }*/ + // Create PDP Socket + int sock = sceNetAdhocPdpCreate((const char*)&item->mac, static_cast(item->port), item->rxbuflen, 0); + item->socket = sock; + if (sock < 1) + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_PORT_IN_USE, "adhoc matching port in use"); // Create & Start the Fake PSP Thread ("matching_ev%d" and "matching_io%d") netAdhocValidateLoopMemory(); @@ -4601,23 +4792,19 @@ static int sceNetAdhocMatchingSelectTarget(int matchingId, const char *macAddres return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhocmatching not initialized"); } -int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTargetWithOpt(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, currentMIPS->pc); - if (!g_Config.bEnableWlan) - return -1; - +int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, int optLen, u32 optDataPtr) { // Initialized Library if (netAdhocMatchingInited) { - SceNetEtherAddr * target = (SceNetEtherAddr *)macAddress; - void * opt = NULL; + SceNetEtherAddr* target = (SceNetEtherAddr*)macAddress; + void* opt = NULL; if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointer(optDataPtr); // Valid Arguments if (target != NULL && ((optLen == 0) || (optLen > 0 && opt != NULL))) { // Find Matching Context - SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); + SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); // Found Matching Context if (context != NULL) @@ -4626,7 +4813,7 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres if (context->running) { // Find Peer - SceNetAdhocMatchingMemberInternal * peer = findPeer(context, (SceNetEtherAddr *)target); + SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)target); // Found Peer if (peer != NULL) @@ -4655,6 +4842,7 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres // Marking peer to be timedout instead of deleting immediately peer->lastping = 0; + hleEatCycles(adhocDefaultDelay); // Return Success return 0; } @@ -4682,11 +4870,18 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhocmatching not initialized"); } +int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { + WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTargetWithOpt(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, currentMIPS->pc); + if (!g_Config.bEnableWlan) + return -1; + return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, optLen, optDataPtr); +} + int sceNetAdhocMatchingCancelTarget(int matchingId, const char *macAddress) { WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTarget(%i, %s)", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str()); if (!g_Config.bEnableWlan) return -1; - return sceNetAdhocMatchingCancelTargetWithOpt(matchingId, macAddress, 0, 0); + return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, 0, 0); } int sceNetAdhocMatchingGetHelloOpt(int matchingId, u32 optLenAddr, u32 optDataAddr) { @@ -5176,7 +5371,7 @@ void __NetTriggerCallbacks() { std::lock_guard adhocGuard(adhocEvtMtx); hleSkipDeadbeef(); - int delayus = 10000; + int delayus = adhocDefaultDelay; auto params = adhocctlEvents.begin(); if (params != adhocctlEvents.end()) @@ -5187,10 +5382,10 @@ void __NetTriggerCallbacks() u32_le args[3] = { 0, 0, 0 }; args[0] = flags; args[1] = error; + u64 now = (u64)(time_now_d() * 1000000.0); // FIXME: When Joining a group, Do we need to wait for group creator's peer data before triggering the callback to make sure the game not to thinks we're the group creator? - u64 now = (u64)(time_now_d() * 1000000.0); - if ((flags != ADHOCCTL_EVENT_CONNECT && flags != ADHOCCTL_EVENT_GAME) || adhocConnectionType != ADHOC_JOIN || getActivePeerCount() > 0 || now - adhocctlStartTime > adhocDefaultTimeout) + if ((flags != ADHOCCTL_EVENT_CONNECT && flags != ADHOCCTL_EVENT_GAME) || adhocConnectionType != ADHOC_JOIN || getActivePeerCount() > 0 || static_cast(now - adhocctlStartTime) > adhocDefaultTimeout) { // Since 0 is a valid index to types_ we use -1 to detects if it was loaded from an old save state if (actionAfterAdhocMipsCall < 0) { @@ -5203,11 +5398,12 @@ void __NetTriggerCallbacks() newState = ADHOCCTL_STATE_CONNECTED; if (adhocConnectionType == ADHOC_CREATE) delayus = adhocEventDelay; // May affects Dissidia 012 and GTA VCS - else if (adhocConnectionType == ADHOC_CONNECT && getActivePeerCount() == 0) + else if (adhocConnectionType == ADHOC_CONNECT) delayus = adhocEventDelay / 2; break; case ADHOCCTL_EVENT_SCAN: // notified only when scan completed? newState = ADHOCCTL_STATE_DISCONNECTED; + //delayus = adhocEventDelay / 2; break; case ADHOCCTL_EVENT_DISCONNECT: newState = ADHOCCTL_STATE_DISCONNECTED; @@ -5216,6 +5412,10 @@ void __NetTriggerCallbacks() { newState = ADHOCCTL_STATE_GAMEMODE; delayus = adhocEventDelay; + // TODO: Use blocking PTP connection to sync the timing just like official prx did (which is done before notifying user-defined Adhocctl Handlers) + // Workaround: Extra delay to prevent Joining player to progress faster than the Creator on Pocket Pool, but unbalanced delays could cause an issue on Shaun White Snowboarding :( + if (adhocConnectionType == ADHOC_JOIN) + delayus += adhocExtraDelay * 3; // Shows player list INFO_LOG(SCENET, "GameMode - All players have joined:"); int i = 0; @@ -5233,8 +5433,7 @@ void __NetTriggerCallbacks() newState = ADHOCCTL_STATE_WOL; break; case ADHOCCTL_EVENT_ERROR: - // FIXME: Should we change the state on error or leave it alone? for example after Creating/Joining a group, doing Scan could trigger an error through handler, what about the AdhocctlState after this error? - //newState = ADHOCCTL_STATE_DISCONNECTED; + delayus = adhocDefaultDelay * 3; break; } @@ -5259,7 +5458,7 @@ void __NetTriggerCallbacks() void __NetMatchingCallbacks() //(int matchingId) { std::lock_guard adhocGuard(adhocEvtMtx); - int delayus = 10000; + int delayus = adhocDefaultDelay; auto params = matchingEvents.begin(); if (params != matchingEvents.end()) @@ -5338,8 +5537,10 @@ const HLEFunction sceNetAdhocMatching[] = { }; int NetAdhocctl_ExitGameMode() { - if (gameModeSocket > 0) - sceNetAdhocPdpDelete(gameModeSocket, 0); + if (gameModeSocket > 0) { + NetAdhocPdp_Delete(gameModeSocket, 0); + gameModeSocket = (int)INVALID_SOCKET; + } deleteAllGMB(); @@ -5373,6 +5574,7 @@ static int sceNetAdhocctlGetGameModeInfo(u32 infoAddr) { break; } + hleEatMicro(1000); return 0; } @@ -5391,7 +5593,7 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) { if (netAdhocctlInited) { // Minimum Arguments if (buflen != NULL) { - // FIXME: Sometimes returing 0x80410682 before AdhocctlGetState became ADHOCCTL_STATE_CONNECTED or related to Auth/Library ? + // FIXME: Sometimes returing 0x80410682 when Adhocctl is still BUSY or before AdhocctlGetState became ADHOCCTL_STATE_CONNECTED or related to Auth/Library ? // Multithreading Lock peerlock.lock(); @@ -5411,6 +5613,8 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) { // Calculate Request Count int requestcount = *buflen / sizeof(SceNetAdhocctlPeerInfoEmu); + // FIXME: When bufAddr is not null but buffer size is smaller than activePeers * sizeof(SceNetAdhocctlPeerInfoEmu), simply return buffer size = 0 without filling the buffer? + // Clear Memory memset(buf, 0, *buflen); @@ -6732,6 +6936,7 @@ int matchingEventThread(int matchingId) context->eventlock->lock(); // Iterate Message List + int msg_count = 0; ThreadMessage * msg = context->event_stack; for (; msg != NULL; msg = msg->next) { @@ -6749,6 +6954,7 @@ int matchingEventThread(int matchingId) // Notify Event Handlers notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); //context->eventlock->lock(); + msg_count++; } // Clear Event Message Stack @@ -6756,6 +6962,7 @@ int matchingEventThread(int matchingId) // Free Stack context->eventlock->unlock(); + INFO_LOG(SCENET, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count); } // Free memory @@ -6968,11 +7175,62 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi } if (contexts != NULL) { + // Process Last Messages + if (context->input_stack != NULL) + { + // Claim Stack + context->inputlock->lock(); + + // Iterate Message List + int msg_count = 0; + ThreadMessage* msg = context->input_stack; + for (; msg != NULL; msg = msg->next) + { + // Default Optional Data + void* opt = NULL; + + // Grab Optional Data + if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); + + // Send Accept Packet + if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); + + // Send Join Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); + + // Send Cancel Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); + + // Send Bulk Data Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Send Birth Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); + + // Send Death Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); + + // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? + //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Pop input stack from front (this should be queue instead of stack?) + context->input_stack = msg->next; + free(msg); + msg = context->input_stack; + msg_count++; + } + + // Free Stack + context->inputlock->unlock(); + INFO_LOG(SCENET, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count); + } + // Clear IO Message Stack clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK); - // Send Bye Messages - sendByePacket(context); + // Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? + //sendByePacket(context); + sendDeathPacket(context, &context->mac); // Free Peer List Buffer clearPeerList(context); //deleteAllMembers(context); diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index 682a609b2f..73016cc20d 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -98,6 +98,8 @@ int NetAdhoc_Term(); extern bool netAdhocInited; extern bool netAdhocctlInited; extern bool networkInited; +extern bool netAdhocGameModeEntered; +extern int netAdhocEnterGameModeTimeout; extern int adhocDefaultTimeout; //3000000 usec extern int adhocDefaultDelay; //10000 extern int adhocExtraDelay; //20000