From 5cc355466703295c95f0c6ac758e1faabe2cdc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Oct 2026 17:47:03 +0100 Subject: [PATCH] sceNetAdhocMatching: Pass the pair matching tests The matching, matching2 and matching3 pair tests (recorded on two PSPs) now pass on both sides: - Event and input messages are queued in order. They were stacks, so a burst came out reversed (PONG and PANG crossed), and two places spawned their messages backwards to compensate. - A peer stays busy until its DATA_ACK is delivered (DATA_BUSY), and AbortSendData discards that DATA_ACK. - A host's SelectTarget while its accept is in progress gives REQUEST_IN_PROGRESS. - A child that leaves, a cancelled request and a denied request (client and P2P) go back to a known non-member peer: no TIMEOUT, hellos keep coming, and it can ask again; a cancelled request gives TARGET_NOT_READY. A client says BYE to a host it has left. - After SetHelloOpt the next join from an unknown peer reaches the host without a REQUEST. - GetMembers lists the console itself first, also on a client. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/proAdhoc.cpp | 31 ++++---- Core/HLE/proAdhoc.h | 15 ++-- Core/HLE/sceNetAdhocMatching.cpp | 127 ++++++++++++++++++------------- 3 files changed, 97 insertions(+), 76 deletions(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 90c129b7fc..56371b5264 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -834,9 +834,13 @@ void linkEVMessage(SceNetAdhocMatchingContext * context, ThreadMessage * message // Lock Access context->eventlock->lock(); - // Link Message - message->next = context->event_stack; - context->event_stack = message; + // Append, so events are delivered in the order they happened + message->next = NULL; + ThreadMessage **tail = &context->event_stack; + while (*tail != NULL) { + tail = &(*tail)->next; + } + *tail = message; // Unlock Access context->eventlock->unlock(); @@ -852,9 +856,13 @@ void linkIOMessage(SceNetAdhocMatchingContext * context, ThreadMessage * message // Lock Access context->inputlock->lock(); - // Link Message - message->next = context->input_stack; - context->input_stack = message; + // Append, so packets go out in the order they were queued (pspautotests net/adhoc/pair/matching2) + message->next = NULL; + ThreadMessage **tail = &context->input_stack; + while (*tail != NULL) { + tail = &(*tail)->next; + } + *tail = message; // Unlock Access context->inputlock->unlock(); @@ -967,17 +975,6 @@ void sendBulkData(SceNetAdhocMatchingContext * context, SceNetAdhocMatchingMembe sendGenericMessage(context, PSP_ADHOC_MATCHING_INPUT_STACK, &peer->mac, PSP_ADHOC_MATCHING_PACKET_BULK, datalen, data); } -/** -* Abort Bulk Data Transfer (if in progress) -* @param context Matching Context Pointer -* @param peer Target Peer -*/ -void abortBulkTransfer(SceNetAdhocMatchingContext * context, SceNetAdhocMatchingMemberInternal * peer) -{ - // Send Bulk Data Abort Message - sendGenericMessage(context, PSP_ADHOC_MATCHING_INPUT_STACK, &peer->mac, PSP_ADHOC_MATCHING_PACKET_BULK_ABORT, 0, NULL); -} - /** * Notify all established Peers about new Kid in the Neighborhood * @param context Matching Context Pointer diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 0884c4a596..4726784599 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -381,6 +381,12 @@ typedef struct SceNetAdhocMatchingMemberInternal { // Last Heartbeat u64_le lastping; // May need to use the same method with sceKernelGetSystemTimeWide (ie. CoreTiming::GetGlobalTimeUsScaled) to prevent timing issue (ie. in game timeout) + + // A host's accept stays in progress until its ESTABLISHED event has been delivered. + s32_le accepting; + + // The send was aborted with AbortSendData, so its DATA_ACK is discarded. + s32_le ackAborted; } SceNetAdhocMatchingMemberInternal; @@ -521,6 +527,9 @@ typedef struct SceNetAdhocMatchingContext { //bool InConnection = false; //u32_le handlerid = -1; //int eventMatchingHandlerUpdate = -1; + + // After the hello data changes, a PSP host's game isn't told about the next join request from a new peer. + bool dropNextJoin = false; } SceNetAdhocMatchingContext; // End of psp definitions @@ -1053,12 +1062,6 @@ void sendCancelMessage(SceNetAdhocMatchingContext * context, SceNetAdhocMatching */ void sendBulkData(SceNetAdhocMatchingContext * context, SceNetAdhocMatchingMemberInternal * peer, int datalen, const void * data); -/** -* Abort Bulk Data Transfer (if in progress) -* @param context Matching Context Pointer -* @param peer Target Peer -*/ -void abortBulkTransfer(SceNetAdhocMatchingContext * context, SceNetAdhocMatchingMemberInternal * peer); /** * Notify all established Peers about new Kid in the Neighborhood diff --git a/Core/HLE/sceNetAdhocMatching.cpp b/Core/HLE/sceNetAdhocMatching.cpp index bb5fc2b3fe..5bbe8f6ef5 100644 --- a/Core/HLE/sceNetAdhocMatching.cpp +++ b/Core/HLE/sceNetAdhocMatching.cpp @@ -436,10 +436,9 @@ void sendBulkDataPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * // Free Memory free(send); - // Remove Busy Bit from Peer - peer->sending = 0; - - // Spawn Data Event + // 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). spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DATA_ACK, mac, 0, NULL); } @@ -558,8 +557,10 @@ void sendByePacket(SceNetAdhocMatchingContext * context) { // Iterate Peers SceNetAdhocMatchingMemberInternal * peer = context->peerlist; for (; peer != NULL; peer = peer->next) { - // Peer of Interest - 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) { + // 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; @@ -671,6 +672,12 @@ void actOnJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sen return; } + // The host takes the request, but the game isn't told (see dropNextJoin). That's only seen for a + // peer the host didn't know yet: one it had already denied gets its REQUEST (pspautotests + // net/adhoc/pair/matching2 and matching3). + const bool silent = context->dropNextJoin && findPeer(context, sendermac) == NULL; + context->dropNextJoin = false; + // 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 @@ -692,7 +699,9 @@ void actOnJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sen // 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. - if (peer != NULL && peer->lastping != 0 && context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { + // 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; } @@ -723,7 +732,7 @@ void actOnJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sen peerlock.unlock(); // Spawn Request Event - spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); + if (!silent) spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); // Return Success return; @@ -739,7 +748,7 @@ void actOnJoinPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * sen peer->lastping = CoreTiming::GetGlobalTimeUsScaled(); // Spawn Request Event - spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); + if (!silent) spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_REQUEST, sendermac, optlen, opt); // Return Success return; @@ -828,14 +837,11 @@ void actOnAcceptPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * s addMember(context, &context->mac); } - // IMPORTANT! The Event Order here is ok! - // Internally the Event Stack appends to the front, so the order will be switched around. + // 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); - - // Spawn Accept Event - spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_ACCEPT, sendermac, optlen, opt); } } @@ -886,9 +892,9 @@ void actOnCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * s // Spawn Deny Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DENY, sendermac, optlen, opt); - // Delete Peer from List - //deletePeer(context, peer); - peer->lastping = 0; + // 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 @@ -915,9 +921,8 @@ void actOnCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * s // Spawn Request Cancel Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_CANCEL, sendermac, optlen, opt); - // Delete Peer from List - //deletePeer(context, peer); - peer->lastping = 0; + // No longer a request: accepting it gives TARGET_NOT_READY (pspautotests net/adhoc/pair/matching2). + peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; } // Leave Room @@ -925,9 +930,9 @@ void actOnCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * s // Spawn Leave / Kick Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_LEAVE, sendermac, optlen, opt); - // Delete Peer from List - //deletePeer(context, peer); - peer->lastping = 0; + // 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; } } @@ -941,9 +946,9 @@ void actOnCancelPacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * s // Spawn Deny Event spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_DENY, sendermac, optlen, opt); - // FIXME: Delete Peer from List? - // Instead of removing the peer immediately, we should let it timedout, otherwise inviter in Crazy Taxi will wait forever without getting timedout, since handleTimeout need the peer data to exist. - peer->lastping = 0; + // 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 @@ -1115,7 +1120,7 @@ void actOnByePacket(SceNetAdhocMatchingContext * context, SceNetEtherAddr * send } // 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) || + 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))) @@ -1206,7 +1211,7 @@ int matchingEventThread(int matchingId) { // Lock again //context->eventlock->lock(); - // Pop event stack from front (this should be queue instead of stack?) + // Pop the event queue from the front context->event_stack = msg->next; free(msg); msg = NULL; @@ -1386,7 +1391,7 @@ int matchingInputThread(int matchingId) { // TODO: The MatchingInput thread is u //context->inputlock->lock(); // Lock again - // Pop input stack from front (this should be queue instead of stack?) + // Pop the input queue from the front context->input_stack = msg->next; free(msg); msg = context->input_stack; @@ -1522,7 +1527,7 @@ int matchingInputThread(int matchingId) { // TODO: The MatchingInput thread is u // 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?) + // Pop the input queue from the front context->input_stack = msg->next; free(msg); msg = context->input_stack; @@ -2036,7 +2041,8 @@ static int sceNetAdhocMatchingSelectTarget(MIPSState *mips, int matchingId, cons if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointerWriteUnchecked(optDataPtr); // Host Mode if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) { - // Already Connected + // 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 @@ -2046,8 +2052,7 @@ static int sceNetAdhocMatchingSelectTarget(MIPSState *mips, int matchingId, cons if (peer->state == PSP_ADHOC_MATCHING_PEER_INCOMING_REQUEST) { // Accept Peer in Group peer->state = PSP_ADHOC_MATCHING_PEER_CHILD; - - // Sending order may need to be reversed since Stack appends to the front, so the order will be switched around. + peer->accepting = 1; // Tell Children about new Sibling sendBirthMessage(context, peer); @@ -2167,18 +2172,21 @@ int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, (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))) { - // Notify other Children of Death - if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && peer->state == PSP_ADHOC_MATCHING_PEER_CHILD && countConnectedPeers(context) > 1) { - // Send Death Message - sendDeathMessage(context, peer); - } + const bool notifyDeath = context->mode == PSP_ADHOC_MATCHING_MODE_PARENT && peer->state == PSP_ADHOC_MATCHING_PEER_CHILD && countConnectedPeers(context) > 1; // Mark Peer as Canceled peer->state = PSP_ADHOC_MATCHING_PEER_CANCEL_IN_PROGRESS; - // Send Cancel Event to Peer + // 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); @@ -2317,6 +2325,10 @@ int sceNetAdhocMatchingSetHelloOpt(int matchingId, int optLenAddr, u32 optDataAd //context->hello = NULL; } + // The first join request after this, from a client the host doesn't know yet, never reaches the game + // on a PSP, even if the client has seen the new hello (pspautotests net/adhoc/pair/matching2). + context->dropNextJoin = true; + // Return Success return hleLogDebug(Log::sceNet, 0); } @@ -2390,8 +2402,8 @@ static int sceNetAdhocMatchingGetMembers(int matchingId, u32 sizeAddr, u32 buf) int filledpeers = 0; if (requestedpeers > 0) { - // Add Self-Peer first, unless if there is existing Parent/P2P peer - if (peercount == 1 || context->mode != PSP_ADHOC_MATCHING_MODE_CHILD) { + // The console itself comes first, also for a client (pspautotests net/adhoc/pair/matching) + { // Add Local MAC buf2[filledpeers++].mac_addr = context->mac; @@ -2481,13 +2493,6 @@ static int sceNetAdhocMatchingGetMembers(int matchingId, u32 sizeAddr, u32 buf) DEBUG_LOG(Log::sceNet, "MemberSibling [%s]", mac2str(&peer->mac).c_str()); } - // Self Peer - else if (peer->state == 0) { - // Add Local MAC - buf2[filledpeers++].mac_addr = peer->mac; - - DEBUG_LOG(Log::sceNet, "MemberSelf [%s]", mac2str(&peer->mac).c_str()); - } } } @@ -2621,12 +2626,12 @@ int sceNetAdhocMatchingAbortSendData(int matchingId, const char *mac) { } // 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) { - // Set Peer as Bulk Idle peer->sending = 0; - - // Stop Bulk Data Sending (if in progress) - abortBulkTransfer(context, peer); + peer->ackAborted = 1; } // Return Success @@ -2692,9 +2697,22 @@ void __NetMatchingCallbacks() { //(int matchingId) // 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. - 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))) { + 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 && 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); @@ -2702,6 +2720,9 @@ void __NetMatchingCallbacks() { //(int matchingId) } 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(); } }