diff --git a/src/core/kernel/common/nt.h b/src/core/kernel/common/nt.h index 446881176..531f20eae 100644 --- a/src/core/kernel/common/nt.h +++ b/src/core/kernel/common/nt.h @@ -261,7 +261,7 @@ XBSYSAPI EXPORTNUM(206) ntstatus_xt NTAPI NtQueueApcThread IN PIO_APC_ROUTINE ApcRoutine, IN PVOID ApcRoutineContext OPTIONAL, IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, - IN ulong_xt ApcReserved OPTIONAL + IN PVOID ApcReserved OPTIONAL ); // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index 3b3342fd9..2e09cce6d 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -29,6 +29,7 @@ #include +#include "core\kernel\support\EmuFS.h" #include #include #include @@ -119,12 +120,6 @@ xbox::PLIST_ENTRY RemoveTailList(xbox::PLIST_ENTRY pListHead) return Result; } -// ****************************************************************** -// * Declaring this in a header causes errors with xboxkrnl -// * namespace, so we must declare it within any file that uses it -// ****************************************************************** -xbox::KPCR* WINAPI KeGetPcr(); - // Interrupts extern volatile DWORD HalInterruptRequestRegister; @@ -157,8 +152,8 @@ void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql) case PASSIVE_LEVEL: KiUnexpectedInterrupt(); break; - case APC_LEVEL: // = 1 // HalpApcInterrupt - EmuLog(LOG_LEVEL::WARNING, "Unimplemented Software Interrupt (APC)"); // TODO : ExecuteApcQueue(); + case APC_LEVEL: // = 1 + xbox::KiExecuteKernelApc(); break; case DISPATCH_LEVEL: // = 2 ExecuteDpcQueue(); @@ -374,7 +369,7 @@ XBSYSAPI EXPORTNUM(160) xbox::KIRQL FASTCALL xbox::KfRaiseIrql LOG_FUNC_ONE_ARG_TYPE(KIRQL_TYPE, NewIrql); // Inlined KeGetCurrentIrql() : - PKPCR Pcr = KeGetPcr(); + PKPCR Pcr = EmuKeGetPcr(); KIRQL OldIrql = (KIRQL)Pcr->Irql; // Set new before check @@ -402,7 +397,7 @@ XBSYSAPI EXPORTNUM(161) xbox::void_xt FASTCALL xbox::KfLowerIrql { LOG_FUNC_ONE_ARG_TYPE(KIRQL_TYPE, NewIrql); - KPCR* Pcr = KeGetPcr(); + KPCR* Pcr = EmuKeGetPcr(); if (g_bIsDebugKernel && NewIrql > Pcr->Irql) { KIRQL OldIrql = Pcr->Irql; @@ -450,12 +445,23 @@ XBSYSAPI EXPORTNUM(163) xbox::void_xt FASTCALL xbox::KiUnlockDispatcherDatabase { LOG_FUNC_ONE_ARG_TYPE(KIRQL_TYPE, OldIrql); - if (!(KeGetCurrentPrcb()->DpcRoutineActive)) // Avoid KeIsExecutingDpc(), as that logs + // Wrong, this should only happen when OldIrql >= DISPATCH_LEVEL + if (!(KeGetCurrentPrcb()->DpcRoutineActive)) { // Avoid KeIsExecutingDpc(), as that logs HalRequestSoftwareInterrupt(DISPATCH_LEVEL); + } - LOG_INCOMPLETE(); // TODO : Thread-switch? + if (OldIrql < DISPATCH_LEVEL) { + // This is wrong: this should perform a thread switch and check the kthread of the new selected thread for pending APCs. + // We can't perform our own threads switching now, so we will just check the current thread + + if (KeGetCurrentThread()->ApcState.KernelApcPending) { + KiExecuteKernelApc(); + } + } KfLowerIrql(OldIrql); + + LOG_INCOMPLETE(); // TODO : Thread-switch? } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlHal.cpp b/src/core/kernel/exports/EmuKrnlHal.cpp index e20b6bcc3..2ed3494de 100644 --- a/src/core/kernel/exports/EmuKrnlHal.cpp +++ b/src/core/kernel/exports/EmuKrnlHal.cpp @@ -35,6 +35,7 @@ #include "EmuKrnlLogging.h" #include "core\kernel\init\CxbxKrnl.h" // For CxbxrKrnlAbort, and CxbxExec #include "core\kernel\support\Emu.h" // For EmuLog(LOG_LEVEL::WARNING, ) +#include "core\kernel\support\EmuFS.h" #include "EmuKrnl.h" #include "devices\x86\EmuX86.h" // HalReadWritePciSpace needs this #include "EmuShared.h" @@ -59,13 +60,6 @@ uint32_t ResetOrShutdownDataValue = 0; // global list of routines executed during a reboot xbox::LIST_ENTRY ShutdownRoutineList = { &ShutdownRoutineList , &ShutdownRoutineList }; // see InitializeListHead() - -// ****************************************************************** -// * Declaring this in a header causes errors with xboxkrnl -// * namespace, so we must declare it within any file that uses it -// ****************************************************************** -xbox::KPCR* WINAPI KeGetPcr(); - #define TRAY_CLOSED_MEDIA_PRESENT 0x60 #define TRAY_CLOSED_NO_MEDIA 0x40 #define TRAY_OPEN 0x10 @@ -458,10 +452,10 @@ XBSYSAPI EXPORTNUM(48) xbox::void_xt FASTCALL xbox::HalRequestSoftwareInterrupt HalInterruptRequestRegister |= InterruptMask; // Get current IRQL - PKPCR Pcr = KeGetPcr(); + PKPCR Pcr = EmuKeGetPcr(); KIRQL CurrentIrql = (KIRQL)Pcr->Irql; - // Get pending Software Interrupts (by masking of the HW interrupt bits) + // Get pending Software Interrupts (by masking off the HW interrupt bits) uint8_t SoftwareInterrupt = HalInterruptRequestRegister & 3; // Get the highest pending software interrupt level diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index d06869692..3cc58fe3f 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -128,11 +128,11 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value) // ****************************************************************** -// * KeGetPcr() +// * EmuKeGetPcr() // * NOTE: This is a macro on the Xbox, however we implement it // * as a function so it can suit our emulated KPCR structure // ****************************************************************** -xbox::KPCR* WINAPI KeGetPcr() +xbox::KPCR* WINAPI EmuKeGetPcr() { xbox::PKPCR Pcr; @@ -140,7 +140,7 @@ xbox::KPCR* WINAPI KeGetPcr() Pcr = (xbox::PKPCR)__readfsdword(TIB_ArbitraryDataSlot); if (Pcr == nullptr) { - // If we reach here, it's a bug: it means we are executing xbox code from a host thread, and we have forgotten to intialize + // If we reach here, it's a bug: it means we are executing xbox code from a host thread, and we have forgotten to initialize // the xbox thread first CxbxrKrnlAbort("KeGetPCR returned nullptr: Was this called from a non-xbox thread?"); } @@ -153,7 +153,7 @@ xbox::KPCR* WINAPI KeGetPcr() // ****************************************************************** xbox::KPRCB *KeGetCurrentPrcb() { - return &(KeGetPcr()->PrcbData); + return &(EmuKeGetPcr()->PrcbData); } // ****************************************************************** @@ -595,7 +595,7 @@ XBSYSAPI EXPORTNUM(103) xbox::KIRQL NTAPI xbox::KeGetCurrentIrql(void) { LOG_FUNC(); // TODO : Remove nested logging on this somehow, so we can call this (instead of inlining) - KPCR* Pcr = KeGetPcr(); + KPCR* Pcr = EmuKeGetPcr(); KIRQL Irql = (KIRQL)Pcr->Irql; RETURN_TYPE(KIRQL_TYPE, Irql); @@ -993,6 +993,9 @@ XBSYSAPI EXPORTNUM(117) xbox::long_xt NTAPI xbox::KeInsertQueue RETURN(0); } +// ****************************************************************** +// * 0x0076 - KeInsertQueueApc() +// ****************************************************************** XBSYSAPI EXPORTNUM(118) xbox::boolean_xt NTAPI xbox::KeInsertQueueApc ( IN PRKAPC Apc, @@ -1008,9 +1011,47 @@ XBSYSAPI EXPORTNUM(118) xbox::boolean_xt NTAPI xbox::KeInsertQueueApc LOG_FUNC_ARG(Increment) LOG_FUNC_END; - LOG_UNIMPLEMENTED(); + KIRQL OldIrql = KeRaiseIrqlToDpcLevel(); - RETURN(TRUE); + PKTHREAD kThread = Apc->Thread; + if (kThread->ApcState.ApcQueueable == FALSE) { + KfLowerIrql(OldIrql); + RETURN(FALSE); + } + else { + Apc->SystemArgument1 = SystemArgument1; + Apc->SystemArgument2 = SystemArgument2; + + if (Apc->Inserted) { + KfLowerIrql(OldIrql); + RETURN(FALSE); + } + else { + g_ApcListMtx.lock(); + InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry); + g_ApcListMtx.unlock(); + Apc->Inserted = TRUE; + + // We can only attempt to execute the queued apc right away if it is been inserted in the current thread, because otherwise the KTHREAD + // in the fs selector will not be correct + if (kThread == KeGetCurrentThread()) { + if (Apc->ApcMode == KernelMode) { // kernel apc + // NOTE: this is wrong, we should check the thread state instead of just signaling the kernel apc, but we currently + // don't set the appropriate state in kthread + kThread->ApcState.KernelApcPending = TRUE; + KiExecuteKernelApc(); + } + else if ((kThread->WaitMode == UserMode) && (kThread->Alertable)) { // user apc + // NOTE: this should also check the thread state + kThread->ApcState.UserApcPending = TRUE; + KiExecuteUserApc(); + } + } + + KfLowerIrql(OldIrql); + RETURN(TRUE); + } + } } // ****************************************************************** @@ -1087,10 +1128,10 @@ XBSYSAPI EXPORTNUM(122) xbox::void_xt NTAPI xbox::KeLeaveCriticalRegion PKTHREAD thread = KeGetCurrentThread(); thread->KernelApcDisable++; if(thread->KernelApcDisable == 0) { - LIST_ENTRY *apcListHead = &thread->ApcState.ApcListHead[0/*=KernelMode*/]; + LIST_ENTRY *apcListHead = &thread->ApcState.ApcListHead[KernelMode]; if(apcListHead->Flink != apcListHead) { - thread->ApcState.KernelApcPending = 1; // TRUE - HalRequestSoftwareInterrupt(1); // APC_LEVEL + thread->ApcState.KernelApcPending = TRUE; + HalRequestSoftwareInterrupt(APC_LEVEL); } } } @@ -2168,7 +2209,7 @@ NoWait: KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { - // TODO: KiDeliverUserApc(); + KiExecuteUserApc(); } RETURN(WaitStatus); @@ -2352,7 +2393,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject // So unlock the dispatcher database, lower the IRQ and return the status KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { - //TODO: KiDeliverUserApc(); + KiExecuteUserApc(); } RETURN(WaitStatus); diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 5f2f66dec..b67c95440 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -80,6 +80,7 @@ the said software). #include // For KeBugCheck, etc. +#include "core\kernel\support\EmuFS.h" #include "Logging.h" // For LOG_FUNC() #include "EmuKrnl.h" // for the list support functions #include "EmuKrnlKi.h" @@ -877,3 +878,53 @@ xbox::void_xt FASTCALL xbox::KiWaitSatisfyAll return; } + +template +static xbox::void_xt KiExecuteApc() +{ + xbox::PKTHREAD kThread = xbox::KeGetCurrentThread(); + + int ApcMode; + if constexpr (KernelApc) { + kThread->ApcState.KernelApcPending = FALSE; + ApcMode = xbox::KernelMode; + } + else { + kThread->ApcState.UserApcPending = FALSE; + ApcMode = xbox::UserMode; + } + + // Even though the apc list is per-thread, it's still possible that another thread will access it while we are processing it below + xbox::g_ApcListMtx.lock(); + while (!IsListEmpty(&kThread->ApcState.ApcListHead[ApcMode])) { + if (KernelApc && (kThread->KernelApcDisable != 0)) { + xbox::g_ApcListMtx.unlock(); + return; + } + xbox::PLIST_ENTRY Entry = kThread->ApcState.ApcListHead[ApcMode].Flink; + xbox::PKAPC Apc = CONTAINING_RECORD(Entry, xbox::KAPC, ApcListEntry); + RemoveEntryList(Entry); + xbox::g_ApcListMtx.unlock(); + Apc->Inserted = FALSE; + + // NOTE: we never use KernelRoutine + if (Apc->NormalRoutine != xbox::zeroptr) { + (Apc->NormalRoutine)(Apc->NormalContext, Apc->SystemArgument1, Apc->SystemArgument2); + } + + xbox::ExFreePool(Apc); + xbox::g_ApcListMtx.lock(); + } + + xbox::g_ApcListMtx.unlock(); +} + +xbox::void_xt xbox::KiExecuteKernelApc() +{ + KiExecuteApc(); +} + +xbox::void_xt xbox::KiExecuteUserApc() +{ + KiExecuteApc(); +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 24b73d6f0..b4286447b 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -47,14 +47,16 @@ namespace xbox int Acquired; } KI_TIMER_LOCK; + // NOTE: since the apc list is per-thread, we could also create a different mutex for each kthread + std::mutex g_ApcListMtx; - xbox::void_xt KiInitSystem(); + void_xt KiInitSystem(); - xbox::void_xt KiTimerLock(); + void_xt KiTimerLock(); - xbox::void_xt KiTimerUnlock(); + void_xt KiTimerUnlock(); - xbox::void_xt KiClockIsr + void_xt KiClockIsr ( IN unsigned int ScalingFactor ); @@ -64,25 +66,25 @@ namespace xbox IN ULARGE_INTEGER CurrentTime ); - xbox::void_xt KxInsertTimer + void_xt KxInsertTimer ( IN PKTIMER Timer, IN ulong_xt Hand ); - xbox::void_xt FASTCALL KiCompleteTimer + void_xt FASTCALL KiCompleteTimer ( IN PKTIMER Timer, IN ulong_xt Hand ); - xbox::void_xt KiRemoveEntryTimer + void_xt KiRemoveEntryTimer ( IN PKTIMER Timer, IN ulong_xt Hand ); - xbox::void_xt KxRemoveTreeTimer + void_xt KxRemoveTreeTimer ( IN PKTIMER Timer ); @@ -99,7 +101,7 @@ namespace xbox IN LARGE_INTEGER Interval ); - xbox::ulong_xt KiComputeTimerTableIndex + ulong_xt KiComputeTimerTableIndex ( IN ulonglong_xt Interval ); @@ -116,7 +118,7 @@ namespace xbox IN PKTIMER Timer ); - xbox::void_xt NTAPI KiTimerExpiration + void_xt NTAPI KiTimerExpiration ( IN PKDPC Dpc, IN PVOID DeferredContext, @@ -124,16 +126,19 @@ namespace xbox IN PVOID SystemArgument2 ); - xbox::void_xt FASTCALL KiTimerListExpire + void_xt FASTCALL KiTimerListExpire ( IN PLIST_ENTRY ExpiredListHead, IN KIRQL OldIrql ); - xbox::void_xt FASTCALL KiWaitSatisfyAll + void_xt FASTCALL KiWaitSatisfyAll ( IN PKWAIT_BLOCK WaitBlock ); + + void_xt KiExecuteKernelApc(); + void_xt KiExecuteUserApc(); }; extern xbox::KPROCESS KiUniqueProcess; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index a09ef3909..2dc8aa8f8 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -30,6 +30,7 @@ #include // For NtAllocateVirtualMemory, etc. +#include "EmuKrnl.h" #include "Logging.h" // For LOG_FUNC() #include "EmuKrnlLogging.h" @@ -41,6 +42,7 @@ namespace NtDll #include "core\kernel\init\CxbxKrnl.h" // For CxbxrKrnlAbort #include "core\kernel\exports\EmuKrnlKe.h" +#include "EmuKrnlKi.h" #include "core\kernel\support\Emu.h" // For EmuLog(LOG_LEVEL::WARNING, ) #include "core\kernel\support\EmuFile.h" // For EmuNtSymbolicLinkObject, NtStatusToString(), etc. #include "core\kernel\memory-manager\VMManager.h" // For g_VMManager @@ -54,12 +56,16 @@ namespace NtDll #include #include +#include -// Used to keep track of duplicate handles created by NtQueueApcThread() -std::unordered_map g_DuplicateHandles; // Prevent setting the system time from multiple threads at the same time std::mutex NtSystemTimeMtx; +// This helper function is used to signal NtDll::NtWaitForMultipleObjects that the wait has been satisfied by an xbox user APC +static void WINAPI EndWait(ULONG_PTR Parameter) +{ + // Do nothing +} // ****************************************************************** // * 0x00B8 - NtAllocateVirtualMemory() @@ -173,15 +179,6 @@ XBSYSAPI EXPORTNUM(187) xbox::ntstatus_xt NTAPI xbox::NtClose if (GetHandleInformation(Handle, &flags) != 0) { // This was a native handle, call NtDll::NtClose ret = NtDll::NtClose(Handle); - - // Delete duplicate threads created by our implementation of NtQueueApcThread() - if (GetHandleInformation(g_DuplicateHandles[Handle], &flags) != 0) - { - EmuLog(LOG_LEVEL::DEBUG, "Closing duplicate handle..."); - - CloseHandle(g_DuplicateHandles[Handle]); - g_DuplicateHandles.erase(Handle); - } } } @@ -1028,7 +1025,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread IN PIO_APC_ROUTINE ApcRoutine, IN PVOID ApcRoutineContext OPTIONAL, IN PIO_STATUS_BLOCK ApcStatusBlock OPTIONAL, - IN ulong_xt ApcReserved OPTIONAL + IN PVOID ApcReserved OPTIONAL ) { LOG_FUNC_BEGIN @@ -1039,54 +1036,27 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread LOG_FUNC_ARG(ApcReserved) LOG_FUNC_END; - // In order for NtQueueApcThread or QueueUserAPC to work, you must... I repeat... - // YOU MUST duplicate the handle with the appropriate permissions first! So far, - // the only game that I know of using this is Metal Slug 3, and it won't launch - // without it. Other SNK games might use it also, beware. + PETHREAD Thread; + ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast(&Thread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); + } - // TODO: Use our implementation of NtDuplicateObject instead? - - HANDLE hApcThread = NULL; - - // Just to be safe, let's see if the appropriate permissions are even set for the - // target thread first... - - NTSTATUS ret = NtDll::NtQueueApcThread( - (NtDll::HANDLE)ThreadHandle, - (NtDll::PIO_APC_ROUTINE)ApcRoutine, - ApcRoutineContext, - (NtDll::PIO_STATUS_BLOCK)ApcStatusBlock, - ApcReserved); - - if( FAILED( ret ) ) - { - EmuLog(LOG_LEVEL::WARNING, "Duplicating handle with THREAD_SET_CONTEXT..." ); - - // If we get here, then attempt to duplicate the thread. - if(!DuplicateHandle(g_CurrentProcessHandle, ThreadHandle, g_CurrentProcessHandle, &hApcThread, THREAD_SET_CONTEXT,FALSE,0)) - EmuLog(LOG_LEVEL::WARNING, "DuplicateHandle failed!"); - else - { - g_DuplicateHandles[ThreadHandle] = hApcThread; // Save this thread because we'll need to de-reference it later - EmuLog(LOG_LEVEL::DEBUG, "DuplicateHandle returned 0x%X (ThreadId 0x%.4X)", hApcThread, GetThreadId( hApcThread ) ); + PKAPC Apc = static_cast(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP')); + if (Apc != zeroptr) { + KeInitializeApc(Apc, &Thread->Tcb, zeroptr, zeroptr, reinterpret_cast(ApcRoutine), UserMode, ApcRoutineContext); + if (!KeInsertQueueApc(Apc, ApcStatusBlock, ApcReserved, 0)) { + ExFreePool(Apc); + result = X_STATUS_UNSUCCESSFUL; } - - - ret = NtDll::NtQueueApcThread( - (NtDll::HANDLE)hApcThread, - (NtDll::PIO_APC_ROUTINE)ApcRoutine, - ApcRoutineContext, - (NtDll::PIO_STATUS_BLOCK)ApcStatusBlock, - ApcReserved); } - if (FAILED(ret)) - { - EmuLog(LOG_LEVEL::WARNING, "NtQueueApcThread failed!"); - CloseHandle( g_DuplicateHandles[ThreadHandle] ); - g_DuplicateHandles.erase( ThreadHandle ); + else { + result = X_STATUS_NO_MEMORY; } - RETURN(ret); + ObfDereferenceObject(Thread); + + RETURN(result); } // ****************************************************************** @@ -1159,8 +1129,8 @@ XBSYSAPI EXPORTNUM(207) xbox::ntstatus_xt NTAPI xbox::NtQueryDirectoryFile ret = NtDll::NtQueryDirectoryFile( FileHandle, Event, - (NtDll::PIO_APC_ROUTINE)ApcRoutine, - ApcContext, + (NtDll::PIO_APC_ROUTINE)ApcRoutine, + ApcContext, (NtDll::IO_STATUS_BLOCK*)IoStatusBlock, /*FileInformation=*/NtFileDirInfo, NtFileDirectoryInformationSize + NtPathBufferSize, @@ -2227,20 +2197,53 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // This function can wait on thread handles, which are currently created by ob, // so we need to check their presence in the handle array - for (ulong_xt i = 0; i < Count; ++i) { if (const auto &nativeHandle = GetNativeHandle(Handles[i])) { - // This a ob handle, so replace it with its native counterpart + // This is a ob handle, so replace it with its native counterpart Handles[i] = *nativeHandle; } } - return NtDll::NtWaitForMultipleObjects( - Count, - Handles, - (NtDll::OBJECT_WAIT_TYPE)WaitType, - Alertable, - (NtDll::PLARGE_INTEGER)Timeout); + // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves + if (Alertable && (WaitMode == UserMode)) { + bool Exit = false; + PETHREAD eThread = PspGetCurrentThread(); + auto &fut = std::async(std::launch::async, [eThread, &Exit]() { + while (true) { + xbox::g_ApcListMtx.lock(); + bool Empty = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[UserMode]); + xbox::g_ApcListMtx.unlock(); + if (Empty == false) { + KiExecuteUserApc(); + // Queue a native APC to the calling thread to forcefully terminate the wait in NtDll::NtWaitForMultipleObjects, + // in the case it didn't terminate already + QueueUserAPC(EndWait, *GetNativeHandle(eThread->UniqueThread), 0); + return true; + } + Sleep(0); + if (Exit) { return false; } + } + }); + + NTSTATUS ret = NtDll::NtWaitForMultipleObjects( + Count, + Handles, + (NtDll::OBJECT_WAIT_TYPE)WaitType, + Alertable, + (NtDll::PLARGE_INTEGER)Timeout); + + Exit = true; + bool result = fut.get(); + return result ? X_STATUS_USER_APC : ret; + } + else { + return NtDll::NtWaitForMultipleObjects( + Count, + Handles, + (NtDll::OBJECT_WAIT_TYPE)WaitType, + Alertable, + (NtDll::PLARGE_INTEGER)Timeout); + } } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 291fd0f05..0e1b0f34e 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -38,6 +38,7 @@ #include "Logging.h" // For LOG_FUNC() #include "EmuKrnlLogging.h" #include "core\kernel\init\CxbxKrnl.h" // For CxbxKrnl_TLS +#include "EmuKrnl.h" #include "core\kernel\support\Emu.h" // For EmuLog(LOG_LEVEL::WARNING, ) #include "core\kernel\support\EmuFS.h" // For EmuGenerateFS #include "core\kernel\support\NativeHandle.h" @@ -404,12 +405,24 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread } }*/ + PKTHREAD kThread = KeGetCurrentThread(); + kThread->ApcState.ApcQueueable = FALSE; + + g_ApcListMtx.lock(); + for (int Mode = KernelMode; Mode < MaximumMode; ++Mode) { + while (!IsListEmpty(&kThread->ApcState.ApcListHead[Mode])) { + PLIST_ENTRY Entry = kThread->ApcState.ApcListHead[Mode].Flink; + PKAPC Apc = CONTAINING_RECORD(Entry, KAPC, ApcListEntry); + RemoveEntryList(Entry); + ExFreePool(Apc); + } + } + g_ApcListMtx.unlock(); + EmuKeFreeThread(); KiUniqueProcess.StackCount--; _endthreadex(ExitStatus); - // ExitThread(ExitStatus); - // CxbxKrnlTerminateThread(); } // ****************************************************************** diff --git a/src/core/kernel/support/EmuFS.cpp b/src/core/kernel/support/EmuFS.cpp index a5e4a0a3d..a25f9d710 100644 --- a/src/core/kernel/support/EmuFS.cpp +++ b/src/core/kernel/support/EmuFS.cpp @@ -116,9 +116,6 @@ NT_TIB *GetNtTib() return (NT_TIB *)__readfsdword(TIB_LinearSelfAddress); } - -xbox::KPCR* WINAPI KeGetPcr(); - uint32_t fs_lock = 0; __declspec(naked) void LockFS() @@ -160,7 +157,7 @@ __declspec(naked) void UnlockFS() void EmuKeSetPcr(xbox::KPCR *Pcr) { - // Store the Xbox KPCR pointer in FS (See KeGetPcr()) + // Store the Xbox KPCR pointer in FS (See EmuKeGetPcr()) // // Note : Cxbx currently doesn't do preemptive thread switching, // which implies that thread-state management is done by Windows. @@ -190,7 +187,7 @@ void EmuKeSetPcr(xbox::KPCR *Pcr) void EmuKeFreePcr() { - xbox::PKPCR Pcr = KeGetPcr(); + xbox::PKPCR Pcr = EmuKeGetPcr(); xbox::PVOID Dummy; xbox::ulong_xt Size; @@ -227,8 +224,8 @@ void EmuKeFreeThread() __declspec(naked) void EmuFS_RefreshKPCR() { - // Backup all registers, call KeGetPcr and then restore all registers - // KeGetPcr makes sure a valid KPCR exists for the current thread + // Backup all registers, call EmuKeGetPcr and then restore all registers + // EmuKeGetPcr makes sure a valid KPCR exists for the current thread // and creates it if missing, we backup and restore all registers // to keep it safe to call in our patches // This function can be later expanded to do nice things @@ -236,7 +233,7 @@ __declspec(naked) void EmuFS_RefreshKPCR() __asm { pushfd pushad - call KeGetPcr + call EmuKeGetPcr popad popfd ret @@ -806,6 +803,8 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PVOID Ethread) EThread->Tcb.TlsData = pNewTLS; // Set PrcbData.CurrentThread Prcb->CurrentThread = (xbox::KTHREAD*)EThread; + Prcb->CurrentThread->KernelApcDisable = 0; + Prcb->CurrentThread->ApcState.ApcQueueable = TRUE; // Initialize the thread header and its wait list Prcb->CurrentThread->Header.Type = xbox::ThreadObject; Prcb->CurrentThread->Header.Size = sizeof(xbox::KTHREAD) / sizeof(xbox::long_xt); @@ -821,7 +820,7 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PVOID Ethread) WaitBlock->WaitListEntry.Blink = &Prcb->CurrentThread->Timer.Header.WaitListHead; } - // Make the KPCR struct available to KeGetPcr() + // Make the KPCR struct available to EmuKeGetPcr() EmuKeSetPcr(NewPcr); EmuLog(LOG_LEVEL::DEBUG, "Installed KPCR in TIB_ArbitraryDataSlot (with pTLS = 0x%.8X)", pTLS); diff --git a/src/core/kernel/support/EmuFS.h b/src/core/kernel/support/EmuFS.h index 402adb568..5dc05332f 100644 --- a/src/core/kernel/support/EmuFS.h +++ b/src/core/kernel/support/EmuFS.h @@ -39,6 +39,9 @@ void EmuKeFreeThread(); // free kpcr allocated for the thread void EmuKeFreePcr(); +void EmuKeSetPcr(xbox::KPCR *Pcr); +xbox::KPCR *_stdcall EmuKeGetPcr(); + typedef struct { std::vector data;