diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index 7b309168e..4d51e8ba7 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -38,6 +38,7 @@ #include "Logging.h" #include "core\kernel\support\Emu.h" #include "core\kernel\exports\EmuKrnl.h" // For DefaultLaunchDataPage +#include "core\kernel\exports\EmuKrnlKi.h" #include "core\kernel\support\EmuFile.h" #include "core\kernel\support\NativeHandle.h" #include "EmuShared.h" @@ -997,16 +998,19 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) LOG_FUNC_ARG(bAlertable) LOG_FUNC_END; - // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves - bool Exit = false; - auto async_bool = WaitApc(bAlertable, UserMode, &Exit); + LARGE_INTEGER ExpireTime, DueTime, NewTime; + ExpireTime.QuadPart = DueTime.QuadPart = (dwMilliseconds == INFINITE) ? ~0ull : -dwMilliseconds; + KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime); - dword_xt dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, dwMilliseconds, bAlertable); + xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); + if (dwRet == WAIT_TIMEOUT) { + return std::nullopt; + } + return std::make_optional(dwRet); + }, &NewTime, bAlertable, UserMode); - Exit = true; - bool result = async_bool.get(); - - RETURN(result ? X_STATUS_USER_APC : dwRet); + RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret); } // ****************************************************************** diff --git a/src/core/kernel/common/types.h b/src/core/kernel/common/types.h index d55b8f838..ddbaaa3b1 100644 --- a/src/core/kernel/common/types.h +++ b/src/core/kernel/common/types.h @@ -105,6 +105,7 @@ typedef void* LPSECURITY_ATTRIBUTES; #define X_STATUS_NOT_COMMITTED 0xC000002DL #define X_STATUS_UNRECOGNIZED_VOLUME 0xC000014FL #define X_STATUS_OBJECT_PATH_NOT_FOUND 0xC000003AL +#define X_STATUS_TIMEOUT 0x00000102L // ****************************************************************** // * Registry value types diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index bf8fce718..361d06f7f 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -210,50 +210,6 @@ const DWORD IrqlMasks[] = { 0x00000000, // IRQL 31 (HIGH_LEVEL) }; -// This helper function is used to signal WinApi waiting functions that the wait has been satisfied by an xbox user APC -static void WINAPI EndWait(ULONG_PTR Parameter) -{ - // Do nothing -} - -std::future WaitApc(xbox::boolean_xt Alertable, xbox::char_xt WaitMode, bool *Exit) -{ - // NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should - // also interrupt the wait - xbox::PKPCR Kpcr = EmuKeGetPcr(); - - // This new thread must execute APCs in the context of the calling thread - return std::async(std::launch::async, [Kpcr, Alertable, WaitMode, Exit]() { - EmuKeSetPcr(Kpcr); - xbox::PETHREAD eThread = reinterpret_cast(Kpcr->Prcb->CurrentThread); - - while (true) { - 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(); - // Queue a native APC to the calling thread to forcefully terminate the wait of the WinApi functions, - // in the case it didn't terminate already - [[maybe_unused]] BOOL ret = QueueUserAPC(EndWait, *GetNativeHandle(eThread->UniqueThread), 0); - assert(ret); - EmuKeSetPcr(nullptr); - return true; - } - SwitchToThread(); - if (*Exit) { - EmuKeSetPcr(nullptr); - return false; - } - } - }); -} // ****************************************************************** // * 0x0033 - InterlockedCompareExchange() diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index 3e83d5330..5414174b8 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -27,6 +27,7 @@ #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" +#include "core\kernel\support\EmuFS.h" #include // CONTAINING_RECORD macro @@ -104,6 +105,40 @@ extern HalSystemInterrupt HalSystemInterrupts[MAX_BUS_INTERRUPT_LEVEL + 1]; bool DisableInterrupts(); void RestoreInterruptMode(bool value); void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql); -std::future WaitApc(xbox::boolean_xt Alertable, xbox::char_xt WaitMode, bool *Exit); + +template +xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER AbsoluteExpireTime, 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); + + while (now <= static_cast(AbsoluteExpireTime->QuadPart)) { + 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; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + 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 3ea3433f8..82c9d306a 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -566,14 +566,27 @@ 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 - bool Exit = false; - auto async_bool = WaitApc(Alertable, WaitMode, &Exit); + LARGE_INTEGER NewTime; + if (Interval) { + LARGE_INTEGER ExpireTime, DueTime; + ExpireTime.QuadPart = DueTime.QuadPart = Interval->QuadPart; + KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime); + } + else { + NewTime.QuadPart = ~0ull; + } - NTSTATUS ret = NtDll::NtDelayExecution(Alertable, (NtDll::LARGE_INTEGER *)Interval); + xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { + NtDll::LARGE_INTEGER ExpireTime; + ExpireTime.QuadPart = 0; + NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); + if (Status == 0) { // STATUS_SUCCESS + return std::nullopt; + } + return std::make_optional(Status); + }, &NewTime, Alertable, WaitMode); - Exit = true; - bool result = async_bool.get(); - RETURN(result ? X_STATUS_USER_APC : ret); + RETURN(ret); } // ****************************************************************** @@ -2000,24 +2013,6 @@ XBSYSAPI EXPORTNUM(156) xbox::dword_xt VOLATILE xbox::KeTickCount = 0; // ****************************************************************** XBSYSAPI EXPORTNUM(157) xbox::ulong_xt xbox::KeTimeIncrement = CLOCK_TIME_INCREMENT; - -xbox::PLARGE_INTEGER FASTCALL KiComputeWaitInterval( - IN xbox::PLARGE_INTEGER OriginalTime, - IN xbox::PLARGE_INTEGER DueTime, - IN OUT xbox::PLARGE_INTEGER NewTime -) -{ - if (OriginalTime->QuadPart >= 0) { - return OriginalTime; - - } - else { - NewTime->QuadPart = xbox::KeQueryInterruptTime(); - NewTime->QuadPart -= DueTime->QuadPart; - return NewTime; - } -} - // ****************************************************************** // * 0x009E - KeWaitForMultipleObjects() // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index e8cf7549c..2d8b5c395 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -928,3 +928,20 @@ 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 +) +{ + if (OriginalTime->QuadPart >= 0) { + return OriginalTime; + } + else { + NewTime->QuadPart = xbox::KeQueryInterruptTime(); + NewTime->QuadPart -= DueTime->QuadPart; + return NewTime; + } +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 501c53b72..0e9dc3481 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -139,6 +139,13 @@ 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 + ); }; extern xbox::KPROCESS KiUniqueProcess; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 54f3d2009..e62ef2ad5 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2200,19 +2200,32 @@ 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 - bool Exit = false; - auto async_bool = WaitApc(Alertable, WaitMode, &Exit); + LARGE_INTEGER NewTime; + if (Timeout) { + LARGE_INTEGER ExpireTime, DueTime; + ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; + KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime); + } + else { + NewTime.QuadPart = ~0ull; + } - NTSTATUS ret = NtDll::NtWaitForMultipleObjects( - Count, - Handles, - (NtDll::OBJECT_WAIT_TYPE)WaitType, - Alertable, - (NtDll::PLARGE_INTEGER)Timeout); + xbox::ntstatus_xt ret = WaitApc([Count, Handles, WaitType, Alertable]() -> std::optional { + NtDll::LARGE_INTEGER ExpireTime; + ExpireTime.QuadPart = 0; + NTSTATUS Status = NtDll::NtWaitForMultipleObjects( + Count, + Handles, + (NtDll::OBJECT_WAIT_TYPE)WaitType, + Alertable, + &ExpireTime); + if (Status == STATUS_TIMEOUT) { + return std::nullopt; + } + return std::make_optional(Status); + }, &NewTime, Alertable, WaitMode); - Exit = true; - bool result = async_bool.get(); - RETURN(result ? X_STATUS_USER_APC : ret); + RETURN(ret); } // ****************************************************************** diff --git a/src/core/kernel/support/NativeHandle.cpp b/src/core/kernel/support/NativeHandle.cpp index 67f3816c5..74f95141f 100644 --- a/src/core/kernel/support/NativeHandle.cpp +++ b/src/core/kernel/support/NativeHandle.cpp @@ -56,6 +56,7 @@ void RegisterXboxHandle(xbox::HANDLE xhandle, HANDLE nhandle) if (ret.second) { return; } + now += std::chrono::milliseconds(5); } // If we reach here, it means that we could not insert the handle after more than two seconds of trying. This probably means