From ac31523b097776ac372d767522f001e77a2dba5d Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 22 Mar 2023 21:42:34 +0100 Subject: [PATCH] Make sure to reset WaitStatus when a new wait starts This fixes an issue in Panzer Dragoon Orta, where KeDelayExecutionThread would return X_STATUS_TIMEOUT | X_STATUS_USER_APC --- src/core/hle/XAPI/Xapi.cpp | 7 ++++--- src/core/kernel/exports/EmuKrnlKe.cpp | 8 +++++--- src/core/kernel/exports/EmuKrnlNt.cpp | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index 42af70701..1be38ec11 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -960,9 +960,10 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Timeout || (Timeout->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -971,7 +972,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable, kThread]() -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; @@ -986,7 +987,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) case WAIT_OBJECT_0: Status = X_STATUS_SUCCESS; break; default: Status = X_STATUS_INVALID_HANDLE; } - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Timeout, bAlertable, UserMode); diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 7d8c52c7d..8f2bd8da1 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -733,9 +733,10 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well // Test case: Metal Slug 3 + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Interval || (Interval->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -744,7 +745,7 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Alertable, kThread]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); @@ -752,9 +753,10 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) { return std::nullopt; } + EmuLog(LOG_LEVEL::DEBUG, "KeDelayExecutionThread -> Staus: %X", Status); // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Interval, Alertable, WaitMode); diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index e01484dad..8b9249a06 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2231,9 +2231,10 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Timeout || (Timeout->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -2242,7 +2243,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable, kThread]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtWaitForMultipleObjects( @@ -2256,7 +2257,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx } // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Timeout, Alertable, WaitMode);