// Copyright (c) 2025- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Core/HLE/sceNetAdhocMatching.h" #include "Core/System.h" #include "Core/HLE/sceNetAdhoc.h" #include "Core/HLE/NetAdhocCommon.h" #include #include #include "Common/Thread/ThreadUtil.h" #include "Common/TimeUtil.h" #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Common/System/OSD.h" #include "Common/Data/Text/I18n.h" #include "Core/Config.h" #include "Core/Reporting.h" #include "Core/MemMapHelpers.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/ErrorCodes.h" std::vector matchingThreads; std::deque matchingEvents; bool netAdhocMatchingInited; // What MatchingStart takes on a PSP (pspautotests net/adhoc/timing). int adhocMatchingEventDelay = 416; void DoNetAdhocMatchingInited(PointerWrap &p) { Do(p, netAdhocMatchingInited); } void DoNetAdhocMatchingThreads(PointerWrap &p) { Do(p, matchingThreads); } void ZeroNetAdhocMatchingThreads() { for (auto& it : matchingThreads) { it = 0; } } int netAdhocMatchingStarted = 0; // Added to the PSP's timeout for a silent peer, for latency over the internet. 0 keeps the hardware's // timeout: it already allows keepalive_int * init_count of silence. static constexpr u64 MATCHING_TIMEOUT_EXTRA_US = 0; // The contexts' input handling and event delivery run from this CoreTiming event, every // MATCHING_POLL_INTERVAL_US of emulated time. static int matchingPollEvent = -1; static constexpr int MATCHING_POLL_INTERVAL_US = 1000; void __UpdateMatchingHandler(const MatchingArgs &ArgsPtr) { std::lock_guard adhocGuard(adhocEvtMtx); matchingEvents.push_back(ArgsPtr); } // Matching callback is void function: typedef void(*SceNetAdhocMatchingHandler)(int id, int event, SceNetEtherAddr * peer, int optlen, void * opt); // Important! The MIPS call need to be fully executed before the next MIPS call invoked, as the game (ie. DBZ Tag Team) may need to prepare something for the next callback event to use // Note: Must not lock peerlock within this function to prevent race-condition with other thread whos owning peerlock and trying to lock context->eventlock owned by this thread void notifyMatchingHandler(SceNetAdhocMatchingContext * context, ThreadMessage * msg, void * opt, u32_le &bufAddr, u32_le &bufLen, u32_le * args) { // Don't share buffer address space with other mipscall in the queue since mipscalls aren't immediately executed MatchingArgs argsNew = { 0 }; u32_le dataBufLen = msg->optlen + 8; //max(bufLen, msg->optlen + 8); u32_le dataBufAddr = userMemory.Alloc(dataBufLen); // We will free this memory after returning from mipscall. FIXME: Are these buffers supposed to be taken/pre-allocated from the memory pool during sceNetAdhocMatchingInit? uint8_t *dataPtr = Memory::GetPointerWriteRangeOrException(dataBufAddr, dataBufLen); if (dataPtr) { memcpy(dataPtr, &msg->mac, sizeof(msg->mac)); if (msg->optlen > 0) memcpy(dataPtr + 8, opt, msg->optlen); argsNew.data[1] = msg->opcode; argsNew.data[2] = dataBufAddr; argsNew.data[3] = msg->optlen; argsNew.data[4] = dataBufAddr + 8; // OptData Addr } else { argsNew.data[1] = PSP_ADHOC_MATCHING_EVENT_ERROR; // not sure where to put the error code for EVENT_ERROR tho //argsNew.data[2] = dataBufAddr; // FIXME: Is the MAC address mandatory (ie. can't be null pointer) even for EVENT_ERROR? Where should we put this MAC data in the case we failed to allocate the memory? may be on the memory pool? } argsNew.data[0] = context->id; argsNew.data[5] = context->handler.entryPoint; //not part of callback argument, just borrowing a space to store callback address so i don't need to search the context first later // ScheduleEvent_Threadsafe_Immediate seems to get mixed up with interrupt (returning from mipscall inside an interrupt) and getting invalid address before returning from interrupt __UpdateMatchingHandler(argsNew); } // 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; } } // For savestate loads: the events' buffers were allocated from the memory the load just replaced. void discardMatchingEvents() { std::lock_guard adhocGuard(adhocEvtMtx); matchingEvents.clear(); } /** * Broadcast Ping Message to other Matching Users * @param context Matching Context Pointer */ void broadcastPingMessage(SceNetAdhocMatchingContext * context) { // Ping Opcode uint8_t ping = PSP_ADHOC_MATCHING_PACKET_PING; // Lock the peer std::lock_guard peer_guard(peerlock); // Send Broadcast // FIXME: Not sure whether this PING supposed to be sent only to AdhocMatching members or to everyone in Adhocctl Group, since we already pinging the AdhocServer to avoid getting kicked out of Adhocctl Group auto peer = friends; // Use context->peerlist if only need to send to AdhocMatching members for (; peer != NULL; peer = peer->next) { // Skipping soon to be removed peer if (peer->last_recv == 0) continue; u16_le port = context->port; auto it = (*context->peerPort).find(peer->mac_addr); if (it != (*context->peerPort).end()) port = it->second; context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac_addr, port, &ping, (u32)sizeof(ping), 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); } } /** * Broadcast Hello Message to other Matching Users * @param context Matching Context Pointer */ void broadcastHelloMessage(SceNetAdhocMatchingContext * context) { static uint8_t * hello = NULL; static int32_t len = -5; // Allocate Hello Message Buffer, reuse when necessary if ((int32_t)context->hellolen > len) { uint8_t* tmp = (uint8_t *)realloc(hello, 5LL + context->hellolen); if (tmp != NULL) { hello = tmp; len = context->hellolen; } } if (hello == NULL) { // Failed to allocate the Hello Message Buffer return; } // Hello Opcode hello[0] = PSP_ADHOC_MATCHING_PACKET_HELLO; // Hello Data Length (have to memcpy this to avoid cpu alignment crash) memcpy(hello + 1, &context->hellolen, sizeof(context->hellolen)); // FIXME: When using JPCSP + prx files the data being sent have a header of 12 bytes instead of 5 bytes: // [01(always 1? size of the next data? or combined with next byte as U16_BE opcode?) 01(matching opcode, or combined with previous byte as U16_BE opcode?) 01 E0(size of next data + hello data in big-endian/U16_BE) 00 0F 42 40(U32_BE? time?) 00 0F 42 40(U32_BE? time?)], // followed by hello data (0x1D8 bytes of opt data, based on Ys vs. Sora no Kiseki), and followed by 16 bytes of (optional?) footer [01 00 00 .. 00 00](footer doesn't exist if the size after opcode is 00 00) // Copy Hello Data if (context->hellolen > 0) memcpy(hello + 5, context->hello, context->hellolen); std::string hellohex; DataToHexString(10, 0, context->hello, context->hellolen, &hellohex); DEBUG_LOG(Log::sceNet, "HELLO Dump (%d bytes):\n%s", context->hellolen, hellohex.c_str()); // Send Broadcast, so everyone know we have a room here peerlock.lock(); SceNetAdhocctlPeerInfo* peer = friends; for (; peer != NULL; peer = peer->next) { // Skipping soon to be removed peer if (peer->last_recv == 0) continue; u16_le port = context->port; auto it = (*context->peerPort).find(peer->mac_addr); if (it != (*context->peerPort).end()) port = it->second; context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac_addr, port, hello, 5 + context->hellolen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); } peerlock.unlock(); } /** * Send Accept Packet to Player * @param context Matching Context Pointer * @param mac Target Player MAC * @param optlen Optional Data Length * @param opt Optional Data */ void sendAcceptPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac, int optlen, void * opt) { // Lock the peer std::lock_guard peer_guard(peerlock); // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, mac); if (peer == NULL || (peer->state != PSP_ADHOC_MATCHING_PEER_CHILD && peer->state != PSP_ADHOC_MATCHING_PEER_P2P)) { // Not found return; } // Required Sibling Buffer uint32_t siblingbuflen = 0; // Parent Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) siblingbuflen = (u32)sizeof(SceNetEtherAddr) * (countConnectedPeers(context) - 2); // Sibling Count int siblingcount = siblingbuflen / sizeof(SceNetEtherAddr); // Allocate Accept Message Buffer uint8_t * accept = (uint8_t *)malloc(9LL + optlen + siblingbuflen); if (accept == NULL) { // Failed to allocate the Accept Message Buffer return; } // Accept Opcode accept[0] = PSP_ADHOC_MATCHING_PACKET_ACCEPT; // Optional Data Length memcpy(accept + 1, &optlen, sizeof(optlen)); // Sibling Count memcpy(accept + 5, &siblingcount, sizeof(siblingcount)); // Copy Optional Data if (optlen > 0) memcpy(accept + 9, opt, optlen); // Parent Mode Extra Data required if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && siblingcount > 0) { // Create MAC Array Pointer uint8_t * siblingmacs = (uint8_t *)(accept + 9 + optlen); // MAC Writing Pointer int i = 0; // Iterate Peer List SceNetAdhocMatchingMemberInternal * item = context->peerlist; for (; item != NULL; item = item->next) { // Ignore Target if (item == peer) continue; // Copy Child MAC if (item->state == PSP_ADHOC_MATCHING_PEER_CHILD) { // Clone MAC the stupid memcpy way to shut up PSP CPU memcpy(siblingmacs + sizeof(SceNetEtherAddr) * i++, &item->mac, sizeof(SceNetEtherAddr)); } } } // Send Data context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)mac, (*context->peerPort)[*mac], accept, 9 + optlen + siblingbuflen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); // Free Memory free(accept); // Spawn Local Established Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ESTABLISHED, mac, 0, NULL); } /** * Send Join Packet to Player * @param context Matching Context Pointer * @param mac Target Player MAC * @param optlen Optional Data Length * @param opt Optional Data */ void sendJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac, int optlen, void * opt) { // Lock the peer std::lock_guard peer_guard(peerlock); // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, mac); if (peer == NULL || peer->state != PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST) { // Valid peer not found return; } // Allocate Join Message Buffer uint8_t * join = (uint8_t *)malloc(5LL + optlen); if (join == NULL) { // Failed to allocate the Join Message Buffer return; } // Join Opcode join[0] = PSP_ADHOC_MATCHING_PACKET_JOIN; // Optional Data Length memcpy(join + 1, &optlen, sizeof(optlen)); // Copy Optional Data if (optlen > 0) memcpy(join + 5, opt, optlen); // Send Data context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)mac, (*context->peerPort)[*mac], join, 5 + optlen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); // Free Memory free(join); } /** * Send Cancel Packet to Player * @param context Matching Context Pointer * @param mac Target Player MAC * @param optlen Optional Data Length * @param opt Optional Data */ void sendCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac, int optlen, void * opt) { // Lock the peer std::lock_guard peer_guard(peerlock); // Allocate Cancel Message Buffer uint8_t * cancel = (uint8_t *)malloc(5LL + optlen); // Allocated Cancel Message Buffer if (cancel != NULL) { // Cancel Opcode cancel[0] = PSP_ADHOC_MATCHING_PACKET_CANCEL; // Optional Data Length memcpy(cancel + 1, &optlen, sizeof(optlen)); // Copy Optional Data if (optlen > 0) memcpy(cancel + 5, opt, optlen); // Send Data context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)mac, (*context->peerPort)[*mac], cancel, 5 + optlen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); // Free Memory free(cancel); } // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, mac); if (peer == NULL) { // Peer not found return; } // Child Mode Fallback - Delete All if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) { // Delete Peer List clearPeerList(context); } // Delete Peer else { // Instead of removing peer immediately, We should give a little time before removing the peer and let it timed out? so it can send the BYE packet when stopping AdhocMatching after Canceling it peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); } } /** * Send Bulk Data Packet to Player * @param context Matching Context Pointer * @param mac Target Player MAC * @param datalen Data Length * @param data Data */ void sendBulkDataPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac, int datalen, void * data) { // Lock the peer std::lock_guard peer_guard(peerlock); // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, mac); if (peer == NULL) { // Invalid Peer return; } // Don't send if it's aborted //if (peer->sending == 0) return; // Allocate Send Message Buffer uint8_t * send = (uint8_t *)malloc(5LL + datalen); if (send == NULL) { // Failed to allocate the Send Message Buffer return; } // Send Opcode send[0] = PSP_ADHOC_MATCHING_PACKET_BULK; // Data Length memcpy(send + 1, &datalen, sizeof(datalen)); // Copy Data memcpy(send + 5, data, datalen); // Send Data context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)mac, (*context->peerPort)[*mac], send, 5 + datalen, 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); // Free Memory free(send); // The aemu protocol has no acknowledgement, so DATA_ACK is made here. The peer stays busy until // it's delivered: a SendData before that gets DATA_BUSY, as on a PSP (pspautotests // net/adhoc/pair/matching). // A peer that has left the group can't acknowledge, so DATA_TIMEOUT comes once the retransmissions // have run out (pspautotests net/adhoc/pair/vanish). if (peerLeftAt(mac) != 0) { peer->dataTimeoutAt = CoreTiming::GetGlobalTimeUsScaled() + (u64)context->resend_int * (u64)std::max(1, context->resendcounter); return; } spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DATA_ACK, mac, 0, NULL); } static void handleDataTimeouts(SceNetAdhocMatchingContext *context, u64 now) { std::lock_guard peer_guard(peerlock); for (SceNetAdhocMatchingMemberInternal *peer = context->peerlist; peer != NULL; peer = peer->next) { if (peer->dataTimeoutAt != 0 && now >= peer->dataTimeoutAt) { peer->dataTimeoutAt = 0; spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DATA_TIMEOUT, &peer->mac, 0, NULL); } } } /** * Tell Established Peers of new Child * @param context Matching Context Pointer * @param mac New Child's MAC */ void sendBirthPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac) { // Lock the peer std::lock_guard peer_guard(peerlock); // Find Newborn Child SceNetAdhocMatchingMemberInternal * newborn = findPeer(context, mac); if (newborn == NULL) { // Did not find Newborn Child return; } // Packet Buffer uint8_t packet[7]; // Set Opcode packet[0] = PSP_ADHOC_MATCHING_PACKET_BIRTH; // Set Newborn MAC memcpy(packet + 1, mac, sizeof(SceNetEtherAddr)); // Iterate Peers SceNetAdhocMatchingMemberInternal * peer = context->peerlist; for (; peer != NULL; peer = peer->next) { // Skip Newborn Child if (peer == newborn) continue; // Send only to children if (peer->state != PSP_ADHOC_MATCHING_PEER_CHILD) { continue; } // Send Packet context->socketlock->lock(); int sent = hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac, (*context->peerPort)[peer->mac], packet, (u32)sizeof(packet), 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); // Log Send Success if (sent >= 0) INFO_LOG(Log::sceNet, "InputLoop: Sending BIRTH [%s] to %s", mac2str(mac).c_str(), mac2str(&peer->mac).c_str()); else WARN_LOG(Log::sceNet, "InputLoop: Failed to Send BIRTH [%s] to %s", mac2str(mac).c_str(), mac2str(&peer->mac).c_str()); } } /** * Tell Established Peers of abandoned Child * @param context Matching Context Pointer * @param mac Dead Child's MAC */ void sendDeathPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * mac) { // Lock the peer std::lock_guard peer_guard(peerlock); // Find abandoned Child SceNetAdhocMatchingMemberInternal * deadkid = findPeer(context, mac); if (deadkid == NULL) { // Did not find abandoned Child return; } // Packet Buffer uint8_t packet[7]; // Set abandoned Child MAC memcpy(packet + 1, mac, sizeof(SceNetEtherAddr)); // Iterate Peers SceNetAdhocMatchingMemberInternal * peer = context->peerlist; for (; peer != NULL; peer = peer->next) { // Skip dead Child? Or May be we should also tells the disconnected Child, that they have been disconnected from the Host (in the case they were disconnected because they went to PPSSPP settings for too long) if (peer == deadkid) { // Set Opcode packet[0] = PSP_ADHOC_MATCHING_PACKET_BYE; // Send Bye Packet context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac, (*context->peerPort)[peer->mac], packet, (u32)sizeof(packet[0]), 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); } else { // Send to other children if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) { // Set Opcode packet[0] = PSP_ADHOC_MATCHING_PACKET_DEATH; // Send Death Packet context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac, (*context->peerPort)[peer->mac], packet, (u32)sizeof(packet), 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); } } } // Delete Peer deletePeer(context, deadkid); } /** * Tell Established Peers that we're shutting the Networking Layer down * @param context Matching Context Pointer */ void sendByePacket(SceNetAdhocMatchingContext * context) { // Lock the peer std::lock_guard peer_guard(peerlock); // Iterate Peers SceNetAdhocMatchingMemberInternal * peer = context->peerlist; for (; peer != NULL; peer = peer->next) { // Peer of Interest. A client also says bye to a host it has left, which a PSP's host gets // (pspautotests net/adhoc/pair/matching). if (peer->state == PSP_ADHOC_MATCHING_PEER_PARENT || peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_P2P || peer->state == PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS || (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && peer->state == PSP_ADHOC_MATCHING_PEER_OFFER)) { // Bye Opcode uint8_t opcode = PSP_ADHOC_MATCHING_PACKET_BYE; // Send Bye Packet context->socketlock->lock(); hleCall(sceNetAdhoc, int, sceNetAdhocPdpSend, currentMIPS, context->socket, (const char*)&peer->mac, (*context->peerPort)[peer->mac], &opcode, (u32)sizeof(opcode), 0, ADHOC_F_NONBLOCK); context->socketlock->unlock(); } } } /** * Handle Ping Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC */ void actOnPingPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); // Found Peer if (peer != NULL) { // Update Receive Timer peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; } } /** * Handle Hello Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnHelloPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, int32_t length) { // Interested in Hello Data if ((context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && findParent(context) == NULL) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { if (length < 5) { // Incomplete Packet Header return; } // Extract Optional Data Length int optlen = 0; memcpy(&optlen, context->rxbuf + 1, sizeof(optlen)); if (optlen < 0 || optlen > length - 5) { // Invalid packet return; } // Set Default Null Data void * opt = NULL; // Extract Optional Data Pointer if (optlen > 0) opt = context->rxbuf + 5; // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); // Peer not found if (peer == NULL) { // Allocate Memory peer = (SceNetAdhocMatchingMemberInternal *)malloc(sizeof(SceNetAdhocMatchingMemberInternal)); // Allocated Memory if (peer != NULL) { // Clear Memory memset(peer, 0, sizeof(SceNetAdhocMatchingMemberInternal)); // Copy Sender MAC peer->mac = *sendermac; // Set Peer State peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; // Initialize Ping Timer peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; peerlock.lock(); // Link Peer into List peer->next = context->peerlist; context->peerlist = peer; peerlock.unlock(); } } // Peer available now if (peer != NULL && peer->state != PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST && peer->state != PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { std::string hellohex; DataToHexString(10, 0, (u8*)opt, optlen, &hellohex); DEBUG_LOG(Log::sceNet, "HELLO Dump (%d bytes):\n%s", optlen, hellohex.c_str()); // Spawn Hello Event. Not from a peer in the middle of joining: on the PSP a client gets no hellos from // the host it's asking to join (pspautotests net/adhoc/pair/matching3), and Bleach 7 cancels the join // if it gets one. spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_HELLO, sendermac, optlen, opt); } } } /** * Handle Join Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, int32_t length) { if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) { // Child mode context return; } // We still got a unoccupied slot in our room (Parent / P2P) if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && countChildren(context) < (context->maxpeers - 1)) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { // Complete Packet Header available if (length >= 5) { // Extract Optional Data Length int optlen = 0; memcpy(&optlen, context->rxbuf + 1, sizeof(optlen)); // Complete Valid Packet available if (optlen >= 0 && optlen <= length - 5) { // Set Default Null Data void * opt = NULL; // Extract Optional Data Pointer if (optlen > 0) opt = context->rxbuf + 5; // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); // If we got the peer in the table already and are a parent, there is nothing left to be done. // This is because the only way a parent can know of a child is via a join request... // If we thus know of a possible child, then we already had a previous join request thus no need for double tapping. // A peer whose earlier request was cancelled or denied can ask again (pspautotests net/adhoc/pair/matching2). if (peer != NULL && peer->lastping != 0 && context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST || peer->state == PSP_ADHOC_MATCHING_PEER_CHILD)) { WARN_LOG(Log::sceNet, "Join Event(2) Ignored"); return; } // New Peer if (peer == NULL) { // Allocate Memory peer = (SceNetAdhocMatchingMemberInternal *)malloc(sizeof(SceNetAdhocMatchingMemberInternal)); // Allocated Memory if (peer != NULL) { // Clear Memory memset(peer, 0, sizeof(SceNetAdhocMatchingMemberInternal)); // Copy Sender MAC peer->mac = *sendermac; // Set Peer State peer->state = PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST; // Initialize Ping Timer peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; peerlock.lock(); // Link Peer into List peer->next = context->peerlist; context->peerlist = peer; peerlock.unlock(); // Spawn Request Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); // Return Success return; } } // Existing Peer (this case is only reachable for P2P mode) else { // Set Peer State peer->state = PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST; // Initialize Ping Timer peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); // Spawn Request Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); // Return Success return; } } } } WARN_LOG(Log::sceNet, "Join Event(2) Rejected"); // Auto-Reject Player sendCancelPacket(context, sendermac, 0, NULL); } /** * Handle Accept Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnAcceptPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, uint32_t length) { if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { // Parent context return; } // Don't have a master yet if ((context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && findParent(context) == NULL) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { if (length < 9) { // Incomplete Packet Header return; } // Extract Optional Data Length int optlen = 0; memcpy(&optlen, context->rxbuf + 1, sizeof(optlen)); // Extract Sibling Count int siblingcount = 0; memcpy(&siblingcount, context->rxbuf + 5, sizeof(siblingcount)); if (optlen < 0 || siblingcount < 0 || static_cast(length) < 9ULL + static_cast(optlen) + sizeof(SceNetEtherAddr) * static_cast(siblingcount)) { // Invalid packet return; } // Set Default Null Data void * opt = NULL; // Extract Optional Data Pointer if (optlen > 0) opt = context->rxbuf + 9; // Sibling MAC Array Null Data SceNetEtherAddr * siblings = NULL; // Extract Optional Sibling MAC Array if (siblingcount > 0) siblings = (SceNetEtherAddr *)(context->rxbuf + 9 + optlen); // Find Outgoing Request SceNetAdhocMatchingMemberInternal * request = findOutgoingRequest(context); // We are waiting for an answer to our request... if (request == NULL) { return; } // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); if (request != peer) { // It's not the answer we wanted! return; } // Change Peer State peer->state = (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) ? (PSP_ADHOC_MATCHING_PEER_PARENT) : (PSP_ADHOC_MATCHING_PEER_P2P); // Remove Unneeded Peer Information postAcceptCleanPeerList(context); // Add Sibling Peers if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) { // Add existing siblings postAcceptAddSiblings(context, siblingcount, siblings); // Add Self Peer to the following position (using peer->state = 0 to identify as Self) addMember(context, &context->mac); context->parentFirstAt = CoreTiming::GetGlobalTimeUsScaled() + context->keepalive_int; } // Spawn Accept Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ACCEPT, sendermac, optlen, opt); // Spawn Established Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ESTABLISHED, sendermac, 0, NULL); } } /** * Handle Cancel Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, int32_t length) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); if (peer == NULL) { // Interest Condition not fulfilled return; } if (length < 5) { // Incomplete Packet Header return; } // Extract Optional Data Length int optlen = 0; memcpy(&optlen, context->rxbuf + 1, sizeof(optlen)); if (optlen < 0 || optlen > length - 5) { // Invalid packet return; } // Set Default Null Data void * opt = NULL; // Extract Optional Data Pointer if (optlen > 0) opt = context->rxbuf + 5; // Get Outgoing Join Request SceNetAdhocMatchingMemberInternal* request = findOutgoingRequest(context); // Child Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) { // Get Parent SceNetAdhocMatchingMemberInternal* parent = findParent(context); // Join Request denied if (request == peer) { // Spawn Deny Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DENY, sendermac, optlen, opt); // Back to a host it only knows from its hellos: they keep coming, there's no TIMEOUT, and it can // ask again (pspautotests net/adhoc/pair/matching2 and matching3). peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; } // Kicked from Room else if (parent == peer) { // Iterate Peers SceNetAdhocMatchingMemberInternal * item = context->peerlist; for (; item != NULL; item = item->next) { // Established Peer if (item->state == PSP_ADHOC_MATCHING_PEER_CHILD || item->state == PSP_ADHOC_MATCHING_PEER_PARENT) { // Spawn Leave / Kick Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_LEAVE, &item->mac, optlen, opt); } } // Delete Peer from List clearPeerList(context); } } // Parent Mode else if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { // Cancel Join Request if (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { // Spawn Request Cancel Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_CANCEL, sendermac, optlen, opt); // No longer a request: accepting it gives TARGET_NOT_READY (pspautotests net/adhoc/pair/matching2). peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; } // Leave Room else if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) { // Spawn Leave / Kick Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_LEAVE, sendermac, optlen, opt); // No longer a member, and no TIMEOUT for it later; it's still known, so its BYE arrives when // it stops (pspautotests net/adhoc/pair/matching). peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; } } // P2P Mode else { // Get P2P Partner SceNetAdhocMatchingMemberInternal* p2p = findP2P(context); // Join Request denied if (request == peer) { // Spawn Deny Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DENY, sendermac, optlen, opt); // Back to a peer it only knows from its hellos, as in client mode: no TIMEOUT, and it can ask again // (pspautotests net/adhoc/pair/matching3). peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; } // Kicked from Room else if (p2p == peer) { // Spawn Leave / Kick Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_LEAVE, sendermac, optlen, opt); // Delete Peer from List //deletePeer(context, peer); peer->lastping = 0; } // Cancel Join Request else if (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { // Spawn Request Cancel Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_CANCEL, sendermac, optlen, opt); // Delete Peer from List //deletePeer(context, peer); peer->lastping = 0; } } } /** * Handle Bulk Data Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnBulkDataPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, int32_t length) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); // Established Peer if (peer != NULL && ( (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) || (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_PARENT)) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && peer->state == PSP_ADHOC_MATCHING_PEER_P2P))) { // Complete Packet Header available if (length > 5) { // Extract Data Length int datalen = 0; memcpy(&datalen, context->rxbuf + 1, sizeof(datalen)); // Complete Valid Packet available if (datalen > 0 && datalen <= length - 5) { // Extract Data void * data = context->rxbuf + 5; // Spawn Data Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DATA, sendermac, datalen, data); } } } } /** * Handle Birth Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnBirthPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, uint32_t length) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); if (peer == NULL || context->mode != PSP_ADHOC_MATCHING_MODE_CHILD || peer != findParent(context)) { // Invalid Circumstances return; } if (length < (1 + sizeof(SceNetEtherAddr))) { // Incomplete packet return; } // Extract Child MAC SceNetEtherAddr mac; memcpy(&mac, context->rxbuf + 1, sizeof(SceNetEtherAddr)); // A child accepted just after another already has it from its accept packet's sibling list. if (findPeer(context, &mac) != NULL || isMacMatch(&mac, &context->mac)) { return; } // Allocate Memory SceNetAdhocMatchingMemberInternal * sibling = (SceNetAdhocMatchingMemberInternal *)malloc(sizeof(SceNetAdhocMatchingMemberInternal)); if (sibling == NULL) { // Failed to allocate memory return; } // Clear Memory memset(sibling, 0, sizeof(SceNetAdhocMatchingMemberInternal)); // Save MAC Address sibling->mac = mac; // Set Peer State sibling->state = PSP_ADHOC_MATCHING_PEER_CHILD; // Initialize Ping Timer sibling->lastping = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0; peerlock.lock(); // Link Peer sibling->next = context->peerlist; context->peerlist = sibling; peerlock.unlock(); // Spawn Established Event. FIXME: ESTABLISHED event should only be triggered for Parent/P2P peer? //spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ESTABLISHED, &sibling->mac, 0, NULL); } /** * Handle Death Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC * @param length Packet Length */ void actOnDeathPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac, uint32_t length) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); if (peer == NULL || context->mode != PSP_ADHOC_MATCHING_MODE_CHILD || peer != findParent(context)) { // Invalid Circumstances return; } if (length < (1 + sizeof(SceNetEtherAddr))) { // Incomplete packet return; } // Extract Child MAC SceNetEtherAddr mac; memcpy(&mac, context->rxbuf + 1, sizeof(SceNetEtherAddr)); // Find Peer SceNetAdhocMatchingMemberInternal * deadkid = findPeer(context, &mac); if (deadkid == NULL || deadkid->state != PSP_ADHOC_MATCHING_PEER_CHILD) { // Invalid Sibling return; } // Spawn Leave Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_LEAVE, &mac, 0, NULL); // Delete Peer deletePeer(context, deadkid); } /** * Handle Bye Packet * @param context Matching Context Pointer * @param sendermac Packet Sender MAC */ void actOnByePacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sendermac) { // Find Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, sendermac); if (peer == NULL) { // We don't know this guy return; } // P2P or Child Bye. FIXME: Should we allow BYE Event to intervene joining process of Parent-Child too just like P2P Mode? (ie. Crazy Taxi uses P2P Mode) if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_OFFER)) || (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && (peer->state == PSP_ADHOC_MATCHING_PEER_P2P || peer->state == PSP_ADHOC_MATCHING_PEER_OFFER || peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST || peer->state == PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST || peer->state == PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS))) { if (context->mode != PSP_ADHOC_MATCHING_MODE_CHILD) { // Spawn Leave / Kick Event. FIXME: DISCONNECT event should only be triggered on Parent/P2P mode and for Parent/P2P peer? spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_BYE, sendermac, 0, NULL); } // Delete Peer deletePeer(context, peer); // Instead of removing peer immediately, We should give a little time before removing the peer and let it timed out? just in case the game is in the middle of communicating with the peer on another thread so it won't recognize it as Unknown peer //peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); } // Parent Bye else if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && peer->state == PSP_ADHOC_MATCHING_PEER_PARENT) { // Spawn Leave / Kick Event. FIXME: DISCONNECT event should only be triggered on Parent/P2P mode and for Parent/P2P peer? spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_BYE, sendermac, 0, NULL); // Delete Peer from List clearPeerList(context); } } // Hands the context's queued events to the game's handler, through __NetMatchingCallbacks. static void matchingDeliverEvents(SceNetAdhocMatchingContext *context) { u32 bufLen = context->rxbuflen; u32 bufAddr = 0; u32_le *args = context->handlerArgs; std::lock_guard guard(*context->eventlock); while (context->event_stack != NULL) { ThreadMessage *msg = context->event_stack; void *opt = NULL; if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); INFO_LOG(Log::sceNet, "EventLoop[%d]: Matching Event [%d=%s][%s] OptSize=%d", context->id, msg->opcode, getMatchingEventStr(msg->opcode), mac2str(&msg->mac).c_str(), msg->optlen); notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); context->event_stack = msg->next; free(msg); } } // Sends the packets SelectTarget, CancelTarget, SendData and the input handling itself queued. static void matchingSendQueued(SceNetAdhocMatchingContext *context) { std::lock_guard guard(*context->inputlock); while (context->input_stack != NULL) { ThreadMessage *msg = context->input_stack; void *opt = NULL; if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); context->input_stack = msg->next; free(msg); } } // One pass of a context's input handling: hellos, timeouts and keepalives when they're due, the // queued packets, then everything that has arrived. static void matchingHandleInput(SceNetAdhocMatchingContext *context) { auto n = GetI18NCategory(I18NCat::NETWORKING); SceNetEtherAddr sendermac; u32_le senderport; int rxbuflen; while (true) { u64 now = CoreTiming::GetGlobalTimeUsScaled(); // Hello Message Sending Context with unoccupied Slots if ((context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (countChildren(context) < (context->maxpeers - 1))) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && findP2P(context) == NULL)) { // Hello Message Broadcast necessary because of Hello Interval if (context->hello_int > 0 && static_cast(now - context->lastHello) >= static_cast(context->hello_int)) { broadcastHelloMessage(context); context->lastHello = now; } } handleDataTimeouts(context, now); // A PSP times out a peer exactly keepalive_int * init_count after its last keepalive, not on // its own keepalive tick (pspautotests net/adhoc/pair/vanishtiming). handleTimeout(context); if (context->keepalive_int > 0 && static_cast(now - context->lastPing) >= static_cast(context->keepalive_int)) { broadcastPingMessage(context); context->lastPing = now; } matchingSendQueued(context); // Receive PDP Datagram rxbuflen = context->rxbuflen; senderport = 0; int recvresult; { std::lock_guard peerGuard(peerlock); std::lock_guard socketGuard(*context->socketlock); recvresult = sceNetAdhocPdpRecv(currentMIPS, context->socket, &sendermac, &senderport, context->rxbuf, &rxbuflen, 0, ADHOC_F_NONBLOCK); } if (recvresult != 0 || rxbuflen <= 0) { break; } // Received Data from a Sender that interests us // Note: There are cases where the sender port might be re-mapped by router or ISP, so we shouldn't check the source port. if (context->rxbuf[0] > 1) { INFO_LOG(Log::sceNet, "InputLoop[%d]: Received %d Bytes (Opcode[%d]=%s)", context->id, rxbuflen, context->rxbuf[0], getMatchingOpcodeStr(context->rxbuf[0])); } // Update Peer Timestamp peerlock.lock(); SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac); if (peer != NULL) { now = CoreTiming::GetGlobalTimeUsScaled(); s64 delta = now - peer->last_recv; DEBUG_LOG(Log::sceNet, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta); } else { WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", context->id, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen); } // Show a warning if other player is having their port being re-mapped, thus that other player may have issue with the communication. // Note: That other player may need to switch side between host and join, or reboot their router to solve this issue. if (context->port != senderport && senderport != (*context->peerPort)[sendermac]) { char name[9] = {}; if (peer != NULL) truncate_cpy(name, sizeof(name), (const char*)peer->nickname.data); WARN_LOG(Log::sceNet, "InputLoop[%d]: Unknown Source Port from [%s][%s:%u -> %u] (Recved=%i, Length=%i)", context->id, name, mac2str(&sendermac).c_str(), senderport, context->port, recvresult, rxbuflen); g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(n->T("AM: Data from Unknown Port")) + std::string(" [") + std::string(name) + std::string("]:") + std::to_string(senderport) + std::string(" -> ") + std::to_string(context->port) + std::string(" (") + std::to_string(portOffset) + std::string(")"), 0.0f, "unknowndata"); } // Keep tracks of re-mapped peer's ports for further communication. // Note: This will only works if this player were able to receives data on normal port from other players (ie. this player's port wasn't remapped) (*context->peerPort)[sendermac] = senderport; peerlock.unlock(); switch (context->rxbuf[0]) { case PSP_ADHOC_MATCHING_PACKET_PING: actOnPingPacket(context, &sendermac); break; case PSP_ADHOC_MATCHING_PACKET_HELLO: actOnHelloPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_JOIN: actOnJoinPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_ACCEPT: actOnAcceptPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_CANCEL: actOnCancelPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_BULK: actOnBulkDataPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_BIRTH: actOnBirthPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_DEATH: actOnDeathPacket(context, &sendermac, rxbuflen); break; case PSP_ADHOC_MATCHING_PACKET_BYE: actOnByePacket(context, &sendermac); break; default: break; // Ignore Incoming Trash Data } } } // Runs every running context's input handling and event delivery, in emulated time, while any is running. static void __NetMatchingPoll(u64 userdata, int cyclesLate) { bool anyRunning = false; SceNetAdhocMatchingContext *next = NULL; for (SceNetAdhocMatchingContext *context = contexts; context != NULL; context = next) { next = context->next; if (context->inputRunning) { matchingHandleInput(context); } if (context->eventRunning) { matchingDeliverEvents(context); } anyRunning = anyRunning || context->inputRunning || context->eventRunning; } if (anyRunning) { CoreTiming::ScheduleEvent(usToCycles(MATCHING_POLL_INTERVAL_US) - cyclesLate, matchingPollEvent, 0); } } static void ScheduleMatchingPoll() { if (!CoreTiming::IsScheduled(matchingPollEvent)) { CoreTiming::ScheduleEvent(usToCycles(MATCHING_POLL_INTERVAL_US), matchingPollEvent, 0); } } void DoNetAdhocMatchingPollEvent(PointerWrap &p, bool saved) { if (saved) { Do(p, matchingPollEvent); } else { matchingPollEvent = -1; } CoreTiming::RestoreRegisterEvent(matchingPollEvent, "__NetMatchingPoll", __NetMatchingPoll); // The contexts aren't in the state, so the ones still running here need the poll whatever the state had. if (p.mode == PointerWrap::MODE_READ) { for (SceNetAdhocMatchingContext *context = contexts; context != NULL; context = context->next) { if (context->inputRunning || context->eventRunning) { ScheduleMatchingPoll(); break; } } } } // What the input handling does as it stops: sends what's still queued, then the bye. static void matchingStopInput(SceNetAdhocMatchingContext *context) { matchingSendQueued(context); clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK); // FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag sendByePacket(context); clearPeerList(context); } void netAdhocMatchingValidateLoopMemory() { if (!matchingThreadHackAddr || (matchingThreadHackAddr && strcmp("matchingThreadHack", kernelMemory.GetBlockTag(matchingThreadHackAddr)) != 0)) { u32 blockSize = sizeof(matchingThreadCode); matchingThreadHackAddr = kernelMemory.Alloc(blockSize, false, "matchingThreadHack"); if (matchingThreadHackAddr) Memory::Memcpy(matchingThreadHackAddr, matchingThreadCode, sizeof(matchingThreadCode)); } } int NetAdhocMatching_Stop(int matchingId) { SceNetAdhocMatchingContext* item = findMatchingContext(matchingId); if (item == NULL) { return 0; } // This will cause using PdpRecv on this socket to return ERROR_NET_ADHOC_SOCKET_ALERTED (Based on Ys vs. Sora no Kiseki when tested with JPCSP + prx files). Is this used to abort inprogress socket activity? NetAdhoc_SetSocketAlert(item->socket, ADHOC_F_ALERTRECV); if (item->inputRunning) { item->inputRunning = false; matchingStopInput(item); } if (item->eventRunning) { item->eventRunning = false; matchingDeliverEvents(item); } // Stop fake PSP Thread. // kernelObjects may already been cleared early during a Shutdown, thus trying to access it may generates Warning/Error in the log if (matchingThreads[item->matching_thid] > 0 && strcmp(__KernelGetThreadName(matchingThreads[item->matching_thid]), "ERROR") != 0) { __KernelStopThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching stopped"); __KernelDeleteThread(matchingThreads[item->matching_thid], SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); } 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(); // Remove your own MAC, or All members, or don't remove at all or we should do this on MatchingDelete ? clearPeerList(item); //deleteAllMembers(item); item->running = 0; netAdhocMatchingStarted--; // Multithreading Unlock peerlock.unlock(); return 0; } int sceNetAdhocMatchingStop(MIPSState *mips, int matchingId) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStop(%i) at %08x", matchingId, mips->pc); if (!findMatchingContext(matchingId)) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); } return NetAdhocMatching_Stop(matchingId); } int NetAdhocMatching_Delete(int matchingId) { // A running context can't be deleted, so stop it first. SceNetAdhocMatchingContext *item = findMatchingContext(matchingId); if (item && item->running) { NetAdhocMatching_Stop(matchingId); } // Multithreading Lock std::lock_guard peer_guard(peerlock); // Previous Context Reference SceNetAdhocMatchingContext* prev = NULL; // Context Pointer SceNetAdhocMatchingContext* context = contexts; // Iterate contexts for (; context != NULL; context = context->next) { // Found matching ID if (context->id == matchingId) { // Unlink Left (Beginning) if (prev == NULL) contexts = context->next; // Unlink Left (Other) else prev->next = context->next; // Delete the Fake PSP Thread //__KernelDeleteThread(item->matching_thid, SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); //delete item->matchingThread; // Free allocated memories free(context->hello); free(context->rxbuf); clearPeerList(context); //deleteAllMembers(item); (*context->peerPort).clear(); delete context->peerPort; // Destroy locks context->eventlock->lock(); // Make sure it's not locked when being deleted context->eventlock->unlock(); delete context->eventlock; context->inputlock->lock(); // Make sure it's not locked when being deleted context->inputlock->unlock(); delete context->inputlock; context->socketlock->lock(); // Make sure it's not locked when being deleted context->socketlock->unlock(); delete context->socketlock; // Free item context memory delete context; context = nullptr; // Making sure there are no leftover matching events from this session which could cause a crash on the next session deleteMatchingEvents(matchingId); // Stop Search break; } // Set Previous Reference prev = context; } return 0; } int sceNetAdhocMatchingDelete(MIPSState *mips, int matchingId) { // WLAN might be disabled in the middle of successfull multiplayer, but we still need to cleanup right? SceNetAdhocMatchingContext *item = findMatchingContext(matchingId); if (!item) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); } // It has to be stopped first (pspautotests net/adhoc/matching). if (item->running) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_IS_RUNNING, "still running"); } NetAdhocMatching_Delete(matchingId); WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingDelete(%i) at %08x", matchingId, mips->pc); // Give a little time to make sure everything are cleaned up before the following AdhocMatchingCreate, Not too long tho, otherwise Naruto Ultimate Ninja Heroes 3 will have an issue //hleDelayResult(0, "give time to init/cleanup", adhocExtraPollDelayMS * 1000); return 0; } int sceNetAdhocMatchingInit(MIPSState *mips, u32 memsize) { WARN_LOG_REPORT_ONCE(sceNetAdhocMatchingInit, Log::sceNet, "sceNetAdhocMatchingInit(%d) at %08x", memsize, mips->pc); // Uninitialized Library if (netAdhocMatchingInited) return SCE_NET_ADHOC_MATCHING_ERROR_ALREADY_INITIALIZED; // Save Fake Pool Size fakePoolSize = memsize; // Initialize Library deleteMatchingEvents(); netAdhocMatchingInited = true; // Return Success return 0; } int NetAdhocMatching_Term() { if (netAdhocMatchingInited) { // Delete all Matching contexts SceNetAdhocMatchingContext* next = NULL; SceNetAdhocMatchingContext* context = contexts; while (context != NULL) { next = context->next; //if (context->running) NetAdhocMatching_Stop(context->id); NetAdhocMatching_Delete(context->id); context = next; } contexts = NULL; matchingThreads.clear(); } return 0; } int sceNetAdhocMatchingTerm(MIPSState *mips) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingTerm() at %08x", mips->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(); netAdhocMatchingInited = false; return 0; } // Presumably returns a "matchingId". static int sceNetAdhocMatchingCreate(MIPSState *mips, int mode, int maxnum, int port, int rxbuflen, int hello_int, int keepalive_int, int init_count, int rexmt_int, u32 callbackAddr) { WARN_LOG(Log::sceNet, "sceNetAdhocMatchingCreate(mode=%i, maxnum=%i, port=%i, rxbuflen=%i, hello=%i, keepalive=%i, initcount=%i, rexmt=%i, callbackAddr=%08x) at %08x", mode, maxnum, port, rxbuflen, hello_int, keepalive_int, init_count, rexmt_int, callbackAddr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } SceNetAdhocMatchingHandler handler; handler.entryPoint = callbackAddr; if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhoc matching not initialized"); } if (maxnum <= 1 || maxnum > 16) { // Invalid Member Limit return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_MAXNUM, "adhoc matching invalid maxnum"); } // The limits below are from pspautotests net/adhoc/matching. if (rxbuflen < 6 * maxnum + 4) { // Invalid Receive Buffer Size return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_RXBUF_TOO_SHORT, "adhoc matching rxbuf too short"); } if (mode < 1 || mode > 3) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_MODE, "adhoc matching invalid mode"); } // Only a client may leave the hello and keep-alive intervals at 0. const bool sendsHello = mode == PSP_ADHOC_MATCHING_MODE_PARENT || mode == PSP_ADHOC_MATCHING_MODE_P2P; if ((sendsHello && (hello_int == 0 || keepalive_int == 0)) || init_count < 0 || rexmt_int == 0) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhoc matching invalid arg"); } // Iterate Matching Contexts SceNetAdhocMatchingContext * item = contexts; for (; item != NULL; item = item->next) { // Port Match found if (item->port == port) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_PORT_IN_USE, "adhoc matching port in use"); } // At most 15 contexts at a time (pspautotests net/adhoc/limits). int contextCount = 0; for (item = contexts; item != NULL; item = item->next) { contextCount++; } if (contextCount >= 15) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_ID_NOT_AVAIL, "too many contexts"); } // Allocate Context Memory SceNetAdhocMatchingContext *context = new SceNetAdhocMatchingContext(); // Allocated Memory if (context) { // This can't be null but let's not reformat all this code.. // Create PDP Socket SceNetEtherAddr localmac; getLocalMac(&localmac); // Clear Memory *context = {}; // Allocate Receive Buffer context->rxbuf = (uint8_t*)malloc(rxbuflen); // Allocated Memory if (context->rxbuf != NULL) { // Clear Memory memset(context->rxbuf, 0, 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 // Timed from the peer's last keepalive (pspautotests net/adhoc/pair/vanishtiming). context->timeout = (u64)context->keepalive_int * (u64)std::max(1, init_count) + MATCHING_TIMEOUT_EXTRA_US; context->handler = handler; context->peerPort = new std::map(); // Fill in Selfpeer context->mac = localmac; // Create locks context->socketlock = new std::recursive_mutex; context->eventlock = new std::recursive_mutex; context->inputlock = new std::recursive_mutex; // 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; // Multithreading UnLock peerlock.unlock(); //contextlock.unlock(); // Just to make sure Adhoc is already connected //hleDelayResult(context->id, "give time to init/cleanup", adhocEventDelayMS * 1000); // Return Matching ID return hleLogDebug(Log::sceNet, context->id, "success"); } // Free Memory delete context; } // Out of Memory return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NO_SPACE, "adhoc matching no space"); } int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int evthStack, int inthPri, int inthPartitionId, int inthStack, int optLen, u32 optDataAddr) { // Multithreading Lock std::lock_guard peer_guard(peerlock); SceNetAdhocMatchingContext* item = findMatchingContext(matchingId); if (item == NULL) { // GTA: VCS gets stuck unable to choose host/join otherwise. if (PSP_CoreParameter().compat.flags().MatchingStartUnknownIdSucceeds) { return hleLogDebug(Log::sceNet, 0, "faking success"); } return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); } if (item->running) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_IS_RUNNING, "already running"); } //sceNetAdhocMatchingSetHelloOpt(matchingId, optLen, optDataAddr); //SetHelloOpt only works when context is running if ((optLen > 0) && Memory::IsValidAddress(optDataAddr)) { // Allocate the memory and copy the content free(item->hello); item->hello = (uint8_t*)malloc(optLen); if (item->hello != NULL) { Memory::Memcpy(item->hello, optDataAddr, optLen); item->hellolen = optLen; item->helloAddr = optDataAddr; } //else return SCE_NET_ADHOC_MATCHING_ERROR_NO_SPACE; //Faking success to prevent GTA:VCS from stuck unable to choose host/join menu } //else return SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG; // SCE_NET_ADHOC_MATCHING_ERROR_INVALID_OPTLEN; // Returning Not Success will cause GTA:VC stuck unable to choose host/join menu // Create PDP Socket int sock = NetAdhocPdpCreate((const char*)&item->mac, static_cast(item->port), item->rxbuflen, 0, true); item->socket = sock; if (sock < 1) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_PORT_IN_USE, "adhoc matching port in use"); } // Create & Start the Fake PSP Thread ("matching_ev%d" and "matching_io%d") netAdhocMatchingValidateLoopMemory(); std::string thrname = std::string("MatchingThr") + std::to_string(matchingId); // Created and started without rescheduling, like adhocctl's thread: starting it through // sceKernelStartThread switched threads inside this call, and the game got the thread's ID back // instead of 0 (pspautotests net/adhoc/matching). matchingThreads[item->matching_thid] = __KernelCreateThread(thrname.c_str(), __KernelGetCurThreadModuleId(), matchingThreadHackAddr, evthPri, evthStack, PSP_THREAD_ATTR_USER, 0, true); //item->matchingThread = new HLEHelperThread(thrname.c_str(), "sceNetAdhocMatching", "__NetMatchingCallbacks", inthPri, inthStack); if (matchingThreads[item->matching_thid] > 0) { __KernelStartThread(matchingThreads[item->matching_thid], 0, 0); //item->matchingThread->Start(matchingId, 0); } // The first hello and keepalive go out on the first poll. item->lastHello = 0; item->lastPing = 0; item->eventRunning = true; item->inputRunning = true; ScheduleMatchingPoll(); item->running = 1; netAdhocMatchingStarted++; return hleLogDebug(Log::sceNet, 0); } // This should be similar with sceNetAdhocMatchingStart2 but using USER_PARTITION_ID (2) for PartitionId params // (KERNEL_PARTITION_ID/USER_PARTITION_ID/VSHELL_PARTITION_ID come from Core/HLE/sceKernelMemory.h) static int sceNetAdhocMatchingStart(MIPSState *mips, int matchingId, int evthPri, int evthStack, int inthPri, int inthStack, int optLen, u32 optDataAddr) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStart(%i, %i, %i, %i, %i, %i, %08x) at %08x", matchingId, evthPri, evthStack, inthPri, inthStack, optLen, optDataAddr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } int retval = NetAdhocMatching_Start(matchingId, evthPri, USER_PARTITION_ID, evthStack, inthPri, USER_PARTITION_ID, inthStack, optLen, optDataAddr); // 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? hleEatMicro(adhocMatchingEventDelay); return retval; } // With params for Partition ID for the event & input handler stack. Each comes after its stack size // (pspautotests net/adhoc/matching). static int sceNetAdhocMatchingStart2(MIPSState *mips, int matchingId, int evthPri, int evthStack, int evthPartitionId, int inthPri, int inthStack, int inthPartitionId, int optLen, u32 optDataAddr) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingStart2(%i, %i, %i, %i, %i, %i, %i, %i, %08x) at %08x", matchingId, evthPri, evthStack, evthPartitionId, inthPri, inthStack, inthPartitionId, optLen, optDataAddr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } // A tiny stack size (where a partition was passed) gives STACKSIZE_TOO_SHORT, then a bad partition // INVALID_ID (pspautotests net/adhoc/matching). How small a stack still works isn't known; 0x1000 does. if (evthStack < 0x100 || inthStack < 0x100) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_STACKSIZE_TOO_SHORT, "stack size too short"); } if (evthPartitionId < 1 || evthPartitionId > 6 || inthPartitionId < 1 || inthPartitionId > 6) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid partition"); } int retval = NetAdhocMatching_Start(matchingId, evthPri, evthPartitionId, evthStack, inthPri, inthPartitionId, inthStack, optLen, optDataAddr); // 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? hleEatMicro(adhocMatchingEventDelay); return retval; } static int sceNetAdhocMatchingSelectTarget(MIPSState *mips, int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingSelectTarget(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); } if (macAddress == NULL) { // Invalid Arguments return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); } SceNetEtherAddr * target = (SceNetEtherAddr *)macAddress; // Find Matching Context for ID SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); if (context == NULL) { // Invalid Matching ID return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "adhocmatching invalid id"); } if (!context->running) { // Idle Context return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "adhocmatching not running"); } // Search Result SceNetAdhocMatchingMemberInternal * peer = findPeer(context, (SceNetEtherAddr *)target); if (peer == NULL) { // Peer not found return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); } if ((optLen != 0) && (optLen <= 0 || optDataPtr == 0)) { // Invalid Optional Data Length return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_OPTLEN, "adhocmatching invalid optlen"); } void * opt = NULL; if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointerWriteUnchecked(optDataPtr); // Host Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { // Already Connected. Right after accepting, it's still in progress (pspautotests net/adhoc/pair/matching). if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD && peer->accepting) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_REQUEST_IN_PROGRESS, "adhocmatching request in progress"); if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_ALREADY_ESTABLISHED, "adhocmatching already established"); // Not enough space if (countChildren(context) == (context->maxpeers - 1)) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_EXCEED_MAXNUM, "adhocmatching exceed maxnum"); // Requesting Peer if (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { // Accept Peer in Group peer->state = PSP_ADHOC_MATCHING_PEER_CHILD; peer->accepting = 1; // Tell Children about new Sibling sendBirthMessage(context, peer); // Spawn Established Event //spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ESTABLISHED, target, 0, NULL); // Send Accept Confirmation to Peer sendAcceptMessage(context, peer, optLen, opt); // Return Success return hleLogDebug(Log::sceNet, 0); } } // Client Mode else if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) { // Already connected if (findParent(context) != NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_ALREADY_ESTABLISHED, "adhocmatching already established"); // Outgoing Request in Progress if (findOutgoingRequest(context) != NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_REQUEST_IN_PROGRESS, "adhocmatching request in progress"); // Valid Offer if (peer->state == PSP_ADHOC_MATCHING_PEER_OFFER) { // Switch into Join Request Mode peer->state = PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST; // Send Join Request to Peer sendJoinRequest(context, peer, optLen, opt); // Return Success return hleLogDebug(Log::sceNet, 0); } } // P2P Mode else { // Already connected if (findP2P(context) != NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_ALREADY_ESTABLISHED, "adhocmatching already established"); // Outgoing Request in Progress if (findOutgoingRequest(context) != NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_REQUEST_IN_PROGRESS, "adhocmatching request in progress"); // Join Request Mode if (peer->state == PSP_ADHOC_MATCHING_PEER_OFFER) { // Switch into Join Request Mode peer->state = PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST; // Send Join Request to Peer sendJoinRequest(context, peer, optLen, opt); // Return Success return hleLogDebug(Log::sceNet, 0); } // Requesting Peer else if (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { // Accept Peer in Group peer->state = PSP_ADHOC_MATCHING_PEER_P2P; // Tell Children about new Sibling //sendBirthMessage(context, peer); // Send Accept Confirmation to Peer sendAcceptMessage(context, peer, optLen, opt); // Return Success return hleLogDebug(Log::sceNet, 0); } } // How did this happen?! It shouldn't! return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_TARGET_NOT_READY, "adhocmatching target not ready"); } int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, int optLen, u32 optDataPtr) { if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); } SceNetEtherAddr* target = (SceNetEtherAddr*)macAddress; void* opt = NULL; if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointerWriteUnchecked(optDataPtr); if (target == NULL || ((optLen != 0) && (optLen <= 0 || opt == NULL))) { // Invalid Arguments return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); } // Find Matching Context SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); if (context == NULL) { // Invalid Matching ID return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "adhocmatching invalid id"); } if (!context->running) { // Context not running return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "adhocmatching not running"); } // Find Peer SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)target); if (peer == NULL) { // Soulcalibur: Broken Destiny calls this over and over when the other player has gone, unless it succeeds. if (PSP_CoreParameter().compat.flags().MatchingCancelUnknownTargetSucceeds) { return hleLogDebug(Log::sceNet, 0, "faking success"); } return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); } // Valid Peer Mode if ((context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && (peer->state == PSP_ADHOC_MATCHING_PEER_PARENT || peer->state == PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST)) || (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST)) || (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && (peer->state == PSP_ADHOC_MATCHING_PEER_P2P || peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST))) { const bool notifyDeath = context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && peer->state == PSP_ADHOC_MATCHING_PEER_CHILD && countConnectedPeers(context) > 1; // A P2P peer whose request is denied goes back to one known from its hellos: no TIMEOUT, and it // can ask again (pspautotests net/adhoc/pair/matching3). if (context->mode == PSP_ADHOC_MATCHING_MODE_P2P && peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { sendCancelMessage(context, peer, optLen, opt); peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); hleEatCycles(adhocDefaultDelay); return hleLogDebug(Log::sceNet, 0); } // Mark Peer as Canceled peer->state = PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS; // Send Cancel Event to Peer. This goes first: the death message also sends the peer a BYE, which // would make a kicked client see DISCONNECT instead of LEAVE (pspautotests net/adhoc/pair/matching2). sendCancelMessage(context, peer, optLen, opt); // Notify other Children of Death if (notifyDeath) { // Send Death Message sendDeathMessage(context, peer); } // Delete Peer from List // Can't delete here, Threads still need this data. // deletePeer(context, peer); // Marking peer to be timedout instead of deleting immediately peer->lastping = 0; hleEatCycles(adhocDefaultDelay); // Return Success return hleLogDebug(Log::sceNet, 0); } // Peer not found //return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); // Faking success to prevent the game (ie. Soul Calibur) to repeatedly calling this function when the other player is disconnected return hleLogDebug(Log::sceNet, 0); } int sceNetAdhocMatchingCancelTargetWithOpt(MIPSState *mips, int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingCancelTargetWithOpt(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, optLen, optDataPtr); } int sceNetAdhocMatchingCancelTarget(int matchingId, const char *macAddress) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingCancelTarget(%i, %s)", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str()); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, 0, 0); } int sceNetAdhocMatchingGetHelloOpt(int matchingId, u32 optLenAddr, u32 optDataAddr) { if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!Memory::IsValidAddress(optLenAddr)) { return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG); } s32_le *optlen = PSPPointer::Create(optLenAddr); // Multithreading Lock peerlock.lock(); SceNetAdhocMatchingContext * item = findMatchingContext(matchingId); if (item != NULL && !item->running) { peerlock.unlock(); return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "not running"); } if (item != NULL) { // Copies at most the game's buffer size, and leaves the length alone (pspautotests net/adhoc/matching). const int copyLen = std::min((int)*optlen, (int)item->hellolen); if (copyLen > 0 && Memory::IsValidRange(optDataAddr, copyLen)) { uint8_t * optdata = Memory::GetPointerWriteUnchecked(optDataAddr); memcpy(optdata, item->hello, copyLen); } //else return SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG; } //else return SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID; // Multithreading Unlock peerlock.unlock(); return hleLogDebug(Log::sceNet, 0); } int sceNetAdhocMatchingSetHelloOpt(int matchingId, int optLenAddr, u32 optDataAddr) { if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); // Multithreading Lock peerlock.lock(); SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); // Multithreading Unlock peerlock.unlock(); // Context not found if (context == NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "adhocmatching invalid id"); // Invalid Matching Mode (Child) if (context->mode == PSP_ADHOC_MATCHING_MODE_CHILD) return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_MODE, "adhocmatching invalid mode"); // Context not running if (!context->running) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "adhocmatching not running"); // Invalid Optional Data Length if ((optLenAddr != 0) && (optDataAddr == 0)) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_OPTLEN, "adhocmatching invalid optlen"); //SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG // Grab Existing Hello Data void* hello = context->hello; // Free Previous Hello Data, or Reuse it //free(hello); // Allocation Required if (optLenAddr > 0) { // Allocate Memory if (optLenAddr > context->hellolen) { hello = realloc(hello, optLenAddr); } // Out of Memory if (hello == NULL) { context->hellolen = 0; return SCE_NET_ADHOC_MATCHING_ERROR_NO_SPACE; } // Clone Hello Data Memory::Memcpy(hello, optDataAddr, optLenAddr); // Set Hello Data context->hello = (uint8_t*)hello; context->hellolen = optLenAddr; context->helloAddr = optDataAddr; } else { // Delete Hello Data context->hellolen = 0; context->helloAddr = 0; //free(context->hello); // Doesn't need to free it since it will be reused later //context->hello = NULL; } // Return Success return hleLogDebug(Log::sceNet, 0); } static int sceNetAdhocMatchingGetMembers(int matchingId, u32 sizeAddr, u32 buf) { if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) return hleLogDebug(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); // Minimum Argument if (!Memory::IsValidAddress(sizeAddr)) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); // Multithreading Lock peerlock.lock(); // Find Matching Context SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); // Multithreading Unlock peerlock.unlock(); // Context not found if (context == NULL) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "adhocmatching invalid id"); // Context not running if (!context->running) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "adhocmatching not running"); // Buffer Length not available if (!Memory::IsValidAddress(sizeAddr)) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); int *buflen = (int*)Memory::GetPointerUnchecked(sizeAddr); SceNetAdhocMatchingMemberInfoEmu* buf2 = NULL; if (Memory::IsValidAddress(buf)) { buf2 = (SceNetAdhocMatchingMemberInfoEmu*)Memory::GetPointerUnchecked(buf); } // Number of Connected Peers, should we exclude timeout members? bool excludeTimedout = false; // false; uint32_t peercount = countConnectedPeers(context, excludeTimedout); // Calculate Connected Peer Bytesize int available = sizeof(SceNetAdhocMatchingMemberInfoEmu) * peercount; // Length Returner Mode if (buf == 0) { // Get Connected Peer Count *buflen = available; DEBUG_LOG(Log::sceNet, "MemberList [Connected: %i]", peercount); } // Normal Mode else { // Fix Negative Length if ((*buflen) < 0) *buflen = 0; // Fix Oversize Request if ((*buflen) > available) *buflen = available; // Clear Memory memset(buf2, 0, *buflen); // Calculate Requested Peer Count int requestedpeers = (*buflen) / sizeof(SceNetAdhocMatchingMemberInfoEmu); // Filled Request Counter int filledpeers = 0; if (requestedpeers > 0) { // The console itself comes first, also for a child until a keepalive interval after it was // accepted. Then the parent does, and the child is listed among the siblings // (pspautotests net/adhoc/pair/matching, net/adhoc/pair/matchingorder). bool parentFirst = context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && context->parentFirstAt != 0 && CoreTiming::GetGlobalTimeUsScaled() >= context->parentFirstAt && findParent(context) != NULL; if (!parentFirst) { // Add Local MAC buf2[filledpeers++].mac_addr = context->mac; DEBUG_LOG(Log::sceNet, "MemberSelf [%s]", mac2str(&context->mac).c_str()); } // Room for more than local peer if (requestedpeers > 1) { // P2P Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_P2P) { // Find P2P Brother SceNetAdhocMatchingMemberInternal* p2p = findP2P(context, excludeTimedout); // P2P Brother found if (p2p != NULL) { // Faking lastping auto friendpeer = findFriend(&p2p->mac); if (p2p->lastping != 0 && friendpeer != NULL && friendpeer->last_recv != 0) p2p->lastping = std::max(p2p->lastping, CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta); else p2p->lastping = 0; // Add P2P Brother MAC buf2[filledpeers++].mac_addr = p2p->mac; DEBUG_LOG(Log::sceNet, "MemberP2P [%s]", mac2str(&p2p->mac).c_str()); } } // Parent or Child Mode else { // Add Parent first SceNetAdhocMatchingMemberInternal* parentpeer = findParent(context); if (parentpeer != NULL) { // Faking lastping auto friendpeer = findFriend(&parentpeer->mac); if (parentpeer->lastping != 0 && friendpeer != NULL && friendpeer->last_recv != 0) parentpeer->lastping = std::max(parentpeer->lastping, CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta); else parentpeer->lastping = 0; // Add Parent MAC buf2[filledpeers++].mac_addr = parentpeer->mac; DEBUG_LOG(Log::sceNet, "MemberParent [%s]", mac2str(&parentpeer->mac).c_str()); } // We may need to rearrange children where last joined player placed last std::deque sortedPeers; // Iterate Peer List SceNetAdhocMatchingMemberInternal* peer = context->peerlist; for (; peer != NULL && filledpeers < requestedpeers; peer = peer->next) { // Should we exclude timedout members? if (!excludeTimedout || peer->lastping != 0) { // Faking lastping auto friendpeer = findFriend(&peer->mac); if (peer->lastping != 0 && friendpeer != NULL && friendpeer->last_recv != 0) peer->lastping = std::max(peer->lastping, CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta); else peer->lastping = 0; // Add Peer MAC sortedPeers.push_front(peer); } } // Iterate rearranged peers for (const auto& peer : sortedPeers) { // Parent Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { // Interested in Children if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) { // Add Child MAC buf2[filledpeers++].mac_addr = peer->mac; DEBUG_LOG(Log::sceNet, "MemberChild [%s]", mac2str(&peer->mac).c_str()); } } // Child Mode else { // Interested in Siblings if (peer->state == PSP_ADHOC_MATCHING_PEER_CHILD) { // Add Peer MAC buf2[filledpeers++].mac_addr = peer->mac; DEBUG_LOG(Log::sceNet, "MemberSibling [%s]", mac2str(&peer->mac).c_str()); } else if (parentFirst && peer->state == 0) { // Self Peer buf2[filledpeers++].mac_addr = peer->mac; DEBUG_LOG(Log::sceNet, "MemberSelf [%s]", mac2str(&peer->mac).c_str()); } } } sortedPeers.clear(); } } // Link Result List for (int i = 0; i < filledpeers - 1; i++) { // Link Next Element //buf2[i].next = &buf2[i + 1]; buf2[i].next = buf + (sizeof(SceNetAdhocMatchingMemberInfoEmu) * (i + 1LL)); } // Fix Last Element if (filledpeers > 0) buf2[filledpeers - 1].next = 0; } // Fix Buffer Size *buflen = sizeof(SceNetAdhocMatchingMemberInfoEmu) * filledpeers; DEBUG_LOG(Log::sceNet, "MemberList [Requested: %i][Discovered: %i]", requestedpeers, filledpeers); } hleEatMicro(4); return hleLogDebug(Log::sceNet, 0); } // Gran Turismo may replace the 1st bit of the 1st byte of MAC address's OUI with 0 (unicast bit), or replace the whole 6-bytes of MAC address with all 00 (invalid mac) for unknown reason int sceNetAdhocMatchingSendData(MIPSState *mips, int matchingId, const char *mac, int dataLen, u32 dataAddr) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingSendData(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)mac).c_str(), dataLen, dataAddr, mips->pc); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "not initialized"); } if (mac == NULL) { // Invalid Arguments return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "invalid arg"); } // Find Matching Context SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); if (context == NULL) { // Invalid Matching ID return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "invalid id"); } if (!context->running) { // Context not running return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "not running"); } // Invalid Data Length if (dataLen <= 0 || dataAddr == 0) { // Invalid Data Length return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_DATALEN, "invalid datalen"); } void* data = NULL; if (Memory::IsValidAddress(dataAddr)) data = Memory::GetPointerWriteUnchecked(dataAddr); // Lock the peer std::lock_guard peer_guard(peerlock); // Find Target Peer SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)mac); if (peer == NULL) { // Peer not found return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "unknown target"); } if (peer->state != PSP_ADHOC_MATCHING_PEER_PARENT && peer->state != PSP_ADHOC_MATCHING_PEER_CHILD && peer->state != PSP_ADHOC_MATCHING_PEER_P2P) { // Not connected / accepted return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_ESTABLISHED, "not established"); } // Send in Progress if (peer->sending) return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_DATA_BUSY, "data busy"); // Mark Peer as Sending peer->sending = 1; // Send Data to Peer sendBulkDataPacket(context, &peer->mac, dataLen, data); // Return Success return hleLogDebug(Log::sceNet, 0); } int sceNetAdhocMatchingAbortSendData(int matchingId, const char *mac) { WARN_LOG(Log::sceNet, "UNTESTED sceNetAdhocMatchingAbortSendData(%i, %s)", matchingId, mac2str((SceNetEtherAddr*)mac).c_str()); if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); } if (mac == NULL) { // Invalid Arguments return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); } // Find Matching Context SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); if (context == NULL) { // Invalid Matching ID return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ID, "adhocmatching invalid id"); } if (!context->running) { // Context not running return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_RUNNING, "adhocmatching not running"); } // Find Target Peer SceNetAdhocMatchingMemberInternal * peer = findPeer(context, (SceNetEtherAddr *)mac); if (peer == NULL) { // Peer not found return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_UNKNOWN_TARGET, "adhocmatching unknown target"); } // Peer is sending // On a PSP, the data still arrives, but there's no DATA_ACK for it, and the peer can send again at // once (pspautotests net/adhoc/pair/matching2). Our data has already gone out; while sending is set, // its DATA_ACK hasn't been delivered, so it's discarded when it comes up. if (peer->sending) { peer->sending = 0; peer->ackAborted = 1; } // Return Success return hleLogDebug(Log::sceNet, 0); } // A PSP keeps 0x20 bytes of the pool for itself. Its maximum is the largest allocation so far: 0 right // after Init (pspautotests net/adhoc/matching). Once there's a context, half the pool, which // isn't measured. static u32 FakePoolSize() { return fakePoolSize >= 0x20 ? fakePoolSize - 0x20 : 0; } static u32 FakePoolMaxAlloc() { return contexts != NULL ? FakePoolSize() / 2 : 0; } // Get the maximum memory usage by the matching library static int sceNetAdhocMatchingGetPoolMaxAlloc() { if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } return hleLogDebug(Log::sceNet, FakePoolMaxAlloc(), "faked value"); } int sceNetAdhocMatchingGetPoolStat(u32 poolstatPtr) { if (!g_Config.bEnableWlan) { return hleLogError(Log::sceNet, -1, "WLAN off"); } if (!netAdhocMatchingInited) { // Uninitialized Library return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_NOT_INITIALIZED, "adhocmatching not initialized"); } SceNetMallocStat * poolstat = NULL; if (Memory::IsValidAddress(poolstatPtr)) poolstat = (SceNetMallocStat *)Memory::GetPointerOrException(poolstatPtr); if (poolstat == NULL) { // Invalid Argument return hleLogError(Log::sceNet, SCE_NET_ADHOC_MATCHING_ERROR_INVALID_ARG, "adhocmatching invalid arg"); } // Fake data, apart from right after Init. poolstat->pool = FakePoolSize(); poolstat->maximum = FakePoolMaxAlloc(); poolstat->free = poolstat->pool - poolstat->maximum; // Return Success return hleLogDebug(Log::sceNet, 0); } void __NetMatchingCallbacks() { //(int matchingId) std::lock_guard adhocGuard(adhocEvtMtx); hleSkipDeadbeef(); // Note: Super Pocket Tennis / Thrillville Off the Rails seems to have a very short timeout (ie. ~5ms) while waiting for the event to arrived on the callback handler, but Lord of Arcana may not work well with 5ms (~3m or ~10ms seems to be good) // Games with 4-players or more (ie. Gundam: Senjou No Kizuna Portable) will also need lower delay/latency (ie. ~3ms seems to be good, 2ms or lower doesn't work well) so MatchingEvents can be processed faster, thus won't be piling up in the queue. // Using 3ms seems to fix Player list issue on StarWars The Force Unleashed. int delayus = 3000; auto params = matchingEvents.begin(); if (params != matchingEvents.end()) { u32_le args[6]; memcpy(args, params->data, sizeof(args)); auto context = findMatchingContext(args[0]); if (actionAfterMatchingMipsCall < 0) { actionAfterMatchingMipsCall = __KernelRegisterActionType(AfterMatchingMipsCall::Create); } DEBUG_LOG(Log::sceNet, "AdhocMatching - Remaining Events: %zu", matchingEvents.size()); auto peer = findPeer(context, (SceNetEtherAddr*)Memory::GetPointerOrException(args[2])); // Discard HELLO events from a peer in the middle of joining: on the PSP a client gets none from the host // it's asking to join (pspautotests net/adhoc/pair/matching3), and Super Pocket Tennis tries to join // again if it gets one. const bool abortedAck = args[1] == PSP_ADHOC_MATCHING_EVENT_DATA_ACK && peer != NULL && peer->ackAborted; if (abortedAck) { peer->ackAborted = 0; if (Memory::IsValidAddress(args[2])) { userMemory.Free(args[2]); } matchingEvents.pop_front(); } else if (peer == NULL || (args[1] != PSP_ADHOC_MATCHING_EVENT_HELLO || (peer->state != PSP_ADHOC_MATCHING_PEER_OUTGOING_REQUEST && peer->state != PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST && peer->state != PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS))) { DEBUG_LOG(Log::sceNet, "AdhocMatchingCallback: [ID=%i][EVENT=%i][%s]", args[0], args[1], mac2str((SceNetEtherAddr *)Memory::GetPointerOrException(args[2])).c_str()); if ((args[1] == PSP_ADHOC_MATCHING_EVENT_DATA_ACK || args[1] == PSP_ADHOC_MATCHING_EVENT_DATA_TIMEOUT) && peer != NULL) { peer->sending = 0; } if (args[1] == PSP_ADHOC_MATCHING_EVENT_ESTABLISHED && peer != NULL) { peer->accepting = 0; } AfterMatchingMipsCall* after = (AfterMatchingMipsCall*)__KernelCreateAction(actionAfterMatchingMipsCall); after->SetData(args[0], args[1], args[2]); hleEnqueueCall(args[5], 5, args, after); matchingEvents.pop_front(); } else { DEBUG_LOG(Log::sceNet, "AdhocMatching - Discarding Callback: [ID=%i][EVENT=%i][%s]", args[0], args[1], mac2str((SceNetEtherAddr*)Memory::GetPointerOrException(args[2])).c_str()); if (Memory::IsValidAddress(args[2])) { userMemory.Free(args[2]); } matchingEvents.pop_front(); } } // 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? hleCall(ThreadManForUser, int, sceKernelDelayThread, delayus); hleNoLogVoid(); } const HLEFunction sceNetAdhocMatching[] = { {0X2A2A1E07, &WrapI_U, "sceNetAdhocMatchingInit", 'i', "x" }, {0X7945ECDA, &WrapI_V, "sceNetAdhocMatchingTerm", 'i', "" }, {0XCA5EDA6F, &WrapI_IIIIIIIIU, "sceNetAdhocMatchingCreate", 'i', "iiiiiiiix"}, {0X93EF3843, &WrapI_IIIIIIU, "sceNetAdhocMatchingStart", 'i', "iiiiiix" }, {0xE8454C65, &WrapI_IIIIIIIIU, "sceNetAdhocMatchingStart2", 'i', "iiiiiiiix"}, {0X32B156B3, &WrapI_I, "sceNetAdhocMatchingStop", 'i', "i" }, {0XF16EAF4F, &WrapI_I, "sceNetAdhocMatchingDelete", 'i', "i" }, {0X5E3D4B79, &WrapI_ICIU, "sceNetAdhocMatchingSelectTarget", 'i', "isix" }, {0XEA3C6108, &WrapI_IC, "sceNetAdhocMatchingCancelTarget", 'i', "is" }, {0X8F58BEDF, &WrapI_ICIU, "sceNetAdhocMatchingCancelTargetWithOpt", 'i', "isix" }, {0XB5D96C2A, &WrapI_IUU, "sceNetAdhocMatchingGetHelloOpt", 'i', "ixx" }, {0XB58E61B7, &WrapI_IIU, "sceNetAdhocMatchingSetHelloOpt", 'i', "iix" }, {0XC58BCD9E, &WrapI_IUU, "sceNetAdhocMatchingGetMembers", 'i', "ixx" }, {0XF79472D7, &WrapI_ICIU, "sceNetAdhocMatchingSendData", 'i', "isix" }, {0XEC19337D, &WrapI_IC, "sceNetAdhocMatchingAbortSendData", 'i', "is" }, {0X40F8F435, &WrapI_V, "sceNetAdhocMatchingGetPoolMaxAlloc", 'i', "" }, {0X9C5CFB7D, &WrapI_U, "sceNetAdhocMatchingGetPoolStat", 'i', "x" }, // Fake function for PPSSPP's use. {0X756E6F00, &WrapV_V<__NetMatchingCallbacks>, "__NetMatchingCallbacks", 'v', "" }, }; void Register_sceNetAdhocMatching() { RegisterHLEModule("sceNetAdhocMatching", ARRAY_SIZE(sceNetAdhocMatching), sceNetAdhocMatching); } void __NetAdhocMatchingInit() { netAdhocMatchingInited = false; matchingPollEvent = CoreTiming::RegisterEvent("__NetMatchingPoll", __NetMatchingPoll); } void __NetAdhocMatchingShutdown() { NetAdhocMatching_Term(); }