diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 7078c3f3cf..542d5dfb05 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -96,6 +96,40 @@ std::map adhocctlHandlers; int adhocctlNotifyEvent = -1; int adhocctlStateEvent = -1; int adhocctlPollEvent = -1; +// When the game made its last request to the Adhoc Server, in emulated time. Its event isn't delivered +// earlier than on a PSP (see PspEventDelayUs). +static u64 adhocctlRequestTimeUs = 0; +// Whether the server has us in a group. The state the game sees only changes when the event is +// delivered, so a Disconnect can come while a Create or Connect is still on its way. +static bool adhocctlInServerGroup = false; + +// How long after the request a PSP delivers an event (pspautotests net/adhoc/timing and +// net/adhoc/pair/timing). The state changes along with it. Over the internet the server's answer can +// come later, and then the event waits for it. +static int PspEventDelayUs(u32 flags) { + switch (flags) { + case ADHOCCTL_EVENT_CONNECT: + switch (adhocConnectionType) { + case ADHOC_CREATE: return 1110000; + case ADHOC_JOIN: return 530000; + // Connect looks for the group first: about 3.5 s to join one, 4 s to create it. + default: return getActivePeerCount() > 0 ? 3500000 : 4000000; + } + case ADHOCCTL_EVENT_SCAN: return 1015000; + case ADHOCCTL_EVENT_DISCONNECT: return 105000; + default: return 0; + } +} + +// A PSP returns from Create, Connect, Join and Scan within about 30 µs (pspautotests net/adhoc/timing). +// The request goes out to the server then, and the call returns once it's sent. +static const int ADHOCCTL_REQUEST_US = 30; + +static int RemainingEventDelayUs(u32 flags) { + s64 left = (s64)(adhocctlRequestTimeUs + PspEventDelayUs(flags)) - (s64)CoreTiming::GetGlobalTimeUs(); + return left > 0 ? (int)left : 0; +} + int adhocSocketNotifyEvent = -1; std::map adhocctlRequests; std::map adhocSocketRequests; @@ -571,6 +605,9 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) { switch(req.opcode) { case OPCODE_CONNECT:{ call_status = adhocctl_connect(adhocctlHandle, (char *)req.group.data); + if (call_status == ADHOCCTL_CALL_SUCCESS) { + adhocctlInServerGroup = true; + } break; } case OPCODE_SCAN:{ @@ -682,6 +719,7 @@ int WaitBlockingAdhocctlSocket(AdhocctlRequest request, int usec, const char* re u64 param = ((u64)__KernelGetCurThread()) << 32 | uid; adhocctlStartTime = (u64)(time_now_d() * 1000000.0); + adhocctlRequestTimeUs = CoreTiming::GetGlobalTimeUs(); adhocctlRequests[uid] = request; CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param); __KernelWaitCurThread(WAITTYPE_NET, uid, request.opcode, 0, false, reason); @@ -1910,6 +1948,7 @@ static void StartFriendFinder() { static void StopFriendFinder() { friendFinderRunning = false; friendFinderStop(); + adhocctlInServerGroup = false; int32_t peercount = 0; freeFriendsRecursive(friends, &peercount); @@ -1920,7 +1959,7 @@ static void StopFriendFinder() { } void __NetAdhocDoState(PointerWrap &p) { - auto s = p.Section("sceNetAdhoc", 1, 11); + auto s = p.Section("sceNetAdhoc", 1, 12); if (!s) return; @@ -2019,8 +2058,15 @@ void __NetAdhocDoState(PointerWrap &p) { adhocctlPollEvent = -1; } CoreTiming::RestoreRegisterEvent(adhocctlPollEvent, "__AdhocctlPoll", __AdhocctlPoll); + if (s >= 12) { + Do(p, adhocctlRequestTimeUs); + } else { + adhocctlRequestTimeUs = 0; + } if (p.mode == p.MODE_READ) { + // The login after a load starts outside any group. + adhocctlInServerGroup = false; // Discard leftover events { std::lock_guard adhocGuard(adhocEvtMtx); @@ -3596,7 +3642,7 @@ int sceNetAdhocctlScan(MIPSState *mips) { if (friendFinderRunning) { AdhocctlRequest req = { OPCODE_SCAN, {0} }; - return hleLogDebugOrError(Log::sceNet, WaitBlockingAdhocctlSocket(req, us, "adhocctl scan")); + return hleLogDebugOrError(Log::sceNet, WaitBlockingAdhocctlSocket(req, ADHOCCTL_REQUEST_US, "adhocctl scan")); } else { adhocctlState = ADHOCCTL_STATE_DISCONNECTED; @@ -3762,15 +3808,15 @@ u32 NetAdhocctl_Disconnect() { // Library initialized if (netAdhocctlInited) { int iResult, error; - // We might need to have at least 16ms (1 frame?) delay before the game calls the next Adhocctl syscall for Tekken 6 not to stuck when exiting Lobby - hleEatMicro(16667); - if (isAdhocctlBusy && CoreTiming::IsScheduled(adhocctlNotifyEvent)) { return SCE_NET_ADHOCCTL_ERROR_BUSY; } + // Returns at once on a PSP, and DISCONNECT follows after about 105 ms (pspautotests net/adhoc/timing). + adhocctlRequestTimeUs = CoreTiming::GetGlobalTimeUs(); // Connected State (Adhoc Mode). Attempting to leave a group while not in a group will be kicked out by Adhoc Server (ie. some games tries to disconnect more than once within a short time) - if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { + if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED || adhocctlInServerGroup) { + adhocctlInServerGroup = false; isAdhocctlBusy = true; // Clear Network Name @@ -3865,7 +3911,7 @@ static u32 sceNetAdhocctlDelHandler(u32 handlerID) { int NetAdhocctl_Term() { if (netAdhocctlInited) { - if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { + if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED || adhocctlInServerGroup) { // Note: This might block current thread if the first attempt to send OPCODE_DISCONNECT to AdhocServer failed with EAGAIN error if (netAdhocGameModeEntered) NetAdhocctl_ExitGameMode(); @@ -3876,6 +3922,7 @@ int NetAdhocctl_Term() { // Stop the adhocctl client friendFinderRunning = false; friendFinderStop(); + adhocctlInServerGroup = false; // TODO: May need to block current thread to make sure all Adhocctl callbacks have been fully executed before terminating Adhoc PSPThread (ie. threadAdhocID). @@ -4071,10 +4118,9 @@ int NetAdhocctl_Create(const char *groupName) { //setConnectionStatus(1); // Wait for Status to be connected to prevent Ford Street Racing from Failed to create game session - int us = adhocDefaultDelay; if (friendFinderRunning) { AdhocctlRequest req = { OPCODE_CONNECT, parameter.group_name }; - return WaitBlockingAdhocctlSocket(req, us, "adhocctl connect"); + return WaitBlockingAdhocctlSocket(req, ADHOCCTL_REQUEST_US, "adhocctl connect"); } //Faking success, to prevent Full Auto 2 from freezing while Initializing Network else { @@ -4091,7 +4137,7 @@ int NetAdhocctl_Create(const char *groupName) { } } - hleEatMicro(us); + hleEatMicro(adhocDefaultDelay); // Return Success // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlCreate switching to a different thread for at least 100ms after returning success and before executing the next line. return 0; @@ -6304,18 +6350,15 @@ void __NetTriggerCallbacks() switch (flags) { case ADHOCCTL_EVENT_CONNECT: newState = ADHOCCTL_STATE_CONNECTED; - if (adhocConnectionType == ADHOC_CREATE) - delayus = adhocEventDelay; // May affects Dissidia 012 and GTA VCS - else if (adhocConnectionType == ADHOC_CONNECT) - delayus = adhocEventDelay / 2; + delayus = RemainingEventDelayUs(flags); break; - case ADHOCCTL_EVENT_SCAN: // notified only when scan completed? + case ADHOCCTL_EVENT_SCAN: newState = ADHOCCTL_STATE_DISCONNECTED; - //delayus = adhocEventDelay / 2; + delayus = RemainingEventDelayUs(flags); break; case ADHOCCTL_EVENT_DISCONNECT: newState = ADHOCCTL_STATE_DISCONNECTED; - delayus = adhocDefaultDelay; // Tekken 5 expects AdhocctlDisconnect to be done within ~17ms (a frame?) + delayus = RemainingEventDelayUs(flags); break; case ADHOCCTL_EVENT_GAME: { diff --git a/Tools/adhoc_multi.py b/Tools/adhoc_multi.py index 331ef37129..9644757ffc 100644 --- a/Tools/adhoc_multi.py +++ b/Tools/adhoc_multi.py @@ -61,17 +61,21 @@ AdhocServerRelayMode = 1 def run_test(headless, test, extra, relay_ini): roles = roles_of(test) memstick = os.path.join(os.path.dirname(headless), "memstick") + # A test that hasn't been recorded on PSPs yet has no expected files; then just show what each instance prints. + compare = all(os.path.exists(os.path.join(TESTS_DIR, "%s_%s.expected" % (test, role))) for role in roles) procs = [] for i, role in enumerate(roles): - cmd = [headless, "--root", os.path.join(ROOT, "pspautotests") + "/", "--compare", "--timeout-wall=%d" % TIMEOUT, + cmd = [headless, "--root", os.path.join(ROOT, "pspautotests") + "/", "--timeout-wall=%d" % TIMEOUT, "--graphics=software", "--memstick=" + memstick, "--realtime", "--instance=%d" % (i + 1)] + if compare: + cmd.append("--compare") if relay_ini: cmd.append("--appendconfig=" + relay_ini) if i == 0: cmd.append("--adhoc-server") cmd += extra cmd.append(os.path.join(TESTS_DIR, "%s_%s.prx" % (test, role))) - procs.append((role, subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))) + procs.append((role, subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT if compare else subprocess.DEVNULL))) if i == 0: # Let the host's built-in server come up before the clients log in to it. time.sleep(1.0) @@ -79,6 +83,11 @@ def run_test(headless, test, extra, relay_ini): passed = True for role, proc in procs: out, _ = proc.communicate(timeout=TIMEOUT + 30) + if not compare: + print("OUTPUT %s_%s (no expected file):" % (test, role)) + sys.stdout.write(out.decode("utf-8", "replace")) + passed = False + continue ok = proc.returncode == 0 passed = passed and ok print("%s %s_%s" % ("PASS" if ok else "FAIL", test, role))