From f0a1301b64a4805f13f6b3cc02c6148337d54ff9 Mon Sep 17 00:00:00 2001 From: RadWolfie Date: Mon, 28 Mar 2022 20:45:59 -0500 Subject: [PATCH] improve xbox thread implement --- CMakeLists.txt | 1 + src/core/kernel/exports/EmuKrnlKe.cpp | 146 ++++++++++++++++ src/core/kernel/exports/EmuKrnlKe.h | 12 ++ src/core/kernel/exports/EmuKrnlKi.cpp | 112 +++++++++++++ src/core/kernel/exports/EmuKrnlKi.h | 26 +++ src/core/kernel/exports/EmuKrnlPs.cpp | 170 +++++++++++++++---- src/core/kernel/exports/EmuKrnlPs.hpp | 28 ++++ src/core/kernel/init/CxbxKrnl.cpp | 11 +- src/core/kernel/support/EmuFS.cpp | 232 +++++++++----------------- src/core/kernel/support/EmuFS.h | 17 +- 10 files changed, 565 insertions(+), 190 deletions(-) create mode 100644 src/core/kernel/exports/EmuKrnlPs.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d7b9a7ec..4fb37ce21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,6 +177,7 @@ file (GLOB CXBXR_HEADER_EMU "${CXBXR_ROOT_DIR}/src/core/kernel/exports/EmuKrnlKe.h" "${CXBXR_ROOT_DIR}/src/core/kernel/exports/EmuKrnlKi.h" "${CXBXR_ROOT_DIR}/src/core/kernel/exports/EmuKrnlLogging.h" + "${CXBXR_ROOT_DIR}/src/core/kernel/exports/EmuKrnlPs.hpp" "${CXBXR_ROOT_DIR}/src/core/kernel/init/CxbxKrnl.h" "${CXBXR_ROOT_DIR}/src/core/kernel/init/KrnlPatches.hpp" "${CXBXR_ROOT_DIR}/src/core/kernel/memory-manager/PhysicalMemory.h" diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index e8c438f61..2716750e1 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -266,7 +266,9 @@ xbox::void_xt NTAPI xbox::KeInitializeTimer xbox::void_xt xbox::KeEmptyQueueApc() { PKTHREAD kThread = KeGetCurrentThread(); + KeEnterCriticalRegion(); kThread->ApcState.ApcQueueable = FALSE; + KeLeaveCriticalRegion(); KiApcListMtx.lock(); for (int Mode = KernelMode; Mode < MaximumMode; ++Mode) { @@ -280,6 +282,150 @@ xbox::void_xt xbox::KeEmptyQueueApc() KiApcListMtx.unlock(); } +// Source: ReactOS (modified to fit in xbox compatibility layer) +template +xbox::void_xt xbox::KeInitializeThread( + IN OUT PKTHREAD Thread, + IN PVOID KernelStack, + IN ulong_xt KernelStackSize, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext, + IN PKPROCESS Process +) +{ + /* ReactOS's KeInitThread inline code begin */ + + /* Initialize the Dispatcher Header */ + Thread->Header.Type = xbox::ThreadObject; + Thread->Header.Size = sizeof(xbox::KTHREAD) / sizeof(xbox::long_xt); + // ThreadControlFlags + // DebugActive + Thread->Header.SignalState = 0; + InitializeListHead(&Thread->Header.WaitListHead); + + /* Initialize the Mutant List */ + InitializeListHead(&Thread->MutantListHead); + +#if 0 // Not used or not yet reverse engineered + /* Set swap settings */ + Thread->EnableStackSwap = TRUE; + Thread->IdealProcessor = 1; + Thread->SwapBusy = FALSE; + Thread->KernelStackResident = TRUE; + Thread->AdjustReason = AdjustNone; +#endif + +#if 0 // Not used or not yet reverse engineered + /* Initialize the lock */ + KeInitializeSpinLock(&Thread->ThreadLock); +#endif + +#if 0 // Not used or not yet reverse engineered + /* Setup the Service Descriptor Table for Native Calls */ + Thread->ServiceTable = KeServiceDescriptorTable; +#endif + + /* Setup APC Fields */ + InitializeListHead(&Thread->ApcState.ApcListHead[xbox::KernelMode]); + InitializeListHead(&Thread->ApcState.ApcListHead[xbox::UserMode]); + Thread->KernelApcDisable = 0; + Thread->ApcState.Process = &KiUniqueProcess; + Thread->ApcState.ApcQueueable = TRUE; + Thread->ApcState.Process->ThreadQuantum = KiUniqueProcess.ThreadQuantum; + + /* Initialize the Suspend APC */ + KeInitializeApc( + &Thread->SuspendApc, + Thread, + KiSuspendNop, + zeroptr, + KiSuspendThread, + KernelMode, + zeroptr); + + /* Initialize the Suspend Semaphore */ + KeInitializeSemaphore(&Thread->SuspendSemaphore, 0, 2); + + /* Setup the timer */ + xbox::KeInitializeTimer(&Thread->Timer); + xbox::PKWAIT_BLOCK TimerWaitBlock = &Thread->TimerWaitBlock; + TimerWaitBlock->Object = &Thread->Timer; + TimerWaitBlock->WaitKey = (xbox::cshort_xt)X_STATUS_TIMEOUT; + TimerWaitBlock->WaitType = xbox::WaitAny; + TimerWaitBlock->Thread = Thread; + TimerWaitBlock->NextWaitBlock = zeroptr; + + /* Link the two wait lists together */ + TimerWaitBlock->WaitListEntry.Flink = &Thread->Timer.Header.WaitListHead; + TimerWaitBlock->WaitListEntry.Blink = &Thread->Timer.Header.WaitListHead; + +#if 0 // Not used or not yet reverse engineered + /* Set the TEB and process */ + Thread->Teb = Teb; + Thread->Process = Process; +#endif + + /* Set the Thread Stacks */ + Thread->StackBase = KernelStack; + Thread->StackLimit = reinterpret_cast(reinterpret_cast(KernelStack) - KernelStackSize); + + /* Initialize the Thread Context */ + KiInitializeContextThread(Thread, TlsDataSize, SystemRoutine, StartRoutine, StartContext); + + /* Set the Thread to initialized */ + Thread->State = Initialized; + + /* ReactOS's KeInitThread inline code end */ + + /* ReactOS's KeStartThread inline code begin */ + // NOTE: The cxbxr's kernel initialization will not be insert into ThreadListHead of Process. + if constexpr (!IsHostThread) { + /* Setup static fields from parent */ + Thread->DisableBoost = Process->DisableBoost; + Thread->Quantum = Process->ThreadQuantum; + + /* Setup volatile data */ + Thread->Priority = Process->BasePriority; + Thread->BasePriority = Process->BasePriority; + + /* Lock the Dispatcher Database */ + UCHAR orig_irql = KeRaiseIrqlToDpcLevel(); + + /* Insert the thread into the process list */ + InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry); + /* Increase the stack count */ + Process->StackCount++; + + /* Release lock and return */ + KfLowerIrql(orig_irql); + } + /* ReactOS's KeStartThread inline code end */ +} +template +xbox::void_xt xbox::KeInitializeThread( + IN OUT PKTHREAD Thread, + IN PVOID KernelStack, + IN ulong_xt KernelStackSize, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext, + IN PKPROCESS Process + ); +template +xbox::void_xt xbox::KeInitializeThread( + IN OUT PKTHREAD Thread, + IN PVOID KernelStack, + IN ulong_xt KernelStackSize, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext, + IN PKPROCESS Process + ); + // Forward KeLowerIrql() to KfLowerIrql() #define KeLowerIrql(NewIrql) \ KfLowerIrql(NewIrql) diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index d92b1cb96..a7e2ebbc7 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -38,5 +38,17 @@ namespace xbox IN PKTIMER Timer ); + template + void_xt KeInitializeThread( + IN OUT PKTHREAD Thread, + IN PVOID KernelStack, + IN ulong_xt KernelStackSize, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext, + IN PKPROCESS Process + ); + void_xt KeEmptyQueueApc(); } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 2d8b5c395..6641294b8 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -945,3 +945,115 @@ xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval return NewTime; } } + +// Source: ReactOS +xbox::void_xt NTAPI xbox::KiSuspendNop( + IN PKAPC Apc, + IN PKNORMAL_ROUTINE* NormalRoutine, + IN PVOID* NormalContext, + IN PVOID* SystemArgument1, + IN PVOID* SystemArgument2 +) +{ + /* Does nothing */ + UNREFERENCED_PARAMETER(Apc); + UNREFERENCED_PARAMETER(NormalRoutine); + UNREFERENCED_PARAMETER(NormalContext); + UNREFERENCED_PARAMETER(SystemArgument1); + UNREFERENCED_PARAMETER(SystemArgument2); +} + +// Source: ReactOS +xbox::void_xt NTAPI xbox::KiSuspendThread( + IN PVOID NormalContext, + IN PVOID SystemArgument1, + IN PVOID SystemArgument2 +) +{ + /* Non-alertable kernel-mode suspended wait */ + KeWaitForSingleObject( + &KeGetCurrentThread()->SuspendSemaphore, + Suspended, + KernelMode, + FALSE, + zeroptr); +} + +xbox::void_xt NTAPI xbox::KiThreadStartup(void_xt) +{ + PKSTART_FRAME StartFrame; + PKSWITCHFRAME SwitchFrame; + + /* Get the start and trap frames */ + SwitchFrame = reinterpret_cast(KeGetCurrentThread()->KernelStack); + StartFrame = reinterpret_cast(SwitchFrame + 1); + + /* Lower to Passive level */ + KfLowerIrql(PASSIVE_LEVEL); + + // NOTE: if assert is triggered, then thread-switching may have been processed. + // If it does, then verify xbox thread's StackBase is from MmCreateKernelStack instead of host's stack and was not deleted by MmDeleteKernelStack. + // Otherwise, feel free to clear this reminder message. + assert(0); + + /* Call the system routine */ + StartFrame->SystemRoutine(StartFrame->StartRoutine, StartFrame->StartContext); + + /* We do not return as it is a top function */ + PsTerminateSystemThread(X_STATUS_NO_MEMORY); +} + +// Source: ReactOS (modified to fit in xbox compatibility layer) +xbox::void_xt xbox::KiInitializeContextThread( + IN PKTHREAD Thread, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext +) +{ + addr_xt StackAddress = reinterpret_cast(Thread->StackBase); + + /* Setup the Fx Area */ + StackAddress -= sizeof(FX_SAVE_AREA); + PFX_SAVE_AREA FxSaveArea = reinterpret_cast(StackAddress); + std::memset(FxSaveArea, 0, sizeof(FX_SAVE_AREA)); + + /* Set the stub FX area */ + FxSaveArea->FloatSave.ControlWord = 0x27F; + FxSaveArea->FloatSave.MXCsr = 0x1F80; + + /* No NPX State */ + Thread->NpxState = NPX_STATE_NOT_LOADED; + + /* Setup the Stack for TlsData dynamic sized array */ + TlsDataSize = ALIGN_UP(TlsDataSize, ulong_xt); + StackAddress -= TlsDataSize; // TlsData section (optional) + if (TlsDataSize) { + Thread->TlsData = reinterpret_cast(StackAddress); + // Title will process which section of TlsData will be fill with data and zero'd. + // So, we leave this untouched. + } + else { + Thread->TlsData = zeroptr; + } + + /* Setup the Stack for KiThreadStartup and Context Switching */ + StackAddress -= sizeof(KSTART_FRAME); + PKSTART_FRAME StartFrame = reinterpret_cast(StackAddress); + StackAddress -= sizeof(KSWITCHFRAME); + PKSWITCHFRAME CtxSwitchFrame = reinterpret_cast(StackAddress); + + /* Now setup the remaining data for KiThreadStartup */ + StartFrame->StartContext = StartContext; + StartFrame->StartRoutine = StartRoutine; + StartFrame->SystemRoutine = SystemRoutine; + + /* And set up the Context Switch Frame */ + CtxSwitchFrame->RetAddr = KiThreadStartup; + CtxSwitchFrame->Unknown = 0x200; // TODO: Find out what this field is. + CtxSwitchFrame->ExceptionList = reinterpret_cast(X_EXCEPTION_CHAIN_END); + + /* Save back the new value of the kernel stack. */ + Thread->KernelStack = reinterpret_cast(CtxSwitchFrame); +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 0e9dc3481..b6737406b 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -146,6 +146,32 @@ namespace xbox IN PLARGE_INTEGER DueTime, IN OUT PLARGE_INTEGER NewTime ); + + // Source: ReactOS + void_xt NTAPI KiSuspendNop( + IN PKAPC Apc, + IN PKNORMAL_ROUTINE* NormalRoutine, + IN PVOID* NormalContext, + IN PVOID* SystemArgument1, + IN PVOID* SystemArgument2 + ); + + // Source: ReactOS + void_xt NTAPI KiSuspendThread( + IN PVOID NormalContext, + IN PVOID SystemArgument1, + IN PVOID SystemArgument2 + ); + + void_xt NTAPI KiThreadStartup(void_xt); + + xbox::void_xt KiInitializeContextThread( + IN PKTHREAD Thread, + IN ulong_xt TlsDataSize, + IN PKSYSTEM_ROUTINE SystemRoutine, + IN PKSTART_ROUTINE StartRoutine, + IN PVOID StartContext + ); }; extern xbox::KPROCESS KiUniqueProcess; diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 0b569a2f2..55d7d09ba 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -31,6 +31,7 @@ #include // For PsCreateSystemThreadEx, etc. +#include "EmuKrnlPs.hpp" #include "core\kernel\exports\EmuKrnlKi.h" #include "core\kernel\exports\EmuKrnlKe.h" #include // For __beginthreadex(), etc. @@ -55,10 +56,8 @@ namespace NtDll // PsCreateSystemThread proxy parameters typedef struct _PCSTProxyParam { - IN xbox::PVOID StartRoutine; - IN xbox::PVOID StartContext; - IN xbox::PVOID SystemRoutine; IN xbox::PVOID Ethread; + IN xbox::ulong_xt TlsDataSize; } PCSTProxyParam; @@ -72,7 +71,8 @@ void LOG_PCSTProxy xbox::PVOID StartRoutine, xbox::PVOID StartContext, xbox::PVOID SystemRoutine, - xbox::PVOID Ethread + xbox::PVOID Ethread, + xbox::ulong_xt TlsDataSize ) { LOG_FUNC_BEGIN @@ -80,6 +80,7 @@ void LOG_PCSTProxy LOG_FUNC_ARG(StartContext) LOG_FUNC_ARG(SystemRoutine) LOG_FUNC_ARG(Ethread) + LOG_FUNC_ARG(TlsDataSize) LOG_FUNC_END; } @@ -97,32 +98,49 @@ static unsigned int WINAPI PCSTProxy // Copy params to the stack so they can be freed PCSTProxyParam params = *iPCSTProxyParam; delete iPCSTProxyParam; - - LOG_PCSTProxy( - params.StartRoutine, - params.StartContext, - params.SystemRoutine, - params.Ethread); +#ifndef ENABLE_KTHREAD_SWITCHING + unsigned Host2XbStackBaseReserved = 0; + __asm mov Host2XbStackBaseReserved, esp; + unsigned Host2XbStackSizeReserved = EmuGenerateStackSize(Host2XbStackBaseReserved, params.TlsDataSize); + __asm sub esp, Host2XbStackSizeReserved; +#endif // Do minimal thread initialization - EmuGenerateFS(CxbxKrnl_TLS, CxbxKrnl_TLSData, static_cast(params.Ethread)); + xbox::PETHREAD eThread = static_cast(params.Ethread); +#ifndef ENABLE_KTHREAD_SWITCHING + EmuGenerateFS(eThread, Host2XbStackBaseReserved, Host2XbStackSizeReserved); +#else + EmuGenerateFS(eThread); +#endif + xbox::PKSTART_FRAME StartFrame = reinterpret_cast(reinterpret_cast(eThread->Tcb.KernelStack) + sizeof(xbox::KSWITCHFRAME)); - auto routine = (xbox::PKSYSTEM_ROUTINE)params.SystemRoutine; + LOG_PCSTProxy( + StartFrame->StartRoutine, + StartFrame->StartContext, + StartFrame->SystemRoutine, + params.Ethread, + params.TlsDataSize); + + auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a // message like: "Exception thrown at 0x00026190 in cxbx.exe: 0xC0000005: Access // violation reading location 0xFD001804.", then this is AS-DESIGNED behaviour! // (To avoid repetitions, uncheck "Break when this exception type is thrown"). - routine(xbox::PKSTART_ROUTINE(params.StartRoutine), params.StartContext); + routine(StartFrame->StartRoutine, StartFrame->StartContext); // This will also handle thread notification : LOG_TEST_CASE("Thread returned from SystemRoutine"); xbox::PsTerminateSystemThread(X_STATUS_SUCCESS); +#ifndef ENABLE_KTHREAD_SWITCHING + __asm add esp, Host2XbStackSizeReserved; +#endif + return 0; // will never be reached } // Placeholder system function, instead of XapiThreadStartup -void PspSystemThreadStartup +xbox::void_xt NTAPI PspSystemThreadStartup ( IN xbox::PKSTART_ROUTINE StartRoutine, IN PVOID StartContext @@ -151,6 +169,64 @@ static xbox::void_xt PspCallThreadNotificationRoutines(xbox::PETHREAD eThread, x } } +// Source: ReactOS +xbox::LIST_ENTRY PspReaperListHead; +xbox::void_xt NTAPI PspReaperRoutine( + IN xbox::PKDPC Dpc, + IN xbox::PVOID DeferredContext, + IN xbox::PVOID SystemArgument1, + IN xbox::PVOID SystemArgument2 +) +{ + using namespace xbox; + xbox::PLIST_ENTRY NextEntry; + PETHREAD Thread; + //PSTRACE(PS_KILL_DEBUG, "Context: %p\n", Context); + + /* Write magic value and return the next entry to process */ + NextEntry = PspReaperListHead.Flink; + + /* Start loop */ + while (NextEntry != &PspReaperListHead) { + /* Get the first Thread Entry */ + Thread = CONTAINING_RECORD(NextEntry, ETHREAD, ReaperLink); + + RemoveEntryList(NextEntry); + + // Currently, only kernel's stack portion reside on the host's stack. + // Once we have our own kernel thread switching implement or in virtual environment. + // Then enable ENABLE_KTHREAD_SWITCHING macro for any further implement needed. +#ifdef ENABLE_KTHREAD_SWITCHING + /* Delete this entry's kernel stack */ + MmDeleteKernelStack(Thread->Tcb.StackBase, Thread->Tcb.StackLimit); +#else + // Backup plan in case if certain titles did not let new thread start. + // And therefore still have the xbox's kernel stack allocated. + if (MmIsAddressValid(Thread->Tcb.StackBase)) { + MmDeleteKernelStack(Thread->Tcb.StackBase, Thread->Tcb.StackLimit); + } +#endif + Thread->Tcb.StackBase = zeroptr; + + /* Move to the next entry */ + NextEntry = NextEntry->Flink; + + /* Dereference this thread */ + ObfDereferenceObject(Thread); + } +} + +static xbox::KDPC PsReaperDpc; +xbox::void_xt xbox::PsInitSystem() +{ +#ifdef ENABLE_KTHREAD_SWITCHING + assert(0); // NOTE: Verify all defined ENABLE_KTHREAD_SWITCHING check are implemented +#endif + /* Setup the reaper */ + InitializeListHead(&PspReaperListHead); + KeInitializeDpc(&PsReaperDpc, PspReaperRoutine, zeroptr); +} + // ****************************************************************** // * 0x00FE - PsCreateSystemThread() // ****************************************************************** @@ -222,20 +298,19 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx LOG_FUNC_ARG(SystemRoutine) LOG_FUNC_END; - // TODO : Arguments to use : TlsDataSize, DebuggerThread - // use default kernel stack size if lesser specified if (KernelStackSize < KERNEL_STACK_SIZE) KernelStackSize = KERNEL_STACK_SIZE; // Double the stack size, this is to account for the overhead HLE patching adds to the stack - KernelStackSize *= 2; + uint32_t hKernelStackSize = KernelStackSize * 2; // round up to the next page boundary if un-aligned KernelStackSize = RoundUp(KernelStackSize, PAGE_SIZE); + hKernelStackSize = RoundUp(hKernelStackSize, PAGE_SIZE); - // create thread, using our special proxy technique - { + // create thread, using our special proxy technique + { PETHREAD eThread; ntstatus_xt result = ObCreateObject(&PsThreadObjectType, zeroptr, sizeof(ETHREAD) + ThreadExtensionSize, reinterpret_cast(&eThread)); if (!X_NT_SUCCESS(result)) { @@ -244,6 +319,17 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx std::memset(eThread, 0, sizeof(ETHREAD) + ThreadExtensionSize); + // Create kernel stack for xbox title to able write on stack instead of host. + PVOID KernelStack = MmCreateKernelStack(KernelStackSize, DebuggerThread); + + if (!KernelStack) { + ObfDereferenceObject(eThread); + RETURN(X_STATUS_INSUFFICIENT_RESOURCES); + } + + // Start thread initialization process here before insert and create thread + KeInitializeThread(&eThread->Tcb, KernelStack, KernelStackSize, TlsDataSize, SystemRoutine, StartRoutine, StartContext, &KiUniqueProcess); + // The ob handle of the ethread obj is the thread id we return to the title result = ObInsertObject(eThread, zeroptr, 0, &eThread->UniqueThread); if (!X_NT_SUCCESS(result)) { @@ -266,16 +352,14 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx *ThreadId = eThread->UniqueThread; } - // PCSTProxy is responsible for cleaning up this pointer + // PCSTProxy is responsible for cleaning up this pointer PCSTProxyParam *iPCSTProxyParam = new PCSTProxyParam; - iPCSTProxyParam->StartRoutine = (PVOID)StartRoutine; - iPCSTProxyParam->StartContext = StartContext; - iPCSTProxyParam->SystemRoutine = (PVOID)SystemRoutine; // NULL, XapiThreadStartup or unknown? iPCSTProxyParam->Ethread = eThread; + iPCSTProxyParam->TlsDataSize = TlsDataSize; unsigned int ThreadId; - HANDLE handle = reinterpret_cast(_beginthreadex(NULL, KernelStackSize, PCSTProxy, iPCSTProxyParam, CREATE_SUSPENDED, &ThreadId)); - if (handle == NULL) { + HANDLE handle = reinterpret_cast(_beginthreadex(NULL, hKernelStackSize, PCSTProxy, iPCSTProxyParam, CREATE_SUSPENDED, &ThreadId)); + if (handle == zeroptr) { delete iPCSTProxyParam; ObpClose(eThread->UniqueThread); ObfDereferenceObject(eThread); @@ -288,8 +372,6 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx ObfReferenceObject(eThread); KeQuerySystemTime(&eThread->CreateTime); - InsertTailList(&KiUniqueProcess.ThreadListHead, &eThread->Tcb.ThreadListEntry); - KiUniqueProcess.StackCount++; RegisterXboxHandle(*ThreadHandle, handle); HANDLE dupHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadId); assert(dupHandle); @@ -368,15 +450,43 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread LOG_FUNC_ONE_ARG(ExitStatus); xbox::PETHREAD eThread = xbox::PspGetCurrentThread(); + eThread->Tcb.HasTerminated = 1; + + KfLowerIrql(PASSIVE_LEVEL); + if (eThread->UniqueThread && g_iThreadNotificationCount) { PspCallThreadNotificationRoutines(eThread, FALSE); } - EmuKeFreeThread(ExitStatus); - // Don't do this in EmuKeFreeThread because we only increment the thread ref count in PsCreateSystemThreadEx - ObfDereferenceObject(eThread); + KeEmptyQueueApc(); + + // Emulate our exit strategy for GetExitCodeThread + KeQuerySystemTime(&eThread->ExitTime); + eThread->ExitStatus = ExitStatus; + eThread->Tcb.Header.SignalState = 1; + if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) { + // TODO: Implement KiWaitTest's relative objects usage + //KiWaitTest() + assert(0); + } + + if (GetNativeHandle(eThread->UniqueThread)) { + NtClose(eThread->UniqueThread); + eThread->UniqueThread = xbox::zeroptr; + } + + // Remove thread from the process + RemoveEntryList(&eThread->Tcb.ThreadListEntry); + eThread->Tcb.State = Terminated; KiUniqueProcess.StackCount--; + // PspReaperRoutine technically free'd the memory allocation from MmCreateKernelStack function. + // Therefore is run from another thread. + InsertTailList(&PspReaperListHead, &((PETHREAD)eThread)->ReaperLink); + KeInsertQueueDpc(&PsReaperDpc, NULL, NULL); + + EmuKeFreePcr(); + _endthreadex(ExitStatus); } diff --git a/src/core/kernel/exports/EmuKrnlPs.hpp b/src/core/kernel/exports/EmuKrnlPs.hpp new file mode 100644 index 000000000..7533803d9 --- /dev/null +++ b/src/core/kernel/exports/EmuKrnlPs.hpp @@ -0,0 +1,28 @@ +// ****************************************************************** +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * All rights reserved +// * +// ****************************************************************** +#pragma once + +namespace xbox +{ + void_xt PsInitSystem(); +}; diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index 23cbaea73..a398c7722 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -43,6 +43,7 @@ #include "core\kernel\exports\EmuKrnl.h" #include "core\kernel\exports\EmuKrnlKi.h" #include "core\kernel\exports\EmuKrnlKe.h" +#include "core\kernel\exports\EmuKrnlPs.hpp" #include "EmuShared.h" #include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For CxbxInitWindow, EmuD3DInit #include "core\hle\DSOUND\DirectSound\DirectSound.hpp" // For CxbxInitAudio @@ -1250,6 +1251,10 @@ static void CxbxrKrnlInitHacks() void(*Entry)(), int BootFlags) { + unsigned Host2XbStackBaseReserved = 0; + __asm mov Host2XbStackBaseReserved, esp; + unsigned Host2XbStackSizeReserved = EmuGenerateStackSize(Host2XbStackBaseReserved, 0); + __asm sub esp, Host2XbStackSizeReserved; // Set windows timer period to 1ms // Windows will automatically restore this value back to original on program exit // But with this, we can replace some busy loops with sleeps. @@ -1404,10 +1409,11 @@ static void CxbxrKrnlInitHacks() // Create a kpcr for this thread. This is necessary because ObInitSystem needs to access the irql. This must also be done before // CxbxInitWindow because that function creates the xbox EmuUpdateTickCount thread - EmuGenerateFS(nullptr, nullptr, xbox::zeroptr); + EmuGenerateFS(xbox::zeroptr, Host2XbStackBaseReserved, Host2XbStackSizeReserved); if (!xbox::ObInitSystem()) { CxbxrKrnlAbortEx(LOG_PREFIX_INIT, "Unable to intialize ObInitSystem."); } + xbox::PsInitSystem(); xbox::KiInitSystem(); // initialize graphics @@ -1507,7 +1513,8 @@ static void CxbxrKrnlInitHacks() xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE); - EmuKeFreePcr(); + EmuKeFreePcr(); + __asm add esp, Host2XbStackSizeReserved; // This will wait forever std::condition_variable cv; diff --git a/src/core/kernel/support/EmuFS.cpp b/src/core/kernel/support/EmuFS.cpp index 7fe3fca2a..0e10bfbd2 100644 --- a/src/core/kernel/support/EmuFS.cpp +++ b/src/core/kernel/support/EmuFS.cpp @@ -115,10 +115,8 @@ // = 0x104/260 */ LIST_ENTRY ThreadListEntry; // = 0x10C/268 */ UCHAR _padding[4]; -template void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread); -template void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread); -template void EmuKeFreePcr(); -template void EmuKeFreePcr(); +template void EmuGenerateFS(xbox::PETHREAD Ethread, unsigned XboxStackBaseReserved, unsigned XboxStackSizeReserved); +template void EmuGenerateFS(xbox::PETHREAD Ethread, unsigned XboxStackBaseReserved, unsigned XboxStackSizeReserved); NT_TIB *GetNtTib() { @@ -194,61 +192,16 @@ void EmuKeSetPcr(xbox::KPCR *Pcr) __writefsdword(TIB_ArbitraryDataSlot, (DWORD)Pcr); } -template void EmuKeFreePcr() { - xbox::PKPCR Pcr = EmuKeGetPcr(); - xbox::PVOID Dummy; - xbox::ulong_xt Size; - xbox::ntstatus_xt Status; - // tls can be nullptr - if (Pcr->NtTib.StackBase) { - // NOTE: the tls pointer was increased by 12 bytes to enforce the 16 bytes alignment, so adjust it to reach the correct pointer - // that was allocated by xbox::NtAllocateVirtualMemory - Dummy = static_cast(Pcr->NtTib.StackBase) - TLS_ALIGNMENT_OFFSET; - Size = xbox::zero; - Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free tls - assert(Status == X_STATUS_SUCCESS); - } - if constexpr (IsHostThread) { - // This only happens for the kernel initialization thread of cxbxr - Dummy = Pcr->Prcb->CurrentThread; - Size = xbox::zero; - Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free ethread - assert(Status == X_STATUS_SUCCESS); - } - Dummy = Pcr; - Size = xbox::zero; - Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free pcr + using namespace xbox; + PVOID Pcr = EmuKeGetPcr(); + ulong_xt Size = zero; + ntstatus_xt Status = NtFreeVirtualMemory(&Pcr, &Size, XBOX_MEM_RELEASE); // free pcr assert(Status == X_STATUS_SUCCESS); __writefsdword(TIB_ArbitraryDataSlot, NULL); } -void EmuKeFreeThread(xbox::ntstatus_xt ExitStatus) -{ - // Free all kernel resources that were allocated fo this thread - - xbox::KeEmptyQueueApc(); - - xbox::PETHREAD eThread = xbox::PspGetCurrentThread(); - - xbox::KeQuerySystemTime(&eThread->ExitTime); - eThread->Tcb.HasTerminated = 1; - - RemoveEntryList(&eThread->Tcb.ThreadListEntry); - - // Emulate our exit strategy for GetExitCodeThread - eThread->ExitStatus = ExitStatus; - eThread->Tcb.Header.SignalState = 1; - - if (GetNativeHandle(eThread->UniqueThread)) { - xbox::NtClose(eThread->UniqueThread); - eThread->UniqueThread = xbox::zeroptr; - } - - EmuKeFreePcr(); -} - __declspec(naked) void EmuFS_RefreshKPCR() { // Backup all registers, call EmuKeGetPcr and then restore all registers @@ -679,81 +632,33 @@ void EmuInitFS() EmuLogEx(CXBXR_MODULE::INIT, LOG_LEVEL::DEBUG, "Done patching FS Register Accesses\n"); } +// Get Xbox's TIB StackBase address from thread's StackBase. +xbox::PVOID EmuGetTIBStackBase(xbox::PVOID ThreadStackBase) { + xbox::addr_xt StackBaseAddr = reinterpret_cast(ThreadStackBase); + StackBaseAddr -= sizeof(xbox::FX_SAVE_AREA); + return reinterpret_cast(StackBaseAddr); +} + +// generate stack size reserved for xbox threads to write on. +xbox::dword_xt EmuGenerateStackSize(xbox::addr_xt& espBaseAddress, xbox::ulong_xt TlsDataSize) { + using namespace xbox; + dword_xt StackSize = espBaseAddress & 15; // Fix 16 byte alignment + espBaseAddress -= StackSize; + StackSize += sizeof(FX_SAVE_AREA); + TlsDataSize = ALIGN_UP(TlsDataSize, ulong_xt); + StackSize += TlsDataSize; // (optional) + StackSize += sizeof(KSTART_FRAME); + StackSize += sizeof(KSWITCHFRAME); + return StackSize; +} + // generate fs segment selector template -void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread) +void EmuGenerateFS(xbox::PETHREAD Ethread, unsigned Host2XbStackBaseReserved, unsigned Host2XbStackSizeReserved) { - void *pNewTLS = nullptr; xbox::PVOID base; xbox::ulong_xt size; - // Be aware that TLS might be absent (for example in homebrew "Wolf3d-xbox") - if (pTLS != nullptr) { - // copy global TLS to the current thread - { - uint32_t dwCopySize = 0; - uint32_t dwZeroSize = pTLS->dwSizeofZeroFill; - - if (pTLSData != NULL) { - // Make sure the TLS Start and End addresses are within Xbox virtual memory - if (pTLS->dwDataStartAddr >= XBE_MAX_VA || pTLS->dwDataEndAddr >= XBE_MAX_VA) { - // ignore - } - else { - dwCopySize = pTLS->dwDataEndAddr - pTLS->dwDataStartAddr; - } - } - - /* + HACK: extra safety padding 0x100 */ - base = xbox::zeroptr; - size = dwCopySize + dwZeroSize + 0x100 + 0xC; - xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE); - pNewTLS = (void*)base; - xbox::RtlZeroMemory(pNewTLS, dwCopySize + dwZeroSize + 0x100 + 0xC); - /* Skip the first 12 bytes so that TLSData will be 16 byte aligned (addr returned by NtAllocateVirtualMemory is 4K aligned) */ - pNewTLS = (uint8_t*)pNewTLS + TLS_ALIGNMENT_OFFSET; - - if (dwCopySize > 0) { - memcpy((uint8_t*)pNewTLS + 4, pTLSData, dwCopySize); - } - -#ifdef _DEBUG_TRACE - // dump raw TLS data - if (pNewTLS == nullptr) { - EmuLog(LOG_LEVEL::DEBUG, "TLS Non-Existant (OK)"); - } else { - EmuLog(LOG_LEVEL::DEBUG, "TLS Data Dump..."); - if (g_bPrintfOn) { - for (uint32_t v = 4; v < dwCopySize + 4; v++) {// Note : Don't dump dwZeroSize - - uint8_t *bByte = (uint8_t*)pNewTLS + v; - - if (v % 0x10 == 0) { - EmuLog(LOG_LEVEL::DEBUG, "0x%.8X:", (xbox::addr_xt)bByte); - } - - // Note : Use printf instead of EmuLog here, which prefixes with GetCurrentThreadId() : - printf(" %.2X", *bByte); - } - - printf("\n"); - } - } -#endif - } - - // prepare TLS - { - if (pTLS->dwTLSIndexAddr != 0) { - *(xbox::addr_xt*)pTLS->dwTLSIndexAddr = xbox::zero; - } - - // dword @ pTLSData := pTLSData - if (pNewTLS != nullptr) - *(void**)pNewTLS = pNewTLS; - } - } - // Allocate the xbox KPCR structure base = xbox::zeroptr; size = sizeof(xbox::KPCR); @@ -769,21 +674,26 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread) // // Once we simulate thread switching ourselves, we can update PrcbData.CurrentThread // and simplify this initialization, by using only one KPCR for the single Xbox processor. - // + // // One way to do our own (preemprive) thread-switching would be to use this technique : // http://www.eran.io/implementing-a-preemptive-kernel-within-a-single-windows-thread/ // See https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/issues/146 for more info. // Copy the Nt TIB over to the emulated TIB : + NT_TIB* hTib = GetNtTib(); { - memcpy(XbTib, GetNtTib(), sizeof(NT_TIB)); + memcpy(XbTib, hTib, sizeof(NT_TIB)); // Fixup the TIB self pointer : NewPcr->NtTib.Self = XbTib; - // Set the stack base - TODO : Verify this, doesn't look right? - NewPcr->NtTib.StackBase = pNewTLS; + // NOTE: The actual issue was TlsData was not within Host's stack which is now implemented. + // But instead of direct Host's stack, (which should not be tampered from Host's kernel stack block!) + // we allocated through inline asm to reserve xbox stack dynamically in order to have xbox's kernel stack reside in + // host's stack (in permitted function's stack usage). // Write the Xbox stack base to the Host, allows ConvertThreadToFiber to work correctly - // Test case: DOA3 + // Test case: + // * DoA2 + // * DoA3 // NOTE: This is disabled due to cause of corruption to host's TIB and // silent crash for xbox threads creation. // Test case: @@ -810,6 +720,8 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread) if constexpr (IsHostThread) { // This only happens for the kernel initialization thread of cxbxr + // Another thing to note, we do not insert into xbox's system as it will not be used in running xbox environment. + // Instead it will be sleeping until title/user make a decision what to do next. assert(Ethread == xbox::zeroptr); base = xbox::zeroptr; @@ -817,39 +729,61 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread) xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE); Ethread = (xbox::PETHREAD)base; xbox::RtlZeroMemory(Ethread, sizeof(xbox::ETHREAD)); // Clear, to prevent side-effects on random contents + // Emulate kernel stack size as we can't use exact size. + xbox::ulong_xt KernelStackSize = Host2XbStackBaseReserved - reinterpret_cast(hTib->StackLimit); + // Since the cxbxr's kernel initialization occur there, we do not create a new thread + // and therefore doesn't need to set any additional System/Start details set in the xbox's kernel stack. + xbox::KeInitializeThread( + &Ethread->Tcb, + (xbox::PVOID)Host2XbStackBaseReserved, + KernelStackSize, + xbox::zero, + xbox::zeroptr, // Unused (SystemRoutine) + xbox::zeroptr, // Unused (StartRoutine) + xbox::zeroptr, // Unused (StartContext) + xbox::zeroptr); // Unused (&KiUniqueProcess) } +#ifndef ENABLE_KTHREAD_SWITCHING + else { + // Otherwise, xbox::PsCreateSystemThreadEx is called and xbox::KeInitializeThread is already called from it. + // But we need to carry the reserved part onto host's stack to able align with xbox and host sharing the same stack in a new thread. + // Since we are using direct execution than in virtualization environment. + // Tcb.StackBase always point at the beginning of kernel stack (DOWN). + xbox::addr_xt xStackBase = reinterpret_cast(Ethread->Tcb.StackBase); + xbox::addr_xt xStackLimit = reinterpret_cast(Ethread->Tcb.StackLimit); + xbox::addr_xt xTlsData = reinterpret_cast(Ethread->Tcb.TlsData); + xbox::addr_xt xKernelStack = reinterpret_cast(Ethread->Tcb.KernelStack); + xbox::dword_xt xKernelStackSize = xStackBase - xKernelStack; + assert(xStackBase - xKernelStack <= Host2XbStackSizeReserved); + PVOID hKernelStack = reinterpret_cast(Host2XbStackBaseReserved - xKernelStackSize); + std::memcpy(hKernelStack, Ethread->Tcb.KernelStack, xKernelStackSize); + // Update TlsData address if used + if (Ethread->Tcb.TlsData) { + Ethread->Tcb.TlsData = reinterpret_cast(Host2XbStackBaseReserved - (xStackBase - xTlsData)); + } + // Set stacks addresses + Ethread->Tcb.StackBase = reinterpret_cast(Host2XbStackBaseReserved); + Ethread->Tcb.StackLimit = hTib->StackLimit; // Always point to host's StackLimit. + Ethread->Tcb.KernelStack = hKernelStack; + // We can safely delete kernel stack as there is no virtualization environment implemented. + xbox::MmDeleteKernelStack(reinterpret_cast(xStackBase), reinterpret_cast(xStackLimit)); + } +#endif // Initialize a fake PrcbData.CurrentThread { + // TODO: Do we need NtTib's overwrite in ENABLE_KTHREAD_SWITCHING usage? + // Set the stack details over to NtTib's structure. + NewPcr->NtTib.StackBase = EmuGetTIBStackBase(Ethread->Tcb.StackBase); + NewPcr->NtTib.StackLimit = Ethread->Tcb.StackLimit; // Set PrcbData.CurrentThread Prcb->CurrentThread = (xbox::PKTHREAD)Ethread; - Prcb->CurrentThread->TlsData = pNewTLS; - // Initialize APC stuff - InitializeListHead(&Prcb->CurrentThread->ApcState.ApcListHead[xbox::KernelMode]); - InitializeListHead(&Prcb->CurrentThread->ApcState.ApcListHead[xbox::UserMode]); - Prcb->CurrentThread->KernelApcDisable = 0; - Prcb->CurrentThread->ApcState.ApcQueueable = TRUE; - Prcb->CurrentThread->ApcState.Process = &KiUniqueProcess; - Prcb->CurrentThread->ApcState.Process->ThreadQuantum = KiUniqueProcess.ThreadQuantum; - // 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); - InitializeListHead(&Prcb->CurrentThread->Header.WaitListHead); - // Also initialize the timer associated with the thread - xbox::KeInitializeTimer(&Prcb->CurrentThread->Timer); - xbox::PKWAIT_BLOCK WaitBlock = &Prcb->CurrentThread->TimerWaitBlock; - WaitBlock->Object = &Prcb->CurrentThread->Timer; - WaitBlock->WaitKey = (xbox::cshort_xt)STATUS_TIMEOUT; - WaitBlock->WaitType = xbox::WaitAny; - WaitBlock->Thread = Prcb->CurrentThread; - WaitBlock->WaitListEntry.Flink = &Prcb->CurrentThread->Timer.Header.WaitListHead; - WaitBlock->WaitListEntry.Blink = &Prcb->CurrentThread->Timer.Header.WaitListHead; } // Make the KPCR struct available to EmuKeGetPcr() EmuKeSetPcr(NewPcr); - EmuLog(LOG_LEVEL::DEBUG, "Installed KPCR in TIB_ArbitraryDataSlot (with pTLS = 0x%.8X)", pTLS); + EmuLog(LOG_LEVEL::DEBUG, "Installed KPCR in TIB_ArbitraryDataSlot (with Ethread->Tcb.TlsData = 0x%.8X)", Ethread->Tcb.TlsData); _controlfp(_PC_53, _MCW_PC); // Set Precision control to 53 bits (verified setting) _controlfp(_RC_NEAR, _MCW_RC); // Set Rounding control to near (unsure about this) diff --git a/src/core/kernel/support/EmuFS.h b/src/core/kernel/support/EmuFS.h index 27da33144..69720262e 100644 --- a/src/core/kernel/support/EmuFS.h +++ b/src/core/kernel/support/EmuFS.h @@ -29,16 +29,20 @@ #include "common\xbe\Xbe.h" #include +// Get Xbox's TIB StackBase address from thread's StackBase. +xbox::PVOID EmuGetTIBStackBase(xbox::PVOID ThreadStackBase); + +// generate stack size reserved for xbox threads to write on. +// espBaseAddress will return aligned DOWN address, do not rely on return's size for offset usage. +xbox::dword_xt EmuGenerateStackSize(xbox::addr_xt& espBaseAddress, IN xbox::ulong_xt TlsDataSize); + // initialize fs segment selector emulation extern void EmuInitFS(); // generate fs segment selector template -void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread); -// free resources allocated for the thread -void EmuKeFreeThread(xbox::ntstatus_xt ExitStatus = X_STATUS_ABANDONED); +void EmuGenerateFS(xbox::PETHREAD Ethread, unsigned XboxThreadStackBaseReserved = 0, unsigned XboxThreadStackSizeReserved = 0); // free kpcr allocated for the thread -template void EmuKeFreePcr(); void EmuKeSetPcr(xbox::KPCR *Pcr); @@ -50,9 +54,4 @@ typedef struct void* functionPtr; }fs_instruction_t; -extern template void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread); -extern template void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread); -extern template void EmuKeFreePcr(); -extern template void EmuKeFreePcr(); - #endif