diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index cc93ce34c..ee381df8b 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -1003,10 +1003,16 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves LARGE_INTEGER NewTime; + PLARGE_INTEGER Timeout; if (dwMilliseconds == INFINITE) { - NewTime.QuadPart = ~0ull; + Timeout = nullptr; + } + else if (dwMilliseconds == 0) { + Timeout = &NewTime; + NewTime.QuadPart = 0; } else { + Timeout = &NewTime; NewTime.QuadPart = xbox::KeQueryInterruptTime(); NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } @@ -1017,7 +1023,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) return std::nullopt; } return std::make_optional(dwRet); - }, &NewTime, bAlertable, UserMode); + }, Timeout, bAlertable, UserMode); RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret); } diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index c998dd505..0b8a691d3 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -112,43 +112,75 @@ void RestoreInterruptMode(bool value); void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql); template -xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER AbsoluteExpireTime, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +std::optional SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +{ + if (const auto ret = Lambda()) { + return ret; + } + + xbox::KiApcListMtx.lock(); + bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]); + bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]); + xbox::KiApcListMtx.unlock(); + + if (EmptyKernel == false) { + xbox::KiExecuteKernelApc(); + } + + if ((EmptyUser == false) && + (Alertable == TRUE) && + (WaitMode == xbox::UserMode)) { + xbox::KiExecuteUserApc(); + return X_STATUS_USER_APC; + } + + return std::nullopt; +} + +template +xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { // NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should // also interrupt the wait - xbox::ulonglong_xt now = xbox::KeQueryInterruptTime(); xbox::PETHREAD eThread = reinterpret_cast(EmuKeGetPcr()->Prcb->CurrentThread); - if (AbsoluteExpireTime->QuadPart == 0) { - // This will only happen when the title specifies a zero timeout - AbsoluteExpireTime->QuadPart = now; - } + if (Timeout == nullptr) { + // No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled + while (true) { + if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { + return *ret; + } - while (now <= static_cast(AbsoluteExpireTime->QuadPart)) { - if (const auto ret = Lambda()) { + std::this_thread::yield(); + } + } + else if (Timeout->QuadPart == 0) { + // A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied + if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { return *ret; } - - xbox::KiApcListMtx.lock(); - bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]); - bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]); - xbox::KiApcListMtx.unlock(); - if (EmptyKernel == false) { - xbox::KiExecuteKernelApc(); + else { + return X_STATUS_TIMEOUT; } - if ((EmptyUser == false) && - (Alertable == TRUE) && - (WaitMode == xbox::UserMode)) { - xbox::KiExecuteUserApc(); - return X_STATUS_USER_APC; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - now = xbox::KeQueryInterruptTime(); } + else { + // A non-zero timeout means we have to check the conditions until we reach the requested time + xbox::LARGE_INTEGER ExpireTime, DueTime, NewTime; + xbox::ulonglong_xt Now; + ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; // either positive, negative, but not NULL + xbox::PLARGE_INTEGER AbsoluteExpireTime = xbox::KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime, &Now); + while (Now <= static_cast(AbsoluteExpireTime->QuadPart)) { + if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { + return *ret; + } - return X_STATUS_TIMEOUT; + std::this_thread::yield(); + Now = xbox::KeQueryInterruptTime(); + } + + return X_STATUS_TIMEOUT; + } } #endif diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index e5bc338ac..3bfd5ebc9 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -714,17 +714,6 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well // Test case: Metal Slug 3 - LARGE_INTEGER NewTime; - PLARGE_INTEGER pNewTime; - if (Interval) { - LARGE_INTEGER ExpireTime, DueTime; - ExpireTime.QuadPart = DueTime.QuadPart = Interval->QuadPart; - pNewTime = KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime); - } - else { - NewTime.QuadPart = ~0ull; - pNewTime = &NewTime; - } xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; @@ -735,7 +724,12 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread return std::nullopt; } return std::make_optional(Status); - }, pNewTime, Alertable, WaitMode); + }, Interval, Alertable, WaitMode); + + if (ret == X_STATUS_TIMEOUT) { + // NOTE: this function considers a timeout a success + ret = X_STATUS_SUCCESS; + } RETURN(ret); } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 6641294b8..a8dabb599 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -929,6 +929,25 @@ xbox::void_xt xbox::KiExecuteUserApc() KiExecuteApc(); } +xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval +( + IN xbox::PLARGE_INTEGER OriginalTime, + IN xbox::PLARGE_INTEGER DueTime, + IN OUT xbox::PLARGE_INTEGER NewTime, + OUT ulonglong_xt *Now +) +{ + *Now = xbox::KeQueryInterruptTime(); + if (OriginalTime->QuadPart >= 0) { + return OriginalTime; + } + else { + NewTime->QuadPart = *Now; + NewTime->QuadPart -= DueTime->QuadPart; + return NewTime; + } +} + xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval ( IN xbox::PLARGE_INTEGER OriginalTime, diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index b6737406b..e23165d7e 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -140,6 +140,14 @@ namespace xbox void_xt KiExecuteKernelApc(); void_xt KiExecuteUserApc(); + PLARGE_INTEGER FASTCALL KiComputeWaitInterval + ( + IN PLARGE_INTEGER OriginalTime, + IN PLARGE_INTEGER DueTime, + IN OUT PLARGE_INTEGER NewTime, + OUT ulonglong_xt *Now + ); + PLARGE_INTEGER FASTCALL KiComputeWaitInterval ( IN PLARGE_INTEGER OriginalTime, diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index bba59e853..1e7d33fc8 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2208,17 +2208,6 @@ 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 - LARGE_INTEGER NewTime; - PLARGE_INTEGER pNewTime; - if (Timeout) { - LARGE_INTEGER ExpireTime, DueTime; - ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; - pNewTime = KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime); - } - else { - NewTime.QuadPart = ~0ull; - pNewTime = &NewTime; - } xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; @@ -2233,7 +2222,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx return std::nullopt; } return std::make_optional(Status); - }, pNewTime, Alertable, WaitMode); + }, Timeout, Alertable, WaitMode); RETURN(ret); }