From fa51700d583f01f1495d1aa176609940b1b593ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 28 Oct 2026 17:02:00 +0100 Subject: [PATCH] sceNetAdhocctl: Fail entering game mode the way a PSP does On a PSP (pspautotests net/adhoc/gamemode): - a JoinEnterGameMode whose host never shows up gets ERROR with TIMEOUT when its timeout runs out, and is out of the group afterwards. The joiner's GAMEMODE, queued as soon as the server answered and held until the host appeared, came after 5 s anyway; - a CreateEnterGameMode whose first member isn't this console returns 0 and reports INVALID_ADDR; - in a normal group, CreateEnterGameMode reports ALREADY_CONNECTED and stays in the group, and ExitGameMode returns INVALID_IBSS; - ExitGameMode outside game mode returns 0 with no event. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/proAdhoc.cpp | 5 +++++ Core/HLE/sceNetAdhoc.cpp | 39 +++++++++++++++++++++++++++++++++++++++ Core/HLE/sceNetAdhoc.h | 4 ++++ 3 files changed, 48 insertions(+) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 30e6f79187..543e03a220 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1641,6 +1641,11 @@ void friendFinderPoll() { // Calculate EnterGameMode Timeout to prevent waiting forever for disconnected players if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered && static_cast(now - adhocctlStartTime) > netAdhocEnterGameModeTimeout) { netAdhocGameModeEntered = false; + // A joiner's GAMEMODE is queued as soon as the server answers, and held until the host shows up. + // It must not come after all (pspautotests net/adhoc/gamemode). + __DropAdhocctlEvents(ADHOCCTL_EVENT_GAME); + // Back to DISCONNECTED with no event but the error, out of the server's group too. + NetAdhocctl_GameModeTimedOut(); // adhocctl's own TIMEOUT (pspautotests net/adhoc/gamemode). notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOCCTL_ERROR_TIMEOUT); } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 542d5dfb05..2950809e11 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -2120,6 +2120,25 @@ void __UpdateAdhocctlHandlers(u32 flag, u32 error) { adhocctlEvents.push_back({ flag, error }); } +void NetAdhocctl_GameModeTimedOut() { + if (adhocctlInServerGroup) { + const std::lock_guard adhocctl_guard(adhocctlMutex); + if (adhocctlHandle != NULL) { + adhocctl_disconnect(adhocctlHandle); + } + adhocctlInServerGroup = false; + } + adhocctlCurrentMode = ADHOCCTL_MODE_NONE; + isAdhocctlBusy = false; +} + +void __DropAdhocctlEvents(u32 flag) { + std::lock_guard adhocGuard(adhocEvtMtx); + adhocctlEvents.erase(std::remove_if(adhocctlEvents.begin(), adhocctlEvents.end(), [flag](const std::pair &e) { + return e.first == flag; + }), adhocctlEvents.end()); +} + void __AdhocNotifInit() { adhocctlNotifyEvent = CoreTiming::RegisterEvent("__AdhocctlNotify", __AdhocctlNotify); adhocSocketNotifyEvent = CoreTiming::RegisterEvent("__AdhocSocketNotify", __AdhocSocketNotify); @@ -4231,6 +4250,21 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n if (game_type < ADHOCCTL_GAMETYPE_1A || game_type > ADHOCCTL_GAMETYPE_2A || num_members < 2 || num_members > 16 || (game_type == ADHOCCTL_GAMETYPE_1A && num_members > 4)) return SCE_NET_ADHOCCTL_ERROR_INVALID_ARG; + // In a normal group, it reports ALREADY_CONNECTED through the handlers and stays in it + // (pspautotests net/adhoc/gamemode). + if (adhocctlState == ADHOCCTL_STATE_CONNECTED) { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOCCTL_ERROR_ALREADY_CONNECTED); + return 0; + } + + // The first member has to be this console: if it isn't, the call succeeds and the handlers get + // ERROR with INVALID_ADDR (pspautotests net/adhoc/gamemode). + SceNetEtherAddr firstMember = *PSPPointer::Create(membersAddr); + if (!isLocalMAC(&firstMember)) { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, SCE_NET_ADHOC_ERROR_INVALID_ADDR); + return 0; + } + deleteAllGMB(); gameModePeerPorts.clear(); @@ -6441,6 +6475,11 @@ const HLEFunction sceNetAdhoc[] = { }; int NetAdhocctl_ExitGameMode() { + // Outside game mode, also after entering it timed out, it returns 0 with no event (pspautotests + // net/adhoc/gamemode). + if (adhocctlCurrentMode != ADHOCCTL_MODE_GAMEMODE) + return adhocctlState == ADHOCCTL_STATE_CONNECTED ? SCE_NET_ADHOCCTL_ERROR_INVALID_IBSS : 0; + if (gameModeSocket > 0) { NetAdhocPdp_Delete(gameModeSocket, 0); gameModeSocket = (int)INVALID_SOCKET; diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index 721442383f..09c97c7630 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -142,6 +142,10 @@ void __NetAdhocDoState(PointerWrap &p); // TODO: expose it via "sceNetAdhocctl.h" void __UpdateAdhocctlHandlers(u32 flags, u32 error); +// Drops queued events of one kind that haven't been delivered yet. +void __DropAdhocctlEvents(u32 flags); +// Leaves the server's group without an event, after entering game mode timed out. +void NetAdhocctl_GameModeTimedOut(); bool __NetAdhocConnected();