diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 0cbfc8e4e..af569c1fd 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -25,6 +25,8 @@ // * // ****************************************************************** +#include + #ifdef _WIN32 #include #endif @@ -34,6 +36,7 @@ #include "Timer.h" #include "common\util\CxbxUtil.h" #include "core\kernel\init\CxbxKrnl.h" +#include "core\kernel\support\EmuFS.h" #ifdef __linux__ #include #endif @@ -121,22 +124,51 @@ void Timer_Destroy(TimerObject* Timer) TimerList.erase(TimerList.begin() + index); } -// Thread that runs the timer -void ClockThread(TimerObject* Timer) +void Timer_Shutdown() { - uint64_t NewExpireTime; + unsigned i, iXboxThreads = 0; + TimerMtx.lock(); + for (i = 0; i < TimerList.size(); i++) { + TimerObject* Timer = TimerList[i]; + // We only need to terminate host threads. + if (!Timer->IsXboxTimer) { + Timer_Exit(Timer); + } + // If the thread is xbox, we need to increment for while statement check + else { + iXboxThreads++; + } + } + + // Only perform wait for host threads, otherwise xbox threads are + // already handled within xbox kernel for shutdown process. See CxbxrKrnlSuspendThreads function. + int counter = 0; + while (iXboxThreads != TimerList.size()) { + if (counter >= 8) { + break; + } + TimerMtx.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + TimerMtx.lock(); + counter++; + } + TimerList.clear(); + TimerMtx.unlock(); +} + +// Thread that runs the timer +void NTAPI ClockThread(void *TimerArg) +{ + TimerObject *Timer = static_cast(TimerArg); if (!Timer->Name.empty()) { CxbxSetThreadName(Timer->Name.c_str()); } - if (Timer->IsXboxTimer) { - InitXboxThread(); - g_AffinityPolicy->SetAffinityXbox(); - } else { + if (!Timer->IsXboxTimer) { g_AffinityPolicy->SetAffinityOther(); } - NewExpireTime = GetNextExpireTime(Timer); + uint64_t NewExpireTime = GetNextExpireTime(Timer); while (true) { if (GetTime_NS(Timer) > NewExpireTime) { @@ -185,7 +217,13 @@ TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool Is void Timer_Start(TimerObject* Timer, uint64_t Expire_MS) { Timer->ExpireTime_MS.store(Expire_MS); - std::thread(ClockThread, Timer).detach(); + if (Timer->IsXboxTimer) { + xbox::HANDLE hThread; + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, ClockThread, Timer, FALSE); + } + else { + std::thread(ClockThread, Timer).detach(); + } } // Retrives the frequency of the high resolution clock of the host diff --git a/src/common/Timer.h b/src/common/Timer.h index b99bb93d9..007c75536 100644 --- a/src/common/Timer.h +++ b/src/common/Timer.h @@ -63,6 +63,7 @@ void Timer_Exit(TimerObject* Timer); void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms); uint64_t GetTime_NS(TimerObject* Timer); void Timer_Init(); +void Timer_Shutdown(); int64_t Timer_GetScaledPerformanceCounter(int64_t Period); diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 86894a588..cf502d96c 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -36,6 +36,8 @@ #include "CxbxVersion.h" #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" +#include "core\kernel\support\EmuFS.h" +#include "core\kernel\support\NativeHandle.h" #include "EmuShared.h" #include "..\FixedFunctionState.h" #include "core\hle\D3D8\ResourceTracker.h" @@ -220,7 +222,7 @@ static xbox::dword_xt *g_Xbox_D3DDevice; // TODO: This should b // Static Function(s) static DWORD WINAPI EmuRenderWindow(LPVOID); static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); -static DWORD WINAPI EmuUpdateTickCount(LPVOID); +static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg); static inline void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD D3DUsage, int iTextureStage, DWORD dwSize); static void UpdateCurrentMSpFAndFPS(); // Used for benchmarking/fps count static void CxbxImpl_SetRenderTarget(xbox::X_D3DSurface *pRenderTarget, xbox::X_D3DSurface *pNewZStencil); @@ -626,15 +628,15 @@ void CxbxInitWindow(bool bFullInit) CxbxKrnl_hEmuParent = NULL; // create timing thread - if (bFullInit) + if (bFullInit && !bLLE_GPU) { - HANDLE hThread = CreateThread(nullptr, 0, EmuUpdateTickCount, nullptr, 0, nullptr); + xbox::HANDLE hThread; + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, EmuUpdateTickCount, xbox::zeroptr, FALSE); // We set the priority of this thread a bit higher, to assure reliable timing : - SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL); - g_AffinityPolicy->SetAffinityOther(hThread); - - CxbxKrnlRegisterThread(hThread); - CloseHandle(hThread); // CxbxKrnlRegisterThread duplicates the handle so we can close this one + auto nativeHandle = GetNativeHandle(hThread); + assert(nativeHandle); + SetThreadPriority(*nativeHandle, THREAD_PRIORITY_ABOVE_NORMAL); + g_AffinityPolicy->SetAffinityOther(*nativeHandle); } /* TODO : Port this Dxbx code : @@ -757,7 +759,7 @@ static void CxbxUpdateCursor(bool forceShow = false) { return; } - if (g_renderbase->IsImGuiFocus() || forceShow) { + if (g_renderbase && g_renderbase->IsImGuiFocus() || forceShow) { if (cursorInfo.flags == 0) { ShowCursor(TRUE); } @@ -1869,8 +1871,6 @@ extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg // rendering window message procedure static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - static bool bAutoPaused = false; - const LRESULT imguiResult = ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam); if (imguiResult != 0) return imguiResult; @@ -2037,27 +2037,10 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar { switch(wParam) { - case SIZE_RESTORED: - case SIZE_MAXIMIZED: - { - if(bAutoPaused) - { - bAutoPaused = false; - CxbxKrnlResume(); - } - } - break; - case SIZE_MINIMIZED: { if(g_XBVideo.bFullScreen) CxbxrKrnlAbort(nullptr); - - if(!g_bEmuSuspended) - { - bAutoPaused = true; - CxbxKrnlSuspend(); - } } break; } @@ -2162,20 +2145,12 @@ std::chrono::steady_clock::time_point GetNextVBlankTime() } // timing thread procedure -static DWORD WINAPI EmuUpdateTickCount(LPVOID) +static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg) { CxbxSetThreadName("Cxbx Timing Thread"); - // since callbacks come from here - InitXboxThread(); - EmuLog(LOG_LEVEL::DEBUG, "Timing thread is running."); - // We check for LLE flag as NV2A handles it's own VBLANK if LLE is enabled! - if (bLLE_GPU) { - return 0; - } - auto nextVBlankTime = GetNextVBlankTime(); while(true) diff --git a/src/core/hle/Patches.cpp b/src/core/hle/Patches.cpp index c341fe3e6..3236914b2 100644 --- a/src/core/hle/Patches.cpp +++ b/src/core/hle/Patches.cpp @@ -354,19 +354,19 @@ std::map g_PatchTable = { PATCH_ENTRY("ConvertThreadToFiber", xbox::EMUPATCH(ConvertThreadToFiber), PATCH_IS_FIBER), PATCH_ENTRY("CreateFiber", xbox::EMUPATCH(CreateFiber), PATCH_IS_FIBER), PATCH_ENTRY("DeleteFiber", xbox::EMUPATCH(DeleteFiber), PATCH_IS_FIBER), - PATCH_ENTRY("GetExitCodeThread", xbox::EMUPATCH(GetExitCodeThread), PATCH_ALWAYS), - PATCH_ENTRY("GetThreadPriority", xbox::EMUPATCH(GetThreadPriority), PATCH_ALWAYS), + //PATCH_ENTRY("GetExitCodeThread", xbox::EMUPATCH(GetExitCodeThread), PATCH_ALWAYS), + //PATCH_ENTRY("GetThreadPriority", xbox::EMUPATCH(GetThreadPriority), PATCH_ALWAYS), PATCH_ENTRY("OutputDebugStringA", xbox::EMUPATCH(OutputDebugStringA), PATCH_ALWAYS), //PATCH_ENTRY("RaiseException", xbox::EMUPATCH(RaiseException), PATCH_ALWAYS), - PATCH_ENTRY("SetThreadPriority", xbox::EMUPATCH(SetThreadPriority), PATCH_ALWAYS), - PATCH_ENTRY("SetThreadPriorityBoost", xbox::EMUPATCH(SetThreadPriorityBoost), PATCH_ALWAYS), + //PATCH_ENTRY("SetThreadPriority", xbox::EMUPATCH(SetThreadPriority), PATCH_ALWAYS), + //PATCH_ENTRY("SetThreadPriorityBoost", xbox::EMUPATCH(SetThreadPriorityBoost), PATCH_ALWAYS), PATCH_ENTRY("SignalObjectAndWait", xbox::EMUPATCH(SignalObjectAndWait), PATCH_ALWAYS), PATCH_ENTRY("SwitchToFiber", xbox::EMUPATCH(SwitchToFiber), PATCH_IS_FIBER), PATCH_ENTRY("XMountMUA", xbox::EMUPATCH(XMountMUA), PATCH_ALWAYS), PATCH_ENTRY("XMountMURootA", xbox::EMUPATCH(XMountMURootA), PATCH_ALWAYS), - PATCH_ENTRY("XSetProcessQuantumLength", xbox::EMUPATCH(XSetProcessQuantumLength), PATCH_ALWAYS), - PATCH_ENTRY("timeKillEvent", xbox::EMUPATCH(timeKillEvent), PATCH_ALWAYS), - PATCH_ENTRY("timeSetEvent", xbox::EMUPATCH(timeSetEvent), PATCH_ALWAYS), + //PATCH_ENTRY("XSetProcessQuantumLength", xbox::EMUPATCH(XSetProcessQuantumLength), PATCH_ALWAYS), + //PATCH_ENTRY("timeKillEvent", xbox::EMUPATCH(timeKillEvent), PATCH_ALWAYS), + //PATCH_ENTRY("timeSetEvent", xbox::EMUPATCH(timeSetEvent), PATCH_ALWAYS), PATCH_ENTRY("XReadMUMetaData", xbox::EMUPATCH(XReadMUMetaData), PATCH_ALWAYS), PATCH_ENTRY("XUnmountMU", xbox::EMUPATCH(XUnmountMU), PATCH_ALWAYS), }; diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index f9997f7eb..82a360e97 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -38,7 +38,9 @@ #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" #include "core\hle\Intercept.hpp" #include "Windef.h" @@ -895,177 +897,6 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(XInputSetLightgunCalibration) RETURN(ret); } -// ****************************************************************** -// * patch: SetThreadPriorityBoost -// ****************************************************************** -xbox::bool_xt WINAPI xbox::EMUPATCH(SetThreadPriorityBoost) -( - HANDLE hThread, - bool_xt DisablePriorityBoost -) -{ - - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(hThread) - LOG_FUNC_ARG(DisablePriorityBoost) - LOG_FUNC_END; - - BOOL bRet = SetThreadPriorityBoost(hThread, DisablePriorityBoost); - - if(bRet == FALSE) - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost Failed!"); - - RETURN(bRet); -} - -// ****************************************************************** -// * patch: SetThreadPriority -// ****************************************************************** -xbox::bool_xt WINAPI xbox::EMUPATCH(SetThreadPriority) -( - HANDLE hThread, - int nPriority -) -{ - - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(hThread) - LOG_FUNC_ARG(nPriority) - LOG_FUNC_END; - - BOOL bRet = SetThreadPriority(hThread, nPriority); - - if(bRet == FALSE) - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority Failed!"); - - RETURN(bRet); -} - - -// ****************************************************************** -// * patch: GetThreadPriority -// ****************************************************************** -int WINAPI xbox::EMUPATCH(GetThreadPriority) -( - HANDLE hThread -) -{ - - - LOG_FUNC_ONE_ARG(hThread); - - int iRet = GetThreadPriority(hThread); - - if(iRet == THREAD_PRIORITY_ERROR_RETURN) - EmuLog(LOG_LEVEL::WARNING, "GetThreadPriority Failed!"); - - RETURN(iRet); -} - -// ****************************************************************** -// * patch: GetExitCodeThread -// ****************************************************************** -xbox::bool_xt WINAPI xbox::EMUPATCH(GetExitCodeThread) -( - HANDLE hThread, - LPDWORD lpExitCode -) -{ - - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(hThread) - LOG_FUNC_ARG(lpExitCode) - LOG_FUNC_END; - - BOOL bRet = GetExitCodeThread(hThread, (::LPDWORD)lpExitCode); - - RETURN(bRet); -} - -// ****************************************************************** -// * patch: XapiThreadStartup -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(XapiThreadStartup) -( - dword_xt dwDummy1, - dword_xt dwDummy2 -) -{ - LOG_FUNC_BEGIN - LOG_FUNC_ARG(dwDummy1) - LOG_FUNC_ARG(dwDummy2) - LOG_FUNC_END; - - typedef int (__stdcall *pfDummyFunc)(dword_xt dwDummy); - - pfDummyFunc func = (pfDummyFunc)dwDummy1; - - func(dwDummy2); - - // TODO: Call thread notify routines ? - - /* - __asm - { - push dwDummy2 - call dwDummy1 - } - */ - - //_asm int 3; -} - -// ****************************************************************** -// * patch: XRegisterThreadNotifyRoutine -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(XRegisterThreadNotifyRoutine) -( - PXTHREAD_NOTIFICATION pThreadNotification, - bool_xt fRegister -) -{ - LOG_FUNC_BEGIN - LOG_FUNC_ARG(pThreadNotification) - LOG_FUNC_ARG(fRegister) - LOG_FUNC_END; - - if(fRegister) - { - // I honestly don't expect this to happen, but if it does... - if(g_iThreadNotificationCount >= 16) - CxbxrKrnlAbort("Too many thread notification routines installed\n"); - - // Find an empty spot in the thread notification array - for(int i = 0; i < 16; i++) - { - // If we find one, then add it to the array, and break the loop so - // that we don't accidently register the same routine twice! - if(g_pfnThreadNotification[i] == NULL) - { - g_pfnThreadNotification[i] = (PVOID)pThreadNotification->pfnNotifyRoutine; - g_iThreadNotificationCount++; - break; - } - } - } - else - { - // Go through each routine and nullify the routine passed in. - for(int i = 0; i < 16; i++) - { - if(pThreadNotification->pfnNotifyRoutine == g_pfnThreadNotification[i]) - { - g_pfnThreadNotification[i] = NULL; - g_iThreadNotificationCount--; - break; - } - } - } -} - typedef struct { LPFIBER_START_ROUTINE lpStartRoutine; LPVOID lpParameter; @@ -1148,214 +979,6 @@ xbox::LPVOID WINAPI xbox::EMUPATCH(ConvertThreadToFiber) RETURN(pRet); } - -// ****************************************************************** -// * patch: QueueUserAPC -// ****************************************************************** -xbox::dword_xt WINAPI xbox::EMUPATCH(QueueUserAPC) -( - PAPCFUNC pfnAPC, - HANDLE hThread, - dword_xt dwData -) -{ - LOG_FUNC_BEGIN - LOG_FUNC_ARG_TYPE(PVOID, pfnAPC) - LOG_FUNC_ARG(hThread) - LOG_FUNC_ARG(dwData) - LOG_FUNC_END; - - dword_xt dwRet = 0; - - // If necessary, we can just continue to emulate NtQueueApcThread (0xCE). - // I added this because NtQueueApcThread fails in Metal Slug 3. - - HANDLE hApcThread = NULL; - if(!DuplicateHandle(g_CurrentProcessHandle, hThread, g_CurrentProcessHandle, &hApcThread, THREAD_SET_CONTEXT,FALSE,0)) - EmuLog(LOG_LEVEL::WARNING, "DuplicateHandle failed!"); - - dwRet = QueueUserAPC(pfnAPC, hApcThread, dwData); - if(!dwRet) - EmuLog(LOG_LEVEL::WARNING, "QueueUserAPC failed!"); - - RETURN(dwRet); -} - -#if 0 // Handled by WaitForSingleObject -// ****************************************************************** -// * patch: GetOverlappedResult -// ****************************************************************** -xbox::bool_xt WINAPI xbox::EMUPATCH(GetOverlappedResult) -( - HANDLE hFile, - LPOVERLAPPED lpOverlapped, - LPDWORD lpNumberOfBytesTransferred, - bool_xt bWait -) -{ - LOG_FUNC_BEGIN - LOG_FUNC_ARG(hFile) - LOG_FUNC_ARG(lpOverlapped) - LOG_FUNC_ARG(lpNumberOfBytesTransferred) - LOG_FUNC_ARG(bWait) - LOG_FUNC_END; - - BOOL bRet = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait ); - -// if(bWait) -// bRet = TRUE; // Sucker... - - RETURN(bRet); -} -#endif - -// ****************************************************************** -// * patch: XLaunchNewImageA -// ****************************************************************** -xbox::dword_xt WINAPI xbox::EMUPATCH(XLaunchNewImageA) -( - LPCSTR lpTitlePath, - PLAUNCH_DATA pLaunchData -) -{ - // Note : This can be tested using "Innocent tears", - // which relaunches different xbes between scenes; - // One for menus, one for fmvs, etc. - // - // Other titles do this too (like "DOA2 Ultimate", - // and probably "Panzer Dragoon Orta"), but these - // titles don't come this far as-of yet. - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(lpTitlePath) - LOG_FUNC_ARG(pLaunchData) - LOG_FUNC_END; - - // TODO : This patch can be removed once NtOpenSymbolicLinkObject() - // and NtQuerySymbolicLinkObject() work together correctly. - // Also, XLaunchNewImageA() depends on XeImageHeader() and uses - // XWriteTitleInfoAndReboot() and indirectly XWriteTitleInfoNoReboot() - - // Update the kernel's LaunchDataPage : - { - if (xbox::LaunchDataPage == xbox::zeroptr) - { - PVOID LaunchDataVAddr = xbox::MmAllocateContiguousMemory(sizeof(xbox::LAUNCH_DATA_PAGE)); - if (!LaunchDataVAddr) - { - RETURN(X_STATUS_NO_MEMORY); - } - xbox::LaunchDataPage = (xbox::LAUNCH_DATA_PAGE*)LaunchDataVAddr; - } - - xbox::LaunchDataPage->Header.dwTitleId = g_pCertificate->dwTitleId; - xbox::LaunchDataPage->Header.dwFlags = 0; // TODO : What to put in here? - xbox::LaunchDataPage->Header.dwLaunchDataType = LDT_TITLE; - - xbox::MmPersistContiguousMemory((PVOID)xbox::LaunchDataPage, PAGE_SIZE, TRUE); - - if (pLaunchData != xbox::zeroptr) - // Save the launch data - memcpy(&(xbox::LaunchDataPage->LaunchData[0]), pLaunchData, sizeof(LAUNCH_DATA)); - - if (lpTitlePath == xbox::zeroptr) - { - // If no path is specified, then the xbe is rebooting to dashboard - char szDashboardPath[xbox::max_path] = { 0 }; - XboxDevice* rootDevice = CxbxDeviceByDevicePath(DeviceHarddisk0Partition2); - if (rootDevice != nullptr) - sprintf(szDashboardPath, "%s\\xboxdash.xbe", rootDevice->HostDevicePath.c_str()); - - if (PathFileExists(szDashboardPath)) - { - PopupInfo(nullptr, "The title is rebooting to dashboard"); - lpTitlePath = "C:\\xboxdash.xbe"; - xbox::LaunchDataPage->Header.dwLaunchDataType = LDT_FROM_DASHBOARD; - // Other options include LDT_NONE, LDT_FROM_DEBUGGER_CMDLINE and LDT_FROM_UPDATE - } - else - CxbxrKrnlAbort("The xbe rebooted to Dashboard and xboxdash.xbe could not be found"); - } - - strncpy(&(xbox::LaunchDataPage->Header.szLaunchPath[0]), lpTitlePath, 520); - } - - // Note : While this patch exists, HalReturnToFirmware() calls - // MmPersistContiguousMemory on LaunchDataPage. When this - // patch on XLaunchNewImageA is removed, remove the call to - // MmPersistContiguousMemory from HalReturnToFirmware() too!! - - xbox::HalReturnToFirmware(xbox::ReturnFirmwareQuickReboot); - - // If this function succeeds, it doesn't get a chance to return anything. - RETURN(ERROR_GEN_FAILURE); -} - -#if 0 // patch disabled -// ****************************************************************** -// * patch: XGetLaunchInfo -// ****************************************************************** -xbox::dword_xt WINAPI xbox::EMUPATCH(XGetLaunchInfo) -( - PDWORD pdwLaunchDataType, - PLAUNCH_DATA pLaunchData -) -{ - - - // TODO : This patch can be removed once we're sure all XAPI library - // functions indirectly reference our xbox::LaunchDataPage variable. - // For this, we need a test-case that hits this function, and run that - // with and without this patch enabled. Behavior should be identical. - // When this is verified, this patch can be removed. - LOG_TEST_CASE("Unpatching test needed"); - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(pdwLaunchDataType) - LOG_FUNC_ARG(pLaunchData) - LOG_FUNC_END; - - dword_xt ret = ERROR_NOT_FOUND; - - if (xbox::LaunchDataPage != NULL) - { - // Note : Here, CxbxRestoreLaunchDataPage() was already called, - // which has loaded LaunchDataPage from a binary file (if present). - - // A title can pass data only to itself, not another title (unless started from the dashboard, of course) : - if ( (xbox::LaunchDataPage->Header.dwTitleId == g_pCertificate->dwTitleId) - || (xbox::LaunchDataPage->Header.dwLaunchDataType == LDT_FROM_DASHBOARD) - || (xbox::LaunchDataPage->Header.dwLaunchDataType == LDT_FROM_DEBUGGER_CMDLINE)) - { - *pdwLaunchDataType = xbox::LaunchDataPage->Header.dwLaunchDataType; - memcpy(pLaunchData, &(xbox::LaunchDataPage->LaunchData[0]), sizeof(LAUNCH_DATA)); - - // Now that LaunchDataPage is retrieved by the emulated software, free it : - MmFreeContiguousMemory(xbox::LaunchDataPage); - xbox::LaunchDataPage = NULL; - - ret = ERROR_SUCCESS; - } - } - - RETURN(ret); -} -#endif - -// ****************************************************************** -// * patch: XSetProcessQuantumLength -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(XSetProcessQuantumLength) -( - dword_xt dwMilliseconds -) -{ - - LOG_FUNC_ONE_ARG(dwMilliseconds); - - // TODO: Implement? - LOG_IGNORED(); -} // ****************************************************************** // * patch: SignalObjectAndWait @@ -1368,7 +991,6 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) bool_xt bAlertable ) { - LOG_FUNC_BEGIN LOG_FUNC_ARG(hObjectToSignal) LOG_FUNC_ARG(hObjectToWaitOn) @@ -1376,53 +998,25 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) LOG_FUNC_ARG(bAlertable) LOG_FUNC_END; - dword_xt dwRet = SignalObjectAndWait( hObjectToSignal, hObjectToWaitOn, dwMilliseconds, bAlertable ); + // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves + LARGE_INTEGER NewTime; + if (dwMilliseconds == INFINITE) { + NewTime.QuadPart = ~0ull; + } + else { + NewTime.QuadPart = xbox::KeQueryInterruptTime(); + NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); + } - RETURN(dwRet); -} + 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); -// ****************************************************************** -// * patch: timeSetEvent -// ****************************************************************** -MMRESULT WINAPI xbox::EMUPATCH(timeSetEvent) -( - uint_xt uDelay, - uint_xt uResolution, - LPTIMECALLBACK fptc, - dword_xt dwUser, - uint_xt fuEvent -) -{ - - - LOG_FUNC_BEGIN - LOG_FUNC_ARG(uDelay) - LOG_FUNC_ARG(uResolution) - LOG_FUNC_ARG_TYPE(PVOID, fptc) - LOG_FUNC_ARG(dwUser) - LOG_FUNC_ARG(fuEvent) - LOG_FUNC_END; - - MMRESULT Ret = timeSetEvent( uDelay, uResolution, fptc, (DWORD_PTR) dwUser, fuEvent ); - - RETURN(Ret); -} - -// ****************************************************************** -// * patch: timeKillEvent -// ****************************************************************** -MMRESULT WINAPI xbox::EMUPATCH(timeKillEvent) -( - uint_xt uTimerID -) -{ - - - LOG_FUNC_ONE_ARG(uTimerID); - - MMRESULT Ret = timeKillEvent( uTimerID ); - - RETURN(Ret); + RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret); } // ****************************************************************** diff --git a/src/core/hle/XAPI/Xapi.cpp.unused-patches b/src/core/hle/XAPI/Xapi.cpp.unused-patches new file mode 100644 index 000000000..11c317c73 --- /dev/null +++ b/src/core/hle/XAPI/Xapi.cpp.unused-patches @@ -0,0 +1,393 @@ +xbox::bool_xt WINAPI xbox::EMUPATCH(SetThreadPriorityBoost) +( + HANDLE hThread, + bool_xt DisablePriorityBoost +) +{ + + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(hThread) + LOG_FUNC_ARG(DisablePriorityBoost) + LOG_FUNC_END; + + if (const auto &nativeHandle = GetNativeHandle(hThread)) { + BOOL bRet = SetThreadPriorityBoost(*nativeHandle, DisablePriorityBoost); + if (bRet == FALSE) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost Failed!"); + } + RETURN(bRet); + } + else { + RETURN(0); + } +} + +xbox::bool_xt WINAPI xbox::EMUPATCH(SetThreadPriority) +( + HANDLE hThread, + int nPriority +) +{ + + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(hThread) + LOG_FUNC_ARG(nPriority) + LOG_FUNC_END; + + if (const auto &nativeHandle = GetNativeHandle(hThread)) { + BOOL bRet = SetThreadPriority(*nativeHandle, nPriority); + if (bRet == FALSE) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority Failed!"); + } + RETURN(bRet); + } + else { + RETURN(0); + } +} + +int WINAPI xbox::EMUPATCH(GetThreadPriority) +( + HANDLE hThread +) +{ + + + LOG_FUNC_ONE_ARG(hThread); + + if (const auto &nativeHandle = GetNativeHandle(hThread)) { + int iRet = GetThreadPriority(*nativeHandle); + if (iRet == THREAD_PRIORITY_ERROR_RETURN) { + EmuLog(LOG_LEVEL::WARNING, "GetThreadPriority Failed!"); + } + RETURN(iRet); + } + else { + RETURN(THREAD_PRIORITY_ERROR_RETURN); + } +} + +xbox::bool_xt WINAPI xbox::EMUPATCH(GetExitCodeThread) +( + HANDLE hThread, + LPDWORD lpExitCode +) +{ + + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(hThread) + LOG_FUNC_ARG(lpExitCode) + LOG_FUNC_END; + + if (const auto &nativeHandle = GetNativeHandle(hThread)) { + RETURN(GetExitCodeThread(*nativeHandle, (::LPDWORD)lpExitCode)); + } + else { + RETURN(0); + } +} + +xbox::void_xt WINAPI xbox::EMUPATCH(XapiThreadStartup) +( + dword_xt dwDummy1, + dword_xt dwDummy2 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(dwDummy1) + LOG_FUNC_ARG(dwDummy2) + LOG_FUNC_END; + + typedef int (__stdcall *pfDummyFunc)(dword_xt dwDummy); + + pfDummyFunc func = (pfDummyFunc)dwDummy1; + + func(dwDummy2); + + // TODO: Call thread notify routines ? + + /* + __asm + { + push dwDummy2 + call dwDummy1 + } + */ + + //_asm int 3; +} + +xbox::void_xt WINAPI xbox::EMUPATCH(XRegisterThreadNotifyRoutine) +( + PXTHREAD_NOTIFICATION pThreadNotification, + bool_xt fRegister +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(pThreadNotification) + LOG_FUNC_ARG(fRegister) + LOG_FUNC_END; + + if(fRegister) + { + // I honestly don't expect this to happen, but if it does... + if(g_iThreadNotificationCount >= 16) + CxbxrKrnlAbort("Too many thread notification routines installed\n"); + + // Find an empty spot in the thread notification array + for(int i = 0; i < 16; i++) + { + // If we find one, then add it to the array, and break the loop so + // that we don't accidently register the same routine twice! + if(g_pfnThreadNotification[i] == NULL) + { + g_pfnThreadNotification[i] = (PVOID)pThreadNotification->pfnNotifyRoutine; + g_iThreadNotificationCount++; + break; + } + } + } + else + { + // Go through each routine and nullify the routine passed in. + for(int i = 0; i < 16; i++) + { + if(pThreadNotification->pfnNotifyRoutine == g_pfnThreadNotification[i]) + { + g_pfnThreadNotification[i] = NULL; + g_iThreadNotificationCount--; + break; + } + } + } +} + +xbox::dword_xt WINAPI xbox::EMUPATCH(QueueUserAPC) +( + PAPCFUNC pfnAPC, + HANDLE hThread, + dword_xt dwData +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG_TYPE(PVOID, pfnAPC) + LOG_FUNC_ARG(hThread) + LOG_FUNC_ARG(dwData) + LOG_FUNC_END; + + dword_xt dwRet = 0; + + // If necessary, we can just continue to emulate NtQueueApcThread (0xCE). + // I added this because NtQueueApcThread fails in Metal Slug 3. + + HANDLE hApcThread = NULL; + if(!DuplicateHandle(g_CurrentProcessHandle, hThread, g_CurrentProcessHandle, &hApcThread, THREAD_SET_CONTEXT,FALSE,0)) + EmuLog(LOG_LEVEL::WARNING, "DuplicateHandle failed!"); + + dwRet = QueueUserAPC(pfnAPC, hApcThread, dwData); + if(!dwRet) + EmuLog(LOG_LEVEL::WARNING, "QueueUserAPC failed!"); + + RETURN(dwRet); +} + +xbox::bool_xt WINAPI xbox::EMUPATCH(GetOverlappedResult) +( + HANDLE hFile, + LPOVERLAPPED lpOverlapped, + LPDWORD lpNumberOfBytesTransferred, + bool_xt bWait +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(hFile) + LOG_FUNC_ARG(lpOverlapped) + LOG_FUNC_ARG(lpNumberOfBytesTransferred) + LOG_FUNC_ARG(bWait) + LOG_FUNC_END; + + BOOL bRet = GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait ); + +// if(bWait) +// bRet = TRUE; // Sucker... + + RETURN(bRet); +} + +xbox::dword_xt WINAPI xbox::EMUPATCH(XLaunchNewImageA) +( + LPCSTR lpTitlePath, + PLAUNCH_DATA pLaunchData +) +{ + // Note : This can be tested using "Innocent tears", + // which relaunches different xbes between scenes; + // One for menus, one for fmvs, etc. + // + // Other titles do this too (like "DOA2 Ultimate", + // and probably "Panzer Dragoon Orta"), but these + // titles don't come this far as-of yet. + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(lpTitlePath) + LOG_FUNC_ARG(pLaunchData) + LOG_FUNC_END; + + // TODO : This patch can be removed once NtOpenSymbolicLinkObject() + // and NtQuerySymbolicLinkObject() work together correctly. + // Also, XLaunchNewImageA() depends on XeImageHeader() and uses + // XWriteTitleInfoAndReboot() and indirectly XWriteTitleInfoNoReboot() + + // Update the kernel's LaunchDataPage : + { + if (xbox::LaunchDataPage == xbox::zeroptr) + { + PVOID LaunchDataVAddr = xbox::MmAllocateContiguousMemory(sizeof(xbox::LAUNCH_DATA_PAGE)); + if (!LaunchDataVAddr) + { + RETURN(X_STATUS_NO_MEMORY); + } + xbox::LaunchDataPage = (xbox::LAUNCH_DATA_PAGE*)LaunchDataVAddr; + } + + xbox::LaunchDataPage->Header.dwTitleId = g_pCertificate->dwTitleId; + xbox::LaunchDataPage->Header.dwFlags = 0; // TODO : What to put in here? + xbox::LaunchDataPage->Header.dwLaunchDataType = LDT_TITLE; + + xbox::MmPersistContiguousMemory((PVOID)xbox::LaunchDataPage, PAGE_SIZE, TRUE); + + if (pLaunchData != xbox::zeroptr) + // Save the launch data + memcpy(&(xbox::LaunchDataPage->LaunchData[0]), pLaunchData, sizeof(LAUNCH_DATA)); + + if (lpTitlePath == xbox::zeroptr) + { + // If no path is specified, then the xbe is rebooting to dashboard + char szDashboardPath[xbox::max_path] = { 0 }; + XboxDevice* rootDevice = CxbxDeviceByDevicePath(DeviceHarddisk0Partition2); + if (rootDevice != nullptr) + sprintf(szDashboardPath, "%s\\xboxdash.xbe", rootDevice->HostDevicePath.c_str()); + + if (PathFileExists(szDashboardPath)) + { + PopupInfo(nullptr, "The title is rebooting to dashboard"); + lpTitlePath = "C:\\xboxdash.xbe"; + xbox::LaunchDataPage->Header.dwLaunchDataType = LDT_FROM_DASHBOARD; + // Other options include LDT_NONE, LDT_FROM_DEBUGGER_CMDLINE and LDT_FROM_UPDATE + } + else + CxbxrKrnlAbort("The xbe rebooted to Dashboard and xboxdash.xbe could not be found"); + } + + strncpy(&(xbox::LaunchDataPage->Header.szLaunchPath[0]), lpTitlePath, 520); + } + + // Note : While this patch exists, HalReturnToFirmware() calls + // MmPersistContiguousMemory on LaunchDataPage. When this + // patch on XLaunchNewImageA is removed, remove the call to + // MmPersistContiguousMemory from HalReturnToFirmware() too!! + + xbox::HalReturnToFirmware(xbox::ReturnFirmwareQuickReboot); + + // If this function succeeds, it doesn't get a chance to return anything. + RETURN(ERROR_GEN_FAILURE); +} + +xbox::dword_xt WINAPI xbox::EMUPATCH(XGetLaunchInfo) +( + PDWORD pdwLaunchDataType, + PLAUNCH_DATA pLaunchData +) +{ + + + // TODO : This patch can be removed once we're sure all XAPI library + // functions indirectly reference our xbox::LaunchDataPage variable. + // For this, we need a test-case that hits this function, and run that + // with and without this patch enabled. Behavior should be identical. + // When this is verified, this patch can be removed. + LOG_TEST_CASE("Unpatching test needed"); + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(pdwLaunchDataType) + LOG_FUNC_ARG(pLaunchData) + LOG_FUNC_END; + + dword_xt ret = ERROR_NOT_FOUND; + + if (xbox::LaunchDataPage != NULL) + { + // Note : Here, CxbxRestoreLaunchDataPage() was already called, + // which has loaded LaunchDataPage from a binary file (if present). + + // A title can pass data only to itself, not another title (unless started from the dashboard, of course) : + if ( (xbox::LaunchDataPage->Header.dwTitleId == g_pCertificate->dwTitleId) + || (xbox::LaunchDataPage->Header.dwLaunchDataType == LDT_FROM_DASHBOARD) + || (xbox::LaunchDataPage->Header.dwLaunchDataType == LDT_FROM_DEBUGGER_CMDLINE)) + { + *pdwLaunchDataType = xbox::LaunchDataPage->Header.dwLaunchDataType; + memcpy(pLaunchData, &(xbox::LaunchDataPage->LaunchData[0]), sizeof(LAUNCH_DATA)); + + // Now that LaunchDataPage is retrieved by the emulated software, free it : + MmFreeContiguousMemory(xbox::LaunchDataPage); + xbox::LaunchDataPage = NULL; + + ret = ERROR_SUCCESS; + } + } + + RETURN(ret); +} + +xbox::void_xt WINAPI xbox::EMUPATCH(XSetProcessQuantumLength) +( + dword_xt dwMilliseconds +) +{ + + LOG_FUNC_ONE_ARG(dwMilliseconds); + + // TODO: Implement? + LOG_IGNORED(); +} + +MMRESULT WINAPI xbox::EMUPATCH(timeSetEvent) +( + uint_xt uDelay, + uint_xt uResolution, + LPTIMECALLBACK fptc, + dword_xt dwUser, + uint_xt fuEvent +) +{ + + + LOG_FUNC_BEGIN + LOG_FUNC_ARG(uDelay) + LOG_FUNC_ARG(uResolution) + LOG_FUNC_ARG_TYPE(PVOID, fptc) + LOG_FUNC_ARG(dwUser) + LOG_FUNC_ARG(fuEvent) + LOG_FUNC_END; + + MMRESULT Ret = timeSetEvent( uDelay, uResolution, fptc, (DWORD_PTR) dwUser, fuEvent ); + + RETURN(Ret); +} + +MMRESULT WINAPI xbox::EMUPATCH(timeKillEvent) +( + uint_xt uTimerID +) +{ + + + LOG_FUNC_ONE_ARG(uTimerID); + + MMRESULT Ret = timeKillEvent( uTimerID ); + + RETURN(Ret); +} diff --git a/src/core/hle/XAPI/Xapi.h b/src/core/hle/XAPI/Xapi.h index 09d12856a..b0a5c1ce4 100644 --- a/src/core/hle/XAPI/Xapi.h +++ b/src/core/hle/XAPI/Xapi.h @@ -509,6 +509,7 @@ xbox::bool_xt WINAPI EMUPATCH(CloseHandle) HANDLE hObject ); +#if 0 // Handled by KeSetBasePriorityThread // ****************************************************************** // * patch: SetThreadPriority // ****************************************************************** @@ -517,7 +518,9 @@ xbox::bool_xt WINAPI EMUPATCH(SetThreadPriority) HANDLE hThread, int nPriority ); +#endif +#if 0 // Handled by KeQueryBasePriorityThread // ****************************************************************** // * patch: GetThreadPriority // ****************************************************************** @@ -525,7 +528,9 @@ int WINAPI EMUPATCH(GetThreadPriority) ( HANDLE hThread ); +#endif +#if 0 // Handled by KeSetDisableBoostThread // ****************************************************************** // * patch: SetThreadPriorityBoost // ****************************************************************** @@ -534,7 +539,9 @@ xbox::bool_xt WINAPI EMUPATCH(SetThreadPriorityBoost) HANDLE hThread, bool_xt DisablePriorityBoost ); +#endif +#if 0 // ****************************************************************** // * patch: GetExitCodeThread // ****************************************************************** @@ -543,6 +550,7 @@ xbox::bool_xt WINAPI EMUPATCH(GetExitCodeThread) HANDLE hThread, LPDWORD lpExitCode ); +#endif // ****************************************************************** // * patch: XapiThreadStartup diff --git a/src/core/kernel/common/kernel.h b/src/core/kernel/common/kernel.h index 2c1fcece4..8ba1597c0 100644 --- a/src/core/kernel/common/kernel.h +++ b/src/core/kernel/common/kernel.h @@ -408,10 +408,10 @@ XBSYSAPI EXPORTNUM(143) long_xt NTAPI KeSetBasePriorityThread IN long_xt Priority ); -XBSYSAPI EXPORTNUM(144) ulong_xt NTAPI KeSetDisableBoostThread +XBSYSAPI EXPORTNUM(144) boolean_xt NTAPI KeSetDisableBoostThread ( IN PKTHREAD Thread, - IN ulong_xt Disable + IN boolean_xt Disable ); // ****************************************************************** diff --git a/src/core/kernel/common/nt.h b/src/core/kernel/common/nt.h index 446881176..01095adc9 100644 --- a/src/core/kernel/common/nt.h +++ b/src/core/kernel/common/nt.h @@ -19,6 +19,8 @@ namespace xbox #define NtCurrentThread() ((HANDLE)-2) +#define X_MAXIMUM_WAIT_OBJECTS 64 + // ****************************************************************** // * NtAllocateVirtualMemory // ****************************************************************** @@ -261,7 +263,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/common/ps.h b/src/core/kernel/common/ps.h index e5ec71aba..0e4ce266c 100644 --- a/src/core/kernel/common/ps.h +++ b/src/core/kernel/common/ps.h @@ -14,18 +14,17 @@ #include "types.h" +#define X_THREAD_QUANTUM 60 + namespace xbox { - -#define PsGetCurrentThread() (CONTAINING_RECORD((KeGetCurrentThread()),ETHREAD,Tcb)) - // ****************************************************************** // * PsCreateSystemThread // ****************************************************************** XBSYSAPI EXPORTNUM(254) ntstatus_xt NTAPI PsCreateSystemThread ( OUT PHANDLE ThreadHandle, - OUT PDWORD ThreadId OPTIONAL, + OUT PHANDLE ThreadId OPTIONAL, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext, IN boolean_xt DebuggerThread @@ -40,7 +39,7 @@ XBSYSAPI EXPORTNUM(255) ntstatus_xt NTAPI PsCreateSystemThreadEx IN ulong_xt ThreadExtensionSize, IN ulong_xt KernelStackSize, IN ulong_xt TlsDataSize, - OUT PDWORD ThreadId OPTIONAL, + OUT PHANDLE ThreadId OPTIONAL, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext, IN boolean_xt CreateSuspended, @@ -69,7 +68,9 @@ XBSYSAPI EXPORTNUM(257) ntstatus_xt NTAPI PsSetCreateThreadNotifyRoutine // ****************************************************************** XBSYSAPI EXPORTNUM(258) void_xt NTAPI PsTerminateSystemThread(IN ntstatus_xt ExitStatus); -XBSYSAPI EXPORTNUM(259) volatile OBJECT_TYPE PsThreadObjectType; +XBSYSAPI EXPORTNUM(259) OBJECT_TYPE PsThreadObjectType; + +PETHREAD PspGetCurrentThread(); } diff --git a/src/core/kernel/common/types.h b/src/core/kernel/common/types.h index b053d70b9..bf5774d94 100644 --- a/src/core/kernel/common/types.h +++ b/src/core/kernel/common/types.h @@ -81,6 +81,7 @@ typedef void* LPSECURITY_ATTRIBUTES; #define X_STATUS_UNRECOGNIZED_MEDIA 0xC0000014L #define X_STATUS_NO_MEMORY 0xC0000017L #define X_STATUS_BUFFER_TOO_SMALL 0xC0000023L +#define X_STATUS_INVALID_PARAMETER 0xC000000DL #define X_STATUS_INVALID_PARAMETER_2 0xC00000F0L #define X_STATUS_ALERTED 0x00000101L #define X_STATUS_USER_APC 0x000000C0L @@ -104,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 @@ -1496,8 +1498,10 @@ KFLOATING_SAVE, *PKFLOATING_SAVE; // ****************************************************************** typedef enum _KOBJECTS { + EventNotificationObject = 0, EventSynchronizationObject = 1, MutantObject = 2, + ProcessObject = 3, QueueObject = 4, SemaphoreObject = 5, ThreadObject = 6, @@ -1506,13 +1510,16 @@ typedef enum _KOBJECTS ApcObject = 0x12, DpcObject = 0x13, DeviceQueueObject = 0x14, + EventPairObject = 0x15, + InterruptObject = 0x16, + ProfileObject = 0x17, } KOBJECTS, *PKOBJECTS; // ****************************************************************** // * PKNORMAL_ROUTINE // ****************************************************************** -typedef void_xt (*PKNORMAL_ROUTINE) +typedef void_xt (NTAPI *PKNORMAL_ROUTINE) ( IN PVOID NormalContext, IN PVOID SystemArgument1, @@ -1914,11 +1921,15 @@ KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD; // ****************************************************************** typedef struct _ETHREAD { - struct _KTHREAD Tcb; - uchar_xt UnknownA[0x1C]; // 0x110 - dword_xt UniqueThread; // 0x12C + struct _KTHREAD Tcb; + LARGE_INTEGER CreateTime; // 0x110 + LARGE_INTEGER ExitTime; // 0x118 + ntstatus_xt ExitStatus; // 0x120 + uchar_xt Unknown[0x8]; // 0x124 + HANDLE UniqueThread; // 0x12C } ETHREAD, *PETHREAD; +static_assert(sizeof(ETHREAD) == 0x130); // ****************************************************************** // * PCREATE_THREAD_NOTIFY_ROUTINE diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index 3b3342fd9..361d06f7f 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -29,6 +29,9 @@ #include +#include +#include "core\kernel\support\EmuFS.h" +#include "core\kernel\support\NativeHandle.h" #include #include #include @@ -119,12 +122,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 +154,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 HalpApcInterrupt + xbox::KiExecuteKernelApc(); break; case DISPATCH_LEVEL: // = 2 ExecuteDpcQueue(); @@ -213,6 +210,7 @@ const DWORD IrqlMasks[] = { 0x00000000, // IRQL 31 (HIGH_LEVEL) }; + // ****************************************************************** // * 0x0033 - InterlockedCompareExchange() // ****************************************************************** @@ -374,7 +372,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 +400,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 +448,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) { + // FIXME: this is wrong, it 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/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index 774551f80..89f1fbf80 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -27,6 +27,8 @@ #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" +#include "core\kernel\support\EmuFS.h" +#include // CONTAINING_RECORD macro // Gets the value of structure member (field - num1),given the type(MYSTRUCT, in this code) and the List_Entry head(temp, in this code) @@ -49,6 +51,8 @@ xbox::PLIST_ENTRY RemoveTailList(xbox::PLIST_ENTRY pListHead); extern xbox::LAUNCH_DATA_PAGE DefaultLaunchDataPage; extern xbox::PKINTERRUPT EmuInterruptList[MAX_BUS_INTERRUPT_LEVEL + 1]; +inline std::condition_variable g_InterruptSignal; +inline std::atomic_bool g_AnyInterruptAsserted = false; class HalSystemInterrupt { public: @@ -59,6 +63,8 @@ public: } m_Asserted = state; + g_AnyInterruptAsserted = true; + g_InterruptSignal.notify_one(); }; void Enable() { @@ -104,4 +110,44 @@ bool DisableInterrupts(); 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) +{ + // 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; + } + + 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/EmuKrnlHal.cpp b/src/core/kernel/exports/EmuKrnlHal.cpp index e20b6bcc3..d22d0552a 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 @@ -605,7 +599,6 @@ XBSYSAPI EXPORTNUM(49) xbox::void_xt DECLSPEC_NORETURN NTAPI xbox::HalReturnToFi } CxbxKrnlShutDown(is_reboot); - TerminateProcess(GetCurrentProcess(), EXIT_SUCCESS); } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 338d27288..bd811eee1 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -81,7 +81,9 @@ namespace NtDll #include "EmuKrnlKi.h" // For KiRemoveTreeTimer(), KiInsertTreeTimer() #include "EmuKrnlKe.h" #include "core\kernel\support\EmuFile.h" // For IsEmuHandle(), NtStatusToString() +#include "core\kernel\support\NativeHandle.h" #include "Timer.h" +#include "Util.h" #include #include @@ -128,24 +130,18 @@ 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; - // See EmuKeSetPcr() - Pcr = (xbox::PKPCR)__readfsdword(TIB_ArbitraryDataSlot); - - if (Pcr == nullptr) { - EmuLog(LOG_LEVEL::WARNING, "KeGetPCR returned nullptr: Was this called from a non-xbox thread?"); - // Attempt to salvage the situation by calling InitXboxThread to setup KPCR in place - InitXboxThread(); - g_AffinityPolicy->SetAffinityXbox(); - Pcr = (xbox::PKPCR)__readfsdword(TIB_ArbitraryDataSlot); - } + xbox::PKPCR Pcr = (xbox::PKPCR)__readfsdword(TIB_ArbitraryDataSlot); + + // If this fails, 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 + assert(Pcr); return Pcr; } @@ -155,7 +151,7 @@ xbox::KPCR* WINAPI KeGetPcr() // ****************************************************************** xbox::KPRCB *KeGetCurrentPrcb() { - return &(KeGetPcr()->PrcbData); + return &(EmuKeGetPcr()->PrcbData); } // ****************************************************************** @@ -265,6 +261,26 @@ xbox::void_xt NTAPI xbox::KeInitializeTimer KeInitializeTimerEx(Timer, NotificationTimer); } +// ****************************************************************** +// * KeEmptyQueueApc() +// ****************************************************************** +xbox::void_xt xbox::KeEmptyQueueApc() +{ + PKTHREAD kThread = KeGetCurrentThread(); + kThread->ApcState.ApcQueueable = FALSE; + + KiApcListMtx.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); + } + } + KiApcListMtx.unlock(); +} + // Forward KeLowerIrql() to KfLowerIrql() #define KeLowerIrql(NewIrql) \ KfLowerIrql(NewIrql) @@ -547,7 +563,30 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread LOG_FUNC_ARG(Interval) LOG_FUNC_END; - NTSTATUS ret = NtDll::NtDelayExecution(Alertable, (NtDll::LARGE_INTEGER*)Interval); + // 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; + ExpireTime.QuadPart = 0; + NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); + if (Status == 0) { // STATUS_SUCCESS + return std::nullopt; + } + return std::make_optional(Status); + }, pNewTime, Alertable, WaitMode); RETURN(ret); } @@ -597,7 +636,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); @@ -995,6 +1034,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, @@ -1010,9 +1052,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 { + KiApcListMtx.lock(); + InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry); + Apc->Inserted = TRUE; + KiApcListMtx.unlock(); + + // 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); + } + } } // ****************************************************************** @@ -1089,10 +1169,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); } } } @@ -1152,9 +1232,16 @@ XBSYSAPI EXPORTNUM(124) xbox::long_xt NTAPI xbox::KeQueryBasePriorityThread { LOG_FUNC_ONE_ARG(Thread); - LOG_UNIMPLEMENTED(); + KIRQL OldIrql; + KiLockDispatcherDatabase(&OldIrql); - RETURN(0); + // It cannot fail because all thread handles are created by ob + const auto& nativeHandle = GetNativeHandle(PspGetCurrentThread()->UniqueThread); + long_xt ret = GetThreadPriority(*nativeHandle); + + KiUnlockDispatcherDatabase(OldIrql); + + RETURN(ret); } // ****************************************************************** @@ -1572,21 +1659,31 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread LOG_FUNC_ARG_OUT(Priority) LOG_FUNC_END; - LONG ret = GetThreadPriority((HANDLE)Thread); + KIRQL oldIRQL; + KiLockDispatcherDatabase(&oldIRQL); + + // It cannot fail because all thread handles are created by ob + const auto &nativeHandle = GetNativeHandle(PspGetCurrentThread()->UniqueThread); + LONG ret = GetThreadPriority(*nativeHandle); // This would work normally, but it will slow down the emulation, // don't do that if the priority is higher then normal (so our own)! - if((Priority <= THREAD_PRIORITY_NORMAL) && ((HANDLE)Thread != GetCurrentThread())) { - SetThreadPriority((HANDLE)Thread, Priority); + if (Priority <= THREAD_PRIORITY_NORMAL) { + BOOL result = SetThreadPriority(*nativeHandle, Priority); + if (!result) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); + } } + KiUnlockDispatcherDatabase(oldIRQL); + RETURN(ret); } -XBSYSAPI EXPORTNUM(144) xbox::ulong_xt NTAPI xbox::KeSetDisableBoostThread +XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread ( IN PKTHREAD Thread, - IN ulong_xt Disable + IN boolean_xt Disable ) { LOG_FUNC_BEGIN @@ -1597,9 +1694,17 @@ XBSYSAPI EXPORTNUM(144) xbox::ulong_xt NTAPI xbox::KeSetDisableBoostThread KIRQL oldIRQL; KiLockDispatcherDatabase(&oldIRQL); - ULONG prevDisableBoost = Thread->DisableBoost; + // It cannot fail because all thread handles are created by ob + const auto &nativeHandle = GetNativeHandle(PspGetCurrentThread()->UniqueThread); + + boolean_xt prevDisableBoost = Thread->DisableBoost; Thread->DisableBoost = (CHAR)Disable; + BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable); + if (!bRet) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str()); + } + KiUnlockDispatcherDatabase(oldIRQL); RETURN(prevDisableBoost); @@ -1910,24 +2015,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() // ****************************************************************** @@ -1992,7 +2079,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects if ((ObjectMutant->Header.SignalState > 0) || (Thread == ObjectMutant->OwnerThread)) { if (ObjectMutant->Header.SignalState != MINLONG) { KiWaitSatisfyMutant(ObjectMutant, Thread); - WaitStatus = (NTSTATUS)(Thread->WaitStatus); + WaitStatus = (NTSTATUS)(Index | Thread->WaitStatus); goto NoWait; } else { @@ -2004,7 +2091,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects else if (ObjectMutant->Header.SignalState) { // Otherwise, if the signal state is > 0, we can still just satisfy the wait KiWaitSatisfyOther(ObjectMutant); - WaitStatus = X_STATUS_SUCCESS; + WaitStatus = Index; goto NoWait; } } else { @@ -2118,7 +2205,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects //WaitStatus = (NTSTATUS)KiSwapThread(); //if (WaitStatus == X_STATUS_USER_APC) { - // TODO: KiDeliverUserApc(); + // KiExecuteUserApc(); //} // If the thread was not awakened for an APC, return the Wait Status @@ -2149,7 +2236,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects // 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); @@ -2170,7 +2257,7 @@ NoWait: KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { - // TODO: KiDeliverUserApc(); + KiExecuteUserApc(); } RETURN(WaitStatus); @@ -2323,7 +2410,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject WaitStatus = (NTSTATUS)KiSwapThread(); if (WaitStatus == X_STATUS_USER_APC) { - // TODO: KiDeliverUserApc(); + KiExecuteUserApc(); } // If the thread was not awakened for an APC, return the Wait Status @@ -2354,7 +2441,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); @@ -2375,7 +2462,7 @@ NoWait: KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { - // TODO: KiDeliverUserApc(); + KiExecuteUserApc(); } RETURN(WaitStatus); diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index cca37ebd9..d92b1cb96 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -27,14 +27,16 @@ namespace xbox { - xbox::void_xt NTAPI KeSetSystemTime + void_xt NTAPI KeSetSystemTime ( IN PLARGE_INTEGER NewTime, OUT PLARGE_INTEGER OldTime ); - xbox::void_xt NTAPI KeInitializeTimer + void_xt NTAPI KeInitializeTimer ( IN PKTIMER Timer ); + + void_xt KeEmptyQueueApc(); } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 3046cf58c..2d8b5c395 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" @@ -88,22 +89,26 @@ the said software). #define ASSERT_TIMER_LOCKED assert(KiTimerMtx.Acquired > 0) +xbox::KPROCESS KiUniqueProcess; const xbox::ulong_xt CLOCK_TIME_INCREMENT = 0x2710; xbox::KDPC KiTimerExpireDpc; xbox::KI_TIMER_LOCK KiTimerMtx; xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; xbox::LIST_ENTRY KiWaitInListHead; +std::mutex xbox::KiApcListMtx; xbox::void_xt xbox::KiInitSystem() { - unsigned int i; + KiUniqueProcess.StackCount = 0; + KiUniqueProcess.ThreadQuantum = X_THREAD_QUANTUM; + InitializeListHead(&KiUniqueProcess.ThreadListHead); InitializeListHead(&KiWaitInListHead); KiTimerMtx.Acquired = 0; KeInitializeDpc(&KiTimerExpireDpc, KiTimerExpiration, NULL); - for (i = 0; i < TIMER_TABLE_SIZE; i++) { + for (unsigned i = 0; i < TIMER_TABLE_SIZE; i++) { InitializeListHead(&KiTimerTableListHead[i].Entry); KiTimerTableListHead[i].Time.u.HighPart = 0xFFFFFFFF; KiTimerTableListHead[i].Time.u.LowPart = 0; @@ -876,3 +881,67 @@ xbox::void_xt FASTCALL xbox::KiWaitSatisfyAll return; } + +template +static xbox::void_xt KiExecuteApc() +{ + xbox::PKTHREAD kThread = xbox::KeGetCurrentThread(); + + if constexpr (ApcMode == xbox::KernelMode) { + kThread->ApcState.KernelApcPending = FALSE; + } + else { + kThread->ApcState.UserApcPending = FALSE; + } + + // 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::KiApcListMtx.lock(); + while (!IsListEmpty(&kThread->ApcState.ApcListHead[ApcMode])) { + if ((ApcMode == xbox::KernelMode) && (kThread->KernelApcDisable != 0)) { + xbox::KiApcListMtx.unlock(); + return; + } + xbox::PLIST_ENTRY Entry = kThread->ApcState.ApcListHead[ApcMode].Flink; + xbox::PKAPC Apc = CONTAINING_RECORD(Entry, xbox::KAPC, ApcListEntry); + RemoveEntryList(Entry); + Apc->Inserted = FALSE; + xbox::KiApcListMtx.unlock(); + + // NOTE: we never use KernelRoutine because that is only used for kernel APCs, which we currently don't use + if (Apc->NormalRoutine != xbox::zeroptr) { + (Apc->NormalRoutine)(Apc->NormalContext, Apc->SystemArgument1, Apc->SystemArgument2); + } + + xbox::ExFreePool(Apc); + xbox::KiApcListMtx.lock(); + } + + xbox::KiApcListMtx.unlock(); +} + +xbox::void_xt xbox::KiExecuteKernelApc() +{ + KiExecuteApc(); +} + +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 159230e49..0e9dc3481 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 + extern std::mutex KiApcListMtx; - 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,18 +126,29 @@ 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(); + + PLARGE_INTEGER FASTCALL KiComputeWaitInterval + ( + IN PLARGE_INTEGER OriginalTime, + IN PLARGE_INTEGER DueTime, + IN OUT PLARGE_INTEGER NewTime + ); }; +extern xbox::KPROCESS KiUniqueProcess; extern const xbox::ulong_xt CLOCK_TIME_INCREMENT; extern xbox::LIST_ENTRY KiWaitInListHead; extern xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 0fc39687f..0c9e78e1b 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 @@ -55,12 +57,9 @@ namespace NtDll #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; - // ****************************************************************** // * 0x00B8 - NtAllocateVirtualMemory() // ****************************************************************** @@ -173,15 +172,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); - } } } @@ -719,18 +709,18 @@ XBSYSAPI EXPORTNUM(197) xbox::ntstatus_xt NTAPI xbox::NtDuplicateObject // On the xbox, the duplicated handle always has the same access rigths of the source handle const ACCESS_MASK DesiredAccess = 0; const ULONG Attributes = 0; - dword_xt nativeOptions = (Options | DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS); + const ULONG nativeOptions = (Options | DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS); if (const auto &nativeHandle = GetNativeHandle(SourceHandle)) { // This was a handle created by Ob PVOID Object; - status = ObReferenceObjectByHandle(SourceHandle, /*ObjectType=*/nullptr, &Object); + status = ObReferenceObjectByHandle(SourceHandle, zeroptr, &Object); if (X_NT_SUCCESS(status)) { if (ObpIsFlagSet(Options, DUPLICATE_CLOSE_SOURCE)) { NtClose(SourceHandle); } - status = ObOpenObjectByPointer(Object, OBJECT_TO_OBJECT_HEADER(Object)->Type, /*OUT*/TargetHandle); + status = ObOpenObjectByPointer(Object, OBJECT_TO_OBJECT_HEADER(Object)->Type, TargetHandle); if (!X_NT_SUCCESS(status)) { *TargetHandle = NULL; RETURN(status); @@ -1028,7 +1018,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 +1029,29 @@ 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. + // Test case: Metal Slug 3, possibly other SNK games too - // TODO: Use our implementation of NtDuplicateObject instead? + PETHREAD Thread; + ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast(&Thread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); + } - 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 +1124,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, @@ -1891,15 +1856,15 @@ XBSYSAPI EXPORTNUM(224) xbox::ntstatus_xt NTAPI xbox::NtResumeThread LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_END; - NTSTATUS ret = NtDll::NtResumeThread( - ThreadHandle, - (::PULONG)PreviousSuspendCount); + if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { + // Thread handles are created by ob + RETURN(NtDll::NtResumeThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); + } + else { + RETURN(X_STATUS_INVALID_HANDLE); + } // TODO : Once we do our own thread-switching, implement NtResumeThread using KetResumeThread - - //Sleep(10); - - RETURN(ret); } // ****************************************************************** @@ -2114,13 +2079,15 @@ XBSYSAPI EXPORTNUM(231) xbox::ntstatus_xt NTAPI xbox::NtSuspendThread LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_END; - NTSTATUS ret = NtDll::NtSuspendThread( - ThreadHandle, - (::PULONG)PreviousSuspendCount); + if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { + // Thread handles are created by ob + RETURN(NtDll::NtSuspendThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); + } + else { + RETURN(X_STATUS_INVALID_HANDLE); + } // TODO : Once we do our own thread-switching, implement NtSuspendThread using KeSuspendThread - - RETURN(ret); } // ****************************************************************** @@ -2223,12 +2190,52 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx LOG_FUNC_ARG(Timeout) LOG_FUNC_END; - return NtDll::NtWaitForMultipleObjects( - Count, - Handles, - (NtDll::OBJECT_WAIT_TYPE)WaitType, - Alertable, - (NtDll::PLARGE_INTEGER)Timeout); + if (!Count || (Count > X_MAXIMUM_WAIT_OBJECTS)) { + RETURN(X_STATUS_INVALID_PARAMETER); + } + + // This function can wait on thread handles, which are currently created by ob, + // so we need to check their presence in the handle array + ::HANDLE nativeHandles[X_MAXIMUM_WAIT_OBJECTS]; + for (ulong_xt i = 0; i < Count; ++i) { + if (const auto &nativeHandle = GetNativeHandle(Handles[i])) { + // This is a ob handle, so replace it with its native counterpart + nativeHandles[i] = *nativeHandle; + } + else { + nativeHandles[i] = Handles[i]; + } + } + + // 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; + ExpireTime.QuadPart = 0; + NTSTATUS Status = NtDll::NtWaitForMultipleObjects( + Count, + nativeHandles, + (NtDll::OBJECT_WAIT_TYPE)WaitType, + Alertable, + &ExpireTime); + if (Status == STATUS_TIMEOUT) { + return std::nullopt; + } + return std::make_optional(Status); + }, pNewTime, Alertable, WaitMode); + + RETURN(ret); } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlOb.cpp b/src/core/kernel/exports/EmuKrnlOb.cpp index 159d1fbe6..647bb07fa 100644 --- a/src/core/kernel/exports/EmuKrnlOb.cpp +++ b/src/core/kernel/exports/EmuKrnlOb.cpp @@ -1019,7 +1019,7 @@ XBSYSAPI EXPORTNUM(246) xbox::ntstatus_xt NTAPI xbox::ObReferenceObjectByHandle if (Handle == NtCurrentThread()) { if ((ObjectType == &PsThreadObjectType) || (ObjectType == NULL)) { - Object = PsGetCurrentThread(); + Object = PspGetCurrentThread(); ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object); InterlockedIncrement((::PLONG)(&ObjectHeader->PointerCount)); *ReturnedObject = Object; diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 2fa2f9564..0b569a2f2 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -31,14 +31,18 @@ #include // For PsCreateSystemThreadEx, etc. +#include "core\kernel\exports\EmuKrnlKi.h" +#include "core\kernel\exports\EmuKrnlKe.h" #include // For __beginthreadex(), etc. #include // For _controlfp constants #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" // prevent name collisions namespace NtDll @@ -46,46 +50,39 @@ namespace NtDll #include "core\kernel\support\EmuNtDll.h" }; -#define PSP_MAX_CREATE_THREAD_NOTIFY 16 /* TODO : Should be 8 */ +#define PSP_MAX_CREATE_THREAD_NOTIFY 8 // PsCreateSystemThread proxy parameters typedef struct _PCSTProxyParam { - IN PVOID StartRoutine; - IN PVOID StartContext; - IN PVOID SystemRoutine; + IN xbox::PVOID StartRoutine; + IN xbox::PVOID StartContext; + IN xbox::PVOID SystemRoutine; + IN xbox::PVOID Ethread; } PCSTProxyParam; -// Global Variable(s) -extern PVOID g_pfnThreadNotification[PSP_MAX_CREATE_THREAD_NOTIFY] = { NULL }; -extern int g_iThreadNotificationCount = 0; +static xbox::PCREATE_THREAD_NOTIFY_ROUTINE g_pfnThreadNotification[PSP_MAX_CREATE_THREAD_NOTIFY] = { xbox::zeroptr }; +static std::atomic_int g_iThreadNotificationCount = 0; +static std::mutex g_ThreadNotificationMtx; // Separate function for logging, otherwise in PCSTProxy __try wont work (Compiler Error C2712) void LOG_PCSTProxy ( - PVOID StartRoutine, - PVOID StartContext, - PVOID SystemRoutine + xbox::PVOID StartRoutine, + xbox::PVOID StartContext, + xbox::PVOID SystemRoutine, + xbox::PVOID Ethread ) { LOG_FUNC_BEGIN LOG_FUNC_ARG(StartRoutine) LOG_FUNC_ARG(StartContext) LOG_FUNC_ARG(SystemRoutine) + LOG_FUNC_ARG(Ethread) LOG_FUNC_END; } -// Overload which doesn't change affinity -void InitXboxThread() -{ - // initialize FS segment selector - EmuGenerateFS(CxbxKrnl_TLS, CxbxKrnl_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) -} - // PsCreateSystemThread proxy procedure // Dxbx Note : The signature of PCSTProxy should conform to System.TThreadFunc ! static unsigned int WINAPI PCSTProxy @@ -104,11 +101,11 @@ static unsigned int WINAPI PCSTProxy LOG_PCSTProxy( params.StartRoutine, params.StartContext, - params.SystemRoutine); - + params.SystemRoutine, + params.Ethread); // Do minimal thread initialization - InitXboxThread(); + EmuGenerateFS(CxbxKrnl_TLS, CxbxKrnl_TLSData, static_cast(params.Ethread)); auto routine = (xbox::PKSYSTEM_ROUTINE)params.SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a @@ -137,13 +134,30 @@ void PspSystemThreadStartup xbox::PsTerminateSystemThread(X_STATUS_SUCCESS); } +xbox::PETHREAD xbox::PspGetCurrentThread() +{ + // This works because we assign ethread to Prcb->CurrentThread + return reinterpret_cast(KeGetCurrentThread()); +} + +static xbox::void_xt PspCallThreadNotificationRoutines(xbox::PETHREAD eThread, xbox::boolean_xt Create) +{ + std::unique_lock lck(g_ThreadNotificationMtx); + for (int i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) { + if (g_pfnThreadNotification[i]) { + EmuLog(LOG_LEVEL::DEBUG, "Calling pfnNotificationRoutine[%d] (0x%.8X)", i, g_pfnThreadNotification[i]); + (*g_pfnThreadNotification[i])(eThread, eThread->UniqueThread, Create); + } + } +} + // ****************************************************************** // * 0x00FE - PsCreateSystemThread() // ****************************************************************** XBSYSAPI EXPORTNUM(254) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThread ( OUT PHANDLE ThreadHandle, - OUT PDWORD ThreadId OPTIONAL, + OUT PHANDLE ThreadId OPTIONAL, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext, IN boolean_xt DebuggerThread @@ -187,7 +201,7 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx IN ulong_xt ThreadExtensionSize, IN ulong_xt KernelStackSize, IN ulong_xt TlsDataSize, - OUT PDWORD ThreadId OPTIONAL, + OUT PHANDLE ThreadId OPTIONAL, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext, IN boolean_xt CreateSuspended, @@ -222,58 +236,75 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx // create thread, using our special proxy technique { - DWORD dwThreadId = 0; + PETHREAD eThread; + ntstatus_xt result = ObCreateObject(&PsThreadObjectType, zeroptr, sizeof(ETHREAD) + ThreadExtensionSize, reinterpret_cast(&eThread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); + } + + std::memset(eThread, 0, sizeof(ETHREAD) + ThreadExtensionSize); + + // 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)) { + ObfDereferenceObject(eThread); + RETURN(result); + } + + if (g_iThreadNotificationCount) { + PspCallThreadNotificationRoutines(eThread, TRUE); + } + + // Create another handle to pass back to the title in the ThreadHandle argument + result = ObOpenObjectByPointer(eThread, &PsThreadObjectType, ThreadHandle); + if (!X_NT_SUCCESS(result)) { + ObfDereferenceObject(eThread); + RETURN(result); + } + + if (ThreadId != zeroptr) { + *ThreadId = eThread->UniqueThread; + } // 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; - /* - // call thread notification routine(s) - if (g_iThreadNotificationCount != 0) - { - for (int i = 0; i < 16; i++) - { - // TODO: This is *very* wrong, ps notification routines are NOT the same as XApi notification routines - // TODO: XAPI notification routines are already handeld by XapiThreadStartup and don't need to be called by us - // TODO: This type of notification routine is PCREATE_THREAD_NOTIFY_ROUTINE, which takes an ETHREAD pointer as well as Thread ID as input - // TODO: This is impossible to support currently, as we do not create or register Xbox ETHREAD objects, so we're better to skip it entirely! - xbox::XTHREAD_NOTIFY_PROC pfnNotificationRoutine = (xbox::XTHREAD_NOTIFY_PROC)g_pfnThreadNotification[i]; - - // If the routine doesn't exist, don't execute it! - if (pfnNotificationRoutine == NULL) - continue; - - EmuLog(LOG_LEVEL::DEBUG, "Calling pfnNotificationRoutine[%d] (0x%.8X)", g_iThreadNotificationCount, pfnNotificationRoutine); - - pfnNotificationRoutine(TRUE); - } - }*/ - - HANDLE handle = reinterpret_cast(_beginthreadex(NULL, KernelStackSize, PCSTProxy, iPCSTProxyParam, CREATE_SUSPENDED, reinterpret_cast(&dwThreadId))); + unsigned int ThreadId; + HANDLE handle = reinterpret_cast(_beginthreadex(NULL, KernelStackSize, PCSTProxy, iPCSTProxyParam, CREATE_SUSPENDED, &ThreadId)); if (handle == NULL) { delete iPCSTProxyParam; + ObpClose(eThread->UniqueThread); + ObfDereferenceObject(eThread); RETURN(X_STATUS_INSUFFICIENT_RESOURCES); } - *ThreadHandle = handle; - if (ThreadId != NULL) - *ThreadId = dwThreadId; + + // Increment the ref count of the thread once more. This is to guard against the case the title closes the thread handle + // before this thread terminates with PsTerminateSystemThread + // Test case: Amped + 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); + RegisterXboxHandle(eThread->UniqueThread, dupHandle); g_AffinityPolicy->SetAffinityXbox(handle); - CxbxKrnlRegisterThread(handle); // Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED) if (!CreateSuspended) { ResumeThread(handle); } - // Note : DO NOT use iPCSTProxyParam anymore, since ownership is transferred to the proxy (which frees it too) - // Log ThreadID identical to how GetCurrentThreadID() is rendered : - EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X]", handle, dwThreadId); + EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X], Native Handle : 0x%X, Native ThreadId : [0x%.4X]", + *ThreadHandle, eThread->UniqueThread, handle, ThreadId); } RETURN(X_STATUS_SUCCESS); @@ -289,14 +320,13 @@ XBSYSAPI EXPORTNUM(256) xbox::ntstatus_xt NTAPI xbox::PsQueryStatistics { LOG_FUNC_ONE_ARG_OUT(ProcessStatistics); - NTSTATUS ret = X_STATUS_SUCCESS; + ntstatus_xt ret = X_STATUS_SUCCESS; if (ProcessStatistics->Length == sizeof(PS_STATISTICS)) { - LOG_INCOMPLETE(); // TODO : Return number of threads and handles that currently exist - ProcessStatistics->ThreadCount = 1; - ProcessStatistics->HandleCount = 1; + ProcessStatistics->ThreadCount = KiUniqueProcess.StackCount; + ProcessStatistics->HandleCount = ObpObjectHandleTable.HandleCount; // This currently doesn't count native handles that we use as xbox handles } else { - ret = STATUS_INVALID_PARAMETER; + ret = X_STATUS_INVALID_PARAMETER; } RETURN(ret); @@ -312,29 +342,16 @@ XBSYSAPI EXPORTNUM(257) xbox::ntstatus_xt NTAPI xbox::PsSetCreateThreadNotifyRou { LOG_FUNC_ONE_ARG(NotifyRoutine); - NTSTATUS ret = X_STATUS_INSUFFICIENT_RESOURCES; - - // Taken from xbox::EmuXRegisterThreadNotifyRoutine (perhaps that can be removed now) : - - // I honestly don't expect this to happen, but if it does... - if (g_iThreadNotificationCount >= PSP_MAX_CREATE_THREAD_NOTIFY) - CxbxrKrnlAbort("Too many thread notification routines installed\n"); - - // Find an empty spot in the thread notification array - for (int i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) - { - // If we find one, then add it to the array, and break the loop so - // that we don't accidently register the same routine twice! - if (g_pfnThreadNotification[i] == NULL) - { - g_pfnThreadNotification[i] = (PVOID)NotifyRoutine; + std::unique_lock lck(g_ThreadNotificationMtx); + for (int i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) { + if (g_pfnThreadNotification[i] == zeroptr) { + g_pfnThreadNotification[i] = NotifyRoutine; g_iThreadNotificationCount++; - ret = X_STATUS_SUCCESS; - break; + RETURN(X_STATUS_SUCCESS); } } - RETURN(ret); + RETURN(X_STATUS_INSUFFICIENT_RESOURCES); } // ****************************************************************** @@ -350,34 +367,23 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread { LOG_FUNC_ONE_ARG(ExitStatus); - /* - // call thread notification routine(s) - if (g_iThreadNotificationCount != 0) - { - for (int i = 0; i < 16; i++) - { - xbox::XTHREAD_NOTIFY_PROC pfnNotificationRoutine = (xbox::XTHREAD_NOTIFY_PROC)g_pfnThreadNotification[i]; + xbox::PETHREAD eThread = xbox::PspGetCurrentThread(); + if (eThread->UniqueThread && g_iThreadNotificationCount) { + PspCallThreadNotificationRoutines(eThread, FALSE); + } - // If the routine doesn't exist, don't execute it! - if (pfnNotificationRoutine == NULL) - continue; + EmuKeFreeThread(ExitStatus); + // Don't do this in EmuKeFreeThread because we only increment the thread ref count in PsCreateSystemThreadEx + ObfDereferenceObject(eThread); + KiUniqueProcess.StackCount--; - EmuLog(LOG_LEVEL::DEBUG, "Calling pfnNotificationRoutine[%d] (0x%.8X)", g_iThreadNotificationCount, pfnNotificationRoutine); - - pfnNotificationRoutine(FALSE); - } - }*/ - - EmuKeFreePcr(); _endthreadex(ExitStatus); - // ExitThread(ExitStatus); - // CxbxKrnlTerminateThread(); } // ****************************************************************** // * 0x0103 - PsThreadObjectType // ****************************************************************** -XBSYSAPI EXPORTNUM(259) xbox::OBJECT_TYPE VOLATILE xbox::PsThreadObjectType = +XBSYSAPI EXPORTNUM(259) xbox::OBJECT_TYPE xbox::PsThreadObjectType = { xbox::ExAllocatePoolWithTag, xbox::ExFreePool, diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index a5c963a02..23cbaea73 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -42,6 +42,7 @@ #include "EmuEEPROM.h" // For CxbxRestoreEEPROM, EEPROM, XboxFactoryGameRegion #include "core\kernel\exports\EmuKrnl.h" #include "core\kernel\exports\EmuKrnlKi.h" +#include "core\kernel\exports\EmuKrnlKe.h" #include "EmuShared.h" #include "core\hle\D3D8\Direct3D9\Direct3D9.h" // For CxbxInitWindow, EmuD3DInit #include "core\hle\DSOUND\DirectSound\DirectSound.hpp" // For CxbxInitAudio @@ -71,6 +72,8 @@ #include "common\crypto\EmuSha.h" // For the SHA1 functions #include "Timer.h" // For Timer_Init #include "common\input\InputManager.h" // For the InputDeviceManager +#include "core/kernel/support/NativeHandle.h" +#include "common/win32/Util.h" // for WinError2Str #include "common/FilePaths.hpp" @@ -93,9 +96,6 @@ DebugMode CxbxrKrnl_DebugMode = DebugMode::DM_NONE; std::string CxbxrKrnl_DebugFileName = ""; Xbe::Certificate *g_pCertificate = NULL; -/*! thread handles */ -static std::vector g_hThreads; - char szFilePath_CxbxReloaded_Exe[MAX_PATH] = { 0 }; std::string g_DataFilePath; char szFilePath_EEPROM_bin[MAX_PATH] = { 0 }; @@ -141,9 +141,11 @@ void SetupPerTitleKeys() } -void CxbxLaunchXbe(void(*Entry)()) +xbox::void_xt NTAPI CxbxLaunchXbe(xbox::PVOID Entry) { - Entry(); + EmuLogInit(LOG_LEVEL::DEBUG, "Calling XBE entry point..."); + static_cast(Entry)(); + EmuLogInit(LOG_LEVEL::DEBUG, "XBE entry point returned"); } // Entry point address XOR keys per Xbe type (Retail, Debug or Chihiro) : @@ -334,37 +336,28 @@ void InitSoftwareInterrupts() } #endif -void TriggerPendingConnectedInterrupts() -{ - for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { - // If the interrupt is pending and connected, process it - if (HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { - HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); - } - SwitchToThread(); - } -} - -static unsigned int WINAPI CxbxKrnlInterruptThread(PVOID param) +static xbox::void_xt NTAPI CxbxKrnlInterruptThread(xbox::PVOID param) { CxbxSetThreadName("CxbxKrnl Interrupts"); - // Make sure Xbox1 code runs on one core : - InitXboxThread(); - g_AffinityPolicy->SetAffinityXbox(); - #if 0 InitSoftwareInterrupts(); #endif + std::mutex m; + std::unique_lock lock(m); while (true) { - if (g_bEnableAllInterrupts) { - TriggerPendingConnectedInterrupts(); + for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { + // If the interrupt is pending and connected, process it + if (HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); + } } - Sleep(1); + g_InterruptSignal.wait(lock, []() { return g_AnyInterruptAsserted.load() && g_bEnableAllInterrupts.load(); }); + g_AnyInterruptAsserted = false; } - return 0; + assert(0); } static void CxbxKrnlClockThread(void* pVoid) @@ -706,7 +699,6 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, unsigned& reserved_systems // Launch Segaboot CxbxLaunchNewXbe(chihiroSegaBootNew); CxbxKrnlShutDown(true); - TerminateProcess(GetCurrentProcess(), EXIT_SUCCESS); } #endif // Chihiro wip block @@ -1405,13 +1397,19 @@ static void CxbxrKrnlInitHacks() CxbxrLogDumpXbeInfo(pLibraryVersion); - CxbxKrnlRegisterThread(GetCurrentThread()); - // Make sure the Xbox1 code runs on one core (as the box itself has only 1 CPU, // this will better aproximate the environment with regard to multi-threading) : EmuLogInit(LOG_LEVEL::DEBUG, "Determining CPU affinity."); g_AffinityPolicy = AffinityPolicy::InitPolicy(); + // 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); + if (!xbox::ObInitSystem()) { + CxbxrKrnlAbortEx(LOG_PREFIX_INIT, "Unable to intialize ObInitSystem."); + } + xbox::KiInitSystem(); + // initialize graphics EmuLogInit(LOG_LEVEL::DEBUG, "Initializing render window."); CxbxInitWindow(true); @@ -1492,13 +1490,6 @@ static void CxbxrKrnlInitHacks() EmuInitFS(); - InitXboxThread(); - g_AffinityPolicy->SetAffinityXbox(); - if (!xbox::ObInitSystem()) { - CxbxrKrnlAbortEx(LOG_PREFIX_INIT, "Unable to intialize ObInitSystem."); - } - xbox::KiInitSystem(); - #ifdef CHIHIRO_WORK // If this title is Chihiro, Setup JVS if (g_bIsChihiro) { @@ -1508,25 +1499,21 @@ static void CxbxrKrnlInitHacks() EmuX86_Init(); // Create the interrupt processing thread - DWORD dwThreadId; - HANDLE hThread = (HANDLE)_beginthreadex(NULL, NULL, CxbxKrnlInterruptThread, NULL, NULL, (unsigned int*)&dwThreadId); + xbox::HANDLE hThread; + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxKrnlInterruptThread, xbox::zeroptr, FALSE); // Start the kernel clock thread - TimerObject* KernelClockThr = Timer_Create(CxbxKrnlClockThread, nullptr, "Kernel clock thread", false); + TimerObject* KernelClockThr = Timer_Create(CxbxKrnlClockThread, nullptr, "Kernel clock thread", true); Timer_Start(KernelClockThr, SCALE_MS_IN_NS); - EmuLogInit(LOG_LEVEL::DEBUG, "Calling XBE entry point..."); - CxbxLaunchXbe(Entry); + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE); - // FIXME: Wait for Cxbx to exit or error fatally - Sleep(INFINITE); + EmuKeFreePcr(); - EmuLogInit(LOG_LEVEL::DEBUG, "XBE entry point returned"); - fflush(stdout); - - CxbxUnlockFilePath(); - - // EmuShared::Cleanup(); FIXME: commenting this line is a bad workaround for issue #617 (https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/issues/617) - CxbxKrnlTerminateThread(); + // This will wait forever + std::condition_variable cv; + std::mutex m; + std::unique_lock lock(m); + cv.wait(lock, [] { return false; }); } // REMARK: the following is useless, but PatrickvL has asked to keep it for documentation purposes @@ -1544,8 +1531,6 @@ static void CxbxrKrnlInitHacks() { g_bEmuException = true; - CxbxKrnlResume(); - // print out error message (if exists) if(szErrorMessage != NULL) { @@ -1575,65 +1560,50 @@ static void CxbxrKrnlInitHacks() CxbxKrnlShutDown(); } -void CxbxKrnlRegisterThread(HANDLE hThread) +void CxbxrKrnlSuspendThreads() { - // we must duplicate this handle in order to retain Suspend/Resume thread rights from a remote thread - { - HANDLE hDupHandle = NULL; + xbox::PLIST_ENTRY ThreadListEntry = KiUniqueProcess.ThreadListHead.Flink; + std::vector threads; + threads.reserve(KiUniqueProcess.StackCount); - if (DuplicateHandle(g_CurrentProcessHandle, hThread, g_CurrentProcessHandle, &hDupHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { - hThread = hDupHandle; // Thread handle was duplicated, continue registration with the duplicate - } - else { - auto message = CxbxGetLastErrorString("DuplicateHandle"); - EmuLog(LOG_LEVEL::WARNING, message.c_str()); - } + // Don't use EmuKeGetPcr because that asserts kpcr + xbox::KPCR* Pcr = reinterpret_cast(__readfsdword(TIB_ArbitraryDataSlot)); + + // If there's nothing in list entry, skip this step. + if (!ThreadListEntry) { + return; } - g_hThreads.push_back(hThread); -} - -void CxbxKrnlSuspend() -{ - if(g_bEmuSuspended || g_bEmuException) - return; - - for (auto it = g_hThreads.begin(); it != g_hThreads.end(); ++it) - { - DWORD dwExitCode; - - if(GetExitCodeThread(*it, &dwExitCode) && dwExitCode == STILL_ACTIVE) { - // suspend thread if it is active - SuspendThread(*it); - } else { - // remove thread from thread list if it is dead - g_hThreads.erase(it); - } - } - - g_bEmuSuspended = true; -} - -void CxbxKrnlResume() -{ - if(!g_bEmuSuspended) - return; - - for (auto it = g_hThreads.begin(); it != g_hThreads.end(); ++it) - { - DWORD dwExitCode; - - if (GetExitCodeThread(*it, &dwExitCode) && dwExitCode == STILL_ACTIVE) { - // resume thread if it is active - ResumeThread(*it); - } - else { - // remove thread from thread list if it is dead - g_hThreads.erase(it); + while (ThreadListEntry != &KiUniqueProcess.ThreadListHead) { + xbox::HANDLE UniqueThread = CONTAINING_RECORD(ThreadListEntry, xbox::ETHREAD, Tcb.ThreadListEntry)->UniqueThread; + if (UniqueThread) { + // Current thread is an xbox thread + if (Pcr) { + const auto& nHandle = GetNativeHandle(UniqueThread); + if (nHandle) { + // We do not want to suspend current thread, so we let it skip this one. + if (*nHandle != NtCurrentThread()) { + threads.push_back(*nHandle); + } + } + } + // Otherwise, convert all UniqueThread to host thead handles. + else { + const auto& nHandle = GetNativeHandle(UniqueThread); + if (nHandle) { + threads.push_back(*nHandle); + } + } } + ThreadListEntry = ThreadListEntry->Flink; } - g_bEmuSuspended = false; + for (const auto& thread : threads) { + DWORD PrevCount = SuspendThread(thread); + if (PrevCount == -1) { + EmuLog(LOG_LEVEL::ERROR2, "Unable to suspend thread 0x%X for: %s", thread, WinError2Str().c_str()); + } + } } void CxbxKrnlShutDown(bool is_reboot) @@ -1656,15 +1626,23 @@ void CxbxKrnlShutDown(bool is_reboot) g_io_mu_metadata = nullptr; } - // Shutdown the memory manager - g_VMManager.Shutdown(); - // Shutdown the render manager if (g_renderbase != nullptr) { g_renderbase->Shutdown(); g_renderbase = nullptr; } + // This is very important process to prevent false positive report and allow IDEs to continue debug multiple reboots. + CxbxrKrnlSuspendThreads(); + + // NOTE: Require to be after g_renderbase's shutdown process. + // Next thing we need to do is shutdown our timer threads. + Timer_Shutdown(); + + // NOTE: Must be last step of shutdown process and before CxbxUnlockFilePath call! + // Shutdown the memory manager + g_VMManager.Shutdown(); + CxbxUnlockFilePath(); if (CxbxKrnl_hEmuParent != NULL && !is_reboot) { @@ -1766,11 +1744,6 @@ void CxbxPrintUEMInfo(ULONG ErrorCode) } } -[[noreturn]] void CxbxKrnlTerminateThread() -{ - TerminateThread(GetCurrentThread(), 0); -} - void CxbxKrnlPanic() { CxbxrKrnlAbort("Kernel Panic!"); diff --git a/src/core/kernel/init/CxbxKrnl.h b/src/core/kernel/init/CxbxKrnl.h index f22a61fa5..2de3a0e27 100644 --- a/src/core/kernel/init/CxbxKrnl.h +++ b/src/core/kernel/init/CxbxKrnl.h @@ -151,17 +151,8 @@ void CxbxKrnlEmulate(unsigned int system, blocks_reserved_t blocks_reserved); #define CxbxrKrnlAbort(fmt, ...) CxbxrKrnlAbortEx(LOG_PREFIX, fmt, ##__VA_ARGS__) -/*! register a thread handle */ -void CxbxKrnlRegisterThread(HANDLE hThread); - -/*! suspend emulation */ -void CxbxKrnlSuspend(); - -/*! resume emulation */ -void CxbxKrnlResume(); - /*! terminate gracefully the emulation */ -void CxbxKrnlShutDown(bool is_reboot = false); +[[noreturn]] void CxbxKrnlShutDown(bool is_reboot = false); /*! display the fatal error message*/ void CxbxKrnlPrintUEM(ULONG ErrorCode); @@ -169,9 +160,6 @@ void CxbxKrnlPrintUEM(ULONG ErrorCode); /*! display the cause of the fatal error message*/ void CxbxPrintUEMInfo(ULONG ErrorCode); -/*! terminate the calling thread */ -[[noreturn]] void CxbxKrnlTerminateThread(); - /*! kernel panic (trap for unimplemented kernel functions) */ void CxbxKrnlPanic(); @@ -193,8 +181,6 @@ extern ULONG g_CxbxFatalErrorCode; extern size_t g_SystemMaxMemory; -void InitXboxThread(); - /*! thread local storage structure */ extern Xbe::TLS *CxbxKrnl_TLS; diff --git a/src/core/kernel/support/Emu.cpp b/src/core/kernel/support/Emu.cpp index 5eec5646e..01b919af2 100644 --- a/src/core/kernel/support/Emu.cpp +++ b/src/core/kernel/support/Emu.cpp @@ -45,7 +45,6 @@ CRITICAL_SECTION dbgCritical; // Global Variable(s) volatile thread_local bool g_bEmuException = false; static thread_local bool bOverrideEmuException; -volatile bool g_bEmuSuspended = false; volatile bool g_bPrintfOn = true; bool g_DisablePixelShaders = false; bool g_UseAllCores = false; diff --git a/src/core/kernel/support/Emu.h b/src/core/kernel/support/Emu.h index 328b993fd..d12f493ab 100644 --- a/src/core/kernel/support/Emu.h +++ b/src/core/kernel/support/Emu.h @@ -58,7 +58,6 @@ void EmuPrintStackTrace(PCONTEXT ContextRecord); // global flags specifying current emulation state extern volatile thread_local bool g_bEmuException; -extern volatile bool g_bEmuSuspended; // global exception patching address extern void * funcExclude[2048]; @@ -67,10 +66,6 @@ extern HWND g_hEmuWindow; #define GET_FRONT_WINDOW_HANDLE ((CxbxKrnl_hEmuParent != nullptr) ? CxbxKrnl_hEmuParent : g_hEmuWindow) -// thread notification routine -extern PVOID g_pfnThreadNotification[16]; -extern int g_iThreadNotificationCount; - extern HANDLE g_CurrentProcessHandle; // Set in CxbxKrnlMain // Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime diff --git a/src/core/kernel/support/EmuFS.cpp b/src/core/kernel/support/EmuFS.cpp index 8b43d22e6..2e2c23116 100644 --- a/src/core/kernel/support/EmuFS.cpp +++ b/src/core/kernel/support/EmuFS.cpp @@ -31,7 +31,9 @@ #include #include "core\kernel\exports\EmuKrnl.h" // For InitializeListHead(), etc. #include "core\kernel\exports\EmuKrnlKe.h" +#include "core\kernel\exports\EmuKrnlKi.h" #include "core\kernel\support\EmuFS.h" // For fs_instruction_t +#include "core\kernel\support\NativeHandle.h" #include "core\kernel\init\CxbxKrnl.h" #include "Logging.h" @@ -43,6 +45,8 @@ #undef RtlZeroMemory #endif +#define TLS_ALIGNMENT_OFFSET 12 + // NT_TIB (Thread Information Block) offsets - see https://www.microsoft.com/msj/archive/S2CE.aspx #define TIB_ExceptionList offsetof(NT_TIB, ExceptionList) // = 0x00/0 #define TIB_StackBase offsetof(NT_TIB, StackBase) // = 0x04/4 @@ -111,14 +115,16 @@ // = 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(); + NT_TIB *GetNtTib() { return (NT_TIB *)__readfsdword(TIB_LinearSelfAddress); } - -xbox::KPCR* WINAPI KeGetPcr(); - uint32_t fs_lock = 0; __declspec(naked) void LockFS() @@ -160,7 +166,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. @@ -188,43 +194,65 @@ void EmuKeSetPcr(xbox::KPCR *Pcr) __writefsdword(TIB_ArbitraryDataSlot, (DWORD)Pcr); } +template void EmuKeFreePcr() { - // NOTE: don't call KeGetPcr because that one creates a new pcr for the thread when __readfsdword returns nullptr, which we don't want - xbox::PKPCR Pcr = reinterpret_cast(__readfsdword(TIB_ArbitraryDataSlot)); - - if (Pcr) { - // tls can be nullptr - xbox::PVOID Dummy; - xbox::ulong_xt Size; - xbox::ntstatus_xt Status; - 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) - 12; - Size = xbox::zero; - Status = xbox::NtFreeVirtualMemory(&Dummy, &Size, XBOX_MEM_RELEASE); // free tls - assert(Status == X_STATUS_SUCCESS); - } + 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 - assert(Status == X_STATUS_SUCCESS); - __writefsdword(TIB_ArbitraryDataSlot, NULL); } - else { - EmuLog(LOG_LEVEL::WARNING, "__readfsdword in EmuKeFreePcr returned nullptr: was this called from a non-xbox thread?"); + Dummy = Pcr; + Size = xbox::zero; + Status = xbox::NtFreeVirtualMemory(&Dummy, &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::zero; } + + EmuKeFreePcr(); } __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 @@ -232,7 +260,7 @@ __declspec(naked) void EmuFS_RefreshKPCR() __asm { pushfd pushad - call KeGetPcr + call EmuKeGetPcr popad popfd ret @@ -652,7 +680,8 @@ void EmuInitFS() } // generate fs segment selector -void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData) +template +void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData, xbox::PETHREAD Ethread) { void *pNewTLS = nullptr; xbox::PVOID base; @@ -682,7 +711,7 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData) 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 + 12; + pNewTLS = (uint8_t*)pNewTLS + TLS_ALIGNMENT_OFFSET; if (dwCopySize > 0) { memcpy((uint8_t*)pNewTLS + 4, pTLSData, dwCopySize); @@ -779,18 +808,29 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData) NewPcr->Irql = PASSIVE_LEVEL; // See KeLowerIrql; } - // Initialize a fake PrcbData.CurrentThread - { + if constexpr (IsHostThread) { + // This only happens for the kernel initialization thread of cxbxr + assert(Ethread == xbox::zeroptr); + base = xbox::zeroptr; size = sizeof(xbox::ETHREAD); xbox::NtAllocateVirtualMemory(&base, 0, &size, XBOX_MEM_RESERVE | XBOX_MEM_COMMIT, XBOX_PAGE_READWRITE); - xbox::ETHREAD *EThread = (xbox::ETHREAD*)base; // Clear, to prevent side-effects on random contents - xbox::RtlZeroMemory(EThread, sizeof(xbox::ETHREAD)); + Ethread = (xbox::PETHREAD)base; + xbox::RtlZeroMemory(Ethread, sizeof(xbox::ETHREAD)); // Clear, to prevent side-effects on random contents + } - EThread->Tcb.TlsData = pNewTLS; - EThread->UniqueThread = GetCurrentThreadId(); + // Initialize a fake PrcbData.CurrentThread + { // Set PrcbData.CurrentThread - Prcb->CurrentThread = (xbox::KTHREAD*)EThread; + 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); @@ -806,8 +846,11 @@ void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData) 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); + + _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 029b2c75d..27da33144 100644 --- a/src/core/kernel/support/EmuFS.h +++ b/src/core/kernel/support/EmuFS.h @@ -33,14 +33,26 @@ extern void EmuInitFS(); // generate fs segment selector -extern void EmuGenerateFS(Xbe::TLS *pTLS, void *pTLSData); +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); +// free kpcr allocated for the thread +template void EmuKeFreePcr(); +void EmuKeSetPcr(xbox::KPCR *Pcr); +xbox::KPCR *_stdcall EmuKeGetPcr(); + typedef struct { std::vector data; 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 diff --git a/src/core/kernel/support/NativeHandle.cpp b/src/core/kernel/support/NativeHandle.cpp index 43b744c6f..9b745d818 100644 --- a/src/core/kernel/support/NativeHandle.cpp +++ b/src/core/kernel/support/NativeHandle.cpp @@ -31,6 +31,8 @@ #include "Windows.h" #include "assert.h" #include "NativeHandle.h" +#include "core\kernel\init\CxbxKrnl.h" +#include "core/kernel/support/EmuFS.h" std::unordered_map g_RegisteredHandles; @@ -39,9 +41,28 @@ std::shared_mutex g_MapMtx; void RegisterXboxHandle(xbox::HANDLE xhandle, HANDLE nhandle) { std::unique_lock lck(g_MapMtx); - [[maybe_unused]] const auto &ret = g_RegisteredHandles.emplace(xhandle, nhandle); - // Even when duplicating xbox handles with NtDuplicateObject, the duplicate will still be different then the source handle - assert(ret.second == true); + auto ret = g_RegisteredHandles.try_emplace(xhandle, nhandle); + if (ret.second == false) { + // This can happen when an ob handle has been destroyed, but then a thread switch happens before the first thread + // got a chance to remove the old handle from g_RegisteredHandles with RemoveXboxHandle + // Test case: dashboard + auto now = std::chrono::system_clock::now(); + auto timeout = now + std::chrono::milliseconds(2000); + while (now <= timeout) { + lck.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + lck.lock(); + ret = g_RegisteredHandles.try_emplace(xhandle, 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 + // that we have forgotten to call RemoveXboxHandle on the old handle, or the other thread is waiting/deadlocked, so this is a bug + CxbxrKrnlAbortEx(CXBXR_MODULE::CXBXR, "Failed to register new xbox handle after more than two seconds!"); + } } void RemoveXboxHandle(xbox::HANDLE xhandle) @@ -51,8 +72,29 @@ void RemoveXboxHandle(xbox::HANDLE xhandle) assert(ret == 1); } +template std::optional GetNativeHandle(xbox::HANDLE xhandle) { + // If SourceHandle is -2 = NtCurrentThread, then we are searching the handle of this thread + // Test case: Metal Slug 3 + if (xhandle == NtCurrentThread()) { + // Only used for threads as Windows doesn't allow non-special handle for same thread. + if (NoConversion) { + return xhandle; + } + else { + xhandle = xbox::PspGetCurrentThread()->UniqueThread; + } + } + // If xhandle is not special handle, check if it's the same as current thread. + // Only used for threads as Windows doesn't allow non-special handle for same thread. + // This will only triggered within kernel functions i.e. KeSetDisableBoostThread and KeSetBasePriorityThread. + else if (NoConversion) { + if (xhandle == xbox::PspGetCurrentThread()->UniqueThread) { + return NtCurrentThread(); + } + } + std::shared_lock lck(g_MapMtx); auto &it = g_RegisteredHandles.find(xhandle); if (it == g_RegisteredHandles.end()) { @@ -62,3 +104,6 @@ std::optional GetNativeHandle(xbox::HANDLE xhandle) return it->second; } } + +template std::optional GetNativeHandle(xbox::HANDLE xhandle); +template std::optional GetNativeHandle(xbox::HANDLE xhandle); diff --git a/src/core/kernel/support/NativeHandle.h b/src/core/kernel/support/NativeHandle.h index 3ba46f9b8..b0ec16dcd 100644 --- a/src/core/kernel/support/NativeHandle.h +++ b/src/core/kernel/support/NativeHandle.h @@ -31,4 +31,4 @@ void RegisterXboxHandle(xbox::HANDLE xhandle, HANDLE nhandle); void RemoveXboxHandle(xbox::HANDLE xhandle); -std::optional GetNativeHandle(xbox::HANDLE xhandle); +template std::optional GetNativeHandle(xbox::HANDLE xhandle); diff --git a/src/devices/network/NVNetDevice.cpp b/src/devices/network/NVNetDevice.cpp index 0bceed388..7b47ddac0 100644 --- a/src/devices/network/NVNetDevice.cpp +++ b/src/devices/network/NVNetDevice.cpp @@ -536,21 +536,21 @@ void NVNetDevice::Reset() bool NVNetDevice::GetMacAddress(std::string adapterName, void* pMAC) { - IP_ADAPTER_INFO AdapterInfo[128]; - PIP_ADAPTER_INFO pAdapterInfo; - ULONG dwBufferLength = sizeof(AdapterInfo); + // AdapterInfo is too large to be allocated on the stack, and will cause a crash in debug builds when _chkstk detects it + PIP_ADAPTER_INFO pAdapterInfo = new IP_ADAPTER_INFO[128]; + ULONG dwBufferLength = sizeof(IP_ADAPTER_INFO) * 128; - DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufferLength); + DWORD dwStatus = GetAdaptersInfo(pAdapterInfo, &dwBufferLength); if (dwStatus != ERROR_SUCCESS) { + delete[] pAdapterInfo; return false; } - pAdapterInfo = AdapterInfo; - // Find the specified adapter do { if (strcmp(pAdapterInfo->AdapterName, adapterName.c_str()) == 0) { memcpy(pMAC, pAdapterInfo->Address, 6); + delete[] pAdapterInfo; return true; } @@ -558,6 +558,7 @@ bool NVNetDevice::GetMacAddress(std::string adapterName, void* pMAC) } while (pAdapterInfo); + delete[] pAdapterInfo; return false; } diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 73214a7b1..661495d56 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -150,8 +150,6 @@ static void update_irq(NV2AState *d) else { HalSystemInterrupts[3].Assert(false); } - - SwitchToThread(); } @@ -1117,7 +1115,7 @@ static void nv2a_vblank_thread(NV2AState *d) //NV2ADevice::UpdateHostDisplay(d); } - Sleep(1); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }