/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** * * @modified Tom Clay, 2026 - Adapted for ReXGlue runtime */ // Disable warnings about unused parameters for kernel functions #pragma GCC diagnostic ignored "-Wunused-parameter" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rex::kernel::xboxkrnl { using namespace rex::system; using rex::runtime::current_ppc_context; // r13 + 0x100: pointer to thread local state // Thread local state: // 0x058: kernel time // 0x14C: thread id // 0x150: if >0 then error states don't get set // 0x160: last error // GetCurrentThreadId: // lwz r11, 0x100(r13) // lwz r3, 0x14C(r11) // RtlGetLastError: // lwz r11, 0x150(r13) // if (r11 == 0) { // lwz r11, 0x100(r13) // stw r3, 0x160(r11) // } // RtlSetLastError: // lwz r11, 0x150(r13) // if (r11 == 0) { // lwz r11, 0x100(r13) // stw r3, 0x160(r11) // } // RtlSetLastNTError: // r3 = RtlNtStatusToDosError(r3) // lwz r11, 0x150(r13) // if (r11 == 0) { // lwz r11, 0x100(r13) // stw r3, 0x160(r11) // } template object_ref LookupNamedObject(KernelState* kernel_state, uint32_t obj_attributes_ptr) { // If the name exists and its type matches, we can return that (ref+1) // with a success of NAME_EXISTS. // If the name exists and its type doesn't match, we do NAME_COLLISION. // Otherwise, we add like normal. if (!obj_attributes_ptr) { return nullptr; } auto obj_attributes = kernel_state->memory()->TranslateVirtual(obj_attributes_ptr); assert_true(obj_attributes->name_ptr != 0); auto name = util::TranslateAnsiStringAddress(kernel_state->memory(), obj_attributes->name_ptr); if (!name.empty()) { X_HANDLE handle = X_INVALID_HANDLE_VALUE; X_RESULT result = kernel_state->object_table()->GetObjectByName(name, &handle); if (XSUCCEEDED(result)) { // Found something! It's been retained, so return. auto obj = kernel_state->object_table()->LookupObject(handle); if (obj) { // The caller will do as it likes. obj->ReleaseHandle(); return obj; } } } return nullptr; } u32 ExCreateThread_entry(mapped_u32 handle_ptr, u32 stack_size, mapped_u32 thread_id_ptr, u32 xapi_thread_startup, mapped_void start_address, mapped_void start_context, u32 creation_flags) { REXKRNL_IMPORT_TRACE( "ExCreateThread", "stack={:#x} xapi_startup={:#x} start={:#x} context={:#x} flags={:#x}", (uint32_t)stack_size, (uint32_t)xapi_thread_startup, start_address.guest_address(), start_context.guest_address(), (uint32_t)creation_flags); // http://jafile.com/uploads/scoop/main.cpp.txt // DWORD // LPHANDLE Handle, // DWORD StackSize, // LPDWORD ThreadId, // LPVOID XapiThreadStartup, ?? often 0 // LPVOID StartAddress, // LPVOID StartContext, // DWORD CreationFlags // 0x80? // Determine target process based on creation flags. uint32_t guest_process = REX_KERNEL_STATE()->GetTitleProcess(); if (creation_flags & 2) { REXKRNL_WARN("[ExCreateThread] Guest is creating a system thread!"); guest_process = REX_KERNEL_STATE()->GetSystemProcess(); } // Inherit default stack size uint32_t actual_stack_size = stack_size; if (actual_stack_size == 0) { actual_stack_size = REX_KERNEL_STATE()->GetExecutableModule()->stack_size(); } // Stack must be aligned to 16kb pages actual_stack_size = std::max((uint32_t)0x4000, ((actual_stack_size + 0xFFF) & 0xFFFFF000)); auto thread = object_ref(new XThread( REX_KERNEL_STATE(), actual_stack_size, xapi_thread_startup, start_address.guest_address(), start_context.guest_address(), creation_flags, true, false, guest_process)); X_STATUS result = thread->Create(); if (XFAILED(result)) { // Failed! REXKRNL_ERROR("Thread creation failed: {:08X}", result); return result; } if (XSUCCEEDED(result)) { if (handle_ptr) { if (creation_flags & 0x80) { *handle_ptr = thread->guest_object(); } else { *handle_ptr = thread->handle(); } } if (thread_id_ptr) { *thread_id_ptr = thread->thread_id(); } } REXKRNL_IMPORT_RESULT("ExCreateThread", "{:#x} handle={:#x} tid={}", result, handle_ptr ? (uint32_t)*handle_ptr : 0, thread_id_ptr ? (uint32_t)*thread_id_ptr : 0); return result; } u32 ExTerminateThread_entry(u32 exit_code) { XThread* thread = XThread::GetCurrentThread(); // NOTE: this kills us right now. We won't return from it. return thread->Exit(exit_code); } u32 NtResumeThread_entry(u32 handle, mapped_u32 suspend_count_ptr) { X_RESULT result = X_STATUS_INVALID_HANDLE; uint32_t suspend_count = 0; auto thread = REX_KERNEL_OBJECTS()->LookupObject(handle); if (thread) { REXKRNL_TRACE("[NtResumeThread] handle={:08X} thread={}", uint32_t(handle), thread->name()); result = thread->Resume(&suspend_count); } else { REXKRNL_WARN("[NtResumeThread] handle={:08X} NOT FOUND", uint32_t(handle)); } if (suspend_count_ptr) { *suspend_count_ptr = suspend_count; } REXKRNL_TRACE("[NtResumeThread] -> {:#x} suspend_count={}", result, suspend_count); return result; } u32 KeResumeThread_entry(mapped_void thread_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto thread = XObject::GetNativeObject(REX_KERNEL_STATE(), thread_ptr); if (thread) { REXKRNL_TRACE("[KeResumeThread] ptr={:08X} thread={}", thread_ptr.guest_address(), thread->name()); result = thread->Resume(); } else { REXKRNL_WARN("[KeResumeThread] ptr={:08X} NOT FOUND", thread_ptr.guest_address()); result = X_STATUS_INVALID_HANDLE; } REXKRNL_TRACE("[KeResumeThread] -> {:#x}", result); return result; } u32 NtSuspendThread_entry(u32 handle, mapped_u32 suspend_count_ptr) { X_RESULT result = X_STATUS_SUCCESS; uint32_t suspend_count = 0; auto thread = REX_KERNEL_OBJECTS()->LookupObject(handle); if (thread) { if (thread->type() != XObject::Type::Thread) { return X_STATUS_OBJECT_TYPE_MISMATCH; } auto guest_thread = thread->guest_object(); if (guest_thread->terminated) { return X_STATUS_THREAD_IS_TERMINATING; } REXKRNL_TRACE("[NtSuspendThread] handle={:08X} thread={}", uint32_t(handle), thread->name()); #if REX_PLATFORM_LINUX auto* current_thread = XThread::GetCurrentThread(); bool is_self_suspend = current_thread && current_thread == thread.get(); if (is_self_suspend) { REXKRNL_TRACE("[NtSuspendThread] self-suspend for thread={:08X}", uint32_t(handle)); suspend_count = thread->SelfSuspend(); result = X_STATUS_SUCCESS; } else { result = thread->Suspend(&suspend_count); } #else result = thread->Suspend(&suspend_count); #endif } else { REXKRNL_WARN("[NtSuspendThread] handle={:08X} NOT FOUND", uint32_t(handle)); result = X_STATUS_INVALID_HANDLE; } if (suspend_count_ptr) { *suspend_count_ptr = suspend_count; } REXKRNL_TRACE("[NtSuspendThread] -> {:#x} suspend_count={}", result, suspend_count); return result; } namespace { void UpdateGuestStackPointers(X_KTHREAD* kthread, X_KPCR* pcr, PPCContext* ctx, uint32_t sp, uint32_t stack_alloc_base, uint32_t stack_base, uint32_t stack_limit) { kthread->stack_alloc_base = stack_alloc_base; kthread->stack_base = stack_base; kthread->stack_limit = stack_limit; pcr->stack_base_ptr = stack_base; pcr->stack_end_ptr = stack_limit; ctx->r1.u64 = sp; } } // namespace void KeSetCurrentStackPointers_entry(mapped_void stack_ptr, ppc_ptr_t thread, mapped_void stack_alloc_base, mapped_void stack_base, mapped_void stack_limit) { auto current_thread = XThread::GetCurrentThread(); auto context = current_thread->thread_state()->context(); auto pcr = REX_KERNEL_MEMORY()->TranslateVirtual(static_cast(context->r13.u64)); UpdateGuestStackPointers(thread, pcr, context, stack_ptr.guest_address(), stack_alloc_base.value(), stack_base.value(), stack_limit.value()); } u32 KeSetAffinityThread_entry(mapped_void thread_ptr, u32 affinity, mapped_u32 previous_affinity_ptr) { REXKRNL_IMPORT_TRACE("KeSetAffinityThread", "thread={:#x} affinity={:#x}", thread_ptr.guest_address(), (uint32_t)affinity); // The Xbox 360, according to disassembly of KeSetAffinityThread, unlike // Windows NT, stores the previous affinity via the pointer provided as an // argument, not in the return value - the return value is used for the // result. if (!affinity) { return X_STATUS_INVALID_PARAMETER; } auto thread = XObject::GetNativeObject(REX_KERNEL_STATE(), thread_ptr); if (!thread) { REXKRNL_IMPORT_WARN("KeSetAffinityThread", "invalid thread handle ptr={:#x}", thread_ptr.guest_address()); return X_STATUS_INVALID_HANDLE; } if (previous_affinity_ptr) { *previous_affinity_ptr = uint32_t(1) << thread->active_cpu(); } thread->SetAffinity(affinity); REXKRNL_IMPORT_RESULT("KeSetAffinityThread", "0x0"); return X_STATUS_SUCCESS; } u32 KeQueryBasePriorityThread_entry(mapped_void thread_ptr) { REXKRNL_IMPORT_TRACE("KeQueryBasePriorityThread", "thread={:#x}", thread_ptr.guest_address()); int32_t priority = 0; auto thread = XObject::GetNativeObject(REX_KERNEL_STATE(), thread_ptr); if (thread) { priority = thread->QueryPriority(); } REXKRNL_IMPORT_RESULT("KeQueryBasePriorityThread", "{}", priority); return priority; } u32 KeSetBasePriorityThread_entry(mapped_void thread_ptr, u32 increment) { REXKRNL_IMPORT_TRACE("KeSetBasePriorityThread", "thread={:#x} increment={}", thread_ptr.guest_address(), (int32_t)increment); int32_t prev_priority = 0; auto thread = XObject::GetNativeObject(REX_KERNEL_STATE(), thread_ptr); if (thread) { prev_priority = thread->QueryPriority(); thread->SetPriority(increment); } REXKRNL_IMPORT_RESULT("KeSetBasePriorityThread", "prev_priority={}", prev_priority); return prev_priority; } u32 KeSetDisableBoostThread_entry(ppc_ptr_t thread_ptr, u32 disabled) { auto old_boost_disabled = reinterpret_cast(&thread_ptr->boost_disabled) ->exchange(static_cast(disabled)); return old_boost_disabled; } u32 KeGetCurrentProcessType_entry() { auto current_thread = XThread::GetCurrentThread(); auto context = current_thread->thread_state()->context(); auto pcr = REX_KERNEL_MEMORY()->TranslateVirtual(static_cast(context->r13.u64)); if (pcr->prcb_data.dpc_active) { return pcr->processtype_value_in_dpc; } auto thread = REX_KERNEL_MEMORY()->TranslateVirtual(pcr->prcb_data.current_thread); return thread->process_type; } void KeSetCurrentProcessType_entry(u32 type) { // One of X_PROCTYPE_? assert_true(type <= 2); REX_KERNEL_STATE()->set_process_type(type); auto current_thread = XThread::GetCurrentThread(); auto context = current_thread->thread_state()->context(); auto pcr = REX_KERNEL_MEMORY()->TranslateVirtual(static_cast(context->r13.u64)); if (pcr->prcb_data.dpc_active) { pcr->processtype_value_in_dpc = static_cast(type); } } u32 KeQueryPerformanceFrequency_entry() { uint64_t result = chrono::Clock::guest_tick_frequency(); return static_cast(result); } u32 KeDelayExecutionThread_entry(u32 processor_mode, u32 alertable, mapped_u64 interval_ptr) { XThread* thread = XThread::GetCurrentThread(); if (alertable) { thread->DeliverAPCs(); } X_STATUS result = thread->Delay(processor_mode, alertable, *interval_ptr); if (alertable && result == X_STATUS_USER_APC) { thread->DeliverAPCs(); } return result; } u32 NtYieldExecution_entry() { rex::thread::MaybeYield(); return X_STATUS_SUCCESS; } void KeQuerySystemTime_entry(mapped_u64 time_ptr) { uint64_t time = chrono::Clock::QueryGuestSystemTime(); if (time_ptr) { *time_ptr = time; } } // https://msdn.microsoft.com/en-us/library/ms686801 u32 KeTlsAlloc_entry() { uint32_t slot = REX_KERNEL_STATE()->AllocateTLS(current_ppc_context()); if (slot != X_TLS_OUT_OF_INDEXES) { XThread::GetCurrentThread()->SetTLSValue(slot, 0); } REXKRNL_IMPORT_RESULT("KeTlsAlloc", "slot={}", slot); return slot; } // https://msdn.microsoft.com/en-us/library/ms686804 u32 KeTlsFree_entry(u32 tls_index) { REXKRNL_IMPORT_TRACE("KeTlsFree", "slot={}", (uint32_t)tls_index); if (tls_index == X_TLS_OUT_OF_INDEXES) { REXKRNL_IMPORT_RESULT("KeTlsFree", "0 (invalid)"); return 0; } REX_KERNEL_STATE()->FreeTLS(current_ppc_context(), tls_index); REXKRNL_IMPORT_RESULT("KeTlsFree", "1"); return 1; } // https://msdn.microsoft.com/en-us/library/ms686812 u32 KeTlsGetValue_entry(u32 tls_index) { // xboxkrnl doesn't actually have an error branch - it always succeeds, even // if it overflows the TLS. uint32_t value = 0; if (XThread::GetCurrentThread()->GetTLSValue(tls_index, &value)) { return value; } return 0; } // https://msdn.microsoft.com/en-us/library/ms686818 u32 KeTlsSetValue_entry(u32 tls_index, u32 tls_value) { REXKRNL_IMPORT_TRACE("KeTlsSetValue", "slot={} value={:#x}", (uint32_t)tls_index, (uint32_t)tls_value); // xboxkrnl doesn't actually have an error branch - it always succeeds, even // if it overflows the TLS. if (XThread::GetCurrentThread()->SetTLSValue(tls_index, tls_value)) { REXKRNL_IMPORT_RESULT("KeTlsSetValue", "1"); return 1; } REXKRNL_IMPORT_RESULT("KeTlsSetValue", "0"); return 0; } void KeInitializeEvent_entry(ppc_ptr_t event_ptr, u32 event_type, u32 initial_state) { event_ptr.Zero(); event_ptr->header.type = event_type; event_ptr->header.signal_state = (uint32_t)initial_state; auto ev = XObject::GetNativeObject(REX_KERNEL_STATE(), event_ptr, event_type); if (!ev) { assert_always(); return; } } uint32_t xeKeSetEvent(X_KEVENT* event_ptr, uint32_t increment, uint32_t wait) { auto ev = XObject::GetNativeObject(REX_KERNEL_STATE(), event_ptr); if (!ev) { assert_always(); return 0; } return ev->Set(increment, !!wait); } u32 KeSetEvent_entry(ppc_ptr_t event_ptr, u32 increment, u32 wait) { return xeKeSetEvent(event_ptr, increment, wait); } u32 KePulseEvent_entry(ppc_ptr_t event_ptr, u32 increment, u32 wait) { auto ev = XObject::GetNativeObject(REX_KERNEL_STATE(), event_ptr); if (!ev) { assert_always(); return 0; } return ev->Pulse(increment, !!wait); } u32 KeResetEvent_entry(ppc_ptr_t event_ptr) { auto ev = XObject::GetNativeObject(REX_KERNEL_STATE(), event_ptr); if (!ev) { assert_always(); return 0; } return ev->Reset(); } u32 NtCreateEvent_entry(mapped_u32 handle_ptr, ppc_ptr_t obj_attributes_ptr, u32 event_type, u32 initial_state) { // Check for an existing timer with the same name. auto existing_object = LookupNamedObject(REX_KERNEL_STATE(), obj_attributes_ptr.guest_address()); if (existing_object) { if (existing_object->type() == XObject::Type::Event) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); } return X_STATUS_OBJECT_NAME_EXISTS; } else { return X_STATUS_INVALID_HANDLE; } } auto ev = object_ref(new XEvent(REX_KERNEL_STATE())); ev->Initialize(!event_type, !!initial_state); // obj_attributes may have a name inside of it, if != NULL. if (obj_attributes_ptr) { ev->SetAttributes(obj_attributes_ptr.guest_address()); } if (handle_ptr) { *handle_ptr = ev->handle(); } return X_STATUS_SUCCESS; } uint32_t xeNtSetEvent(uint32_t handle, rex::be* previous_state_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto ev = REX_KERNEL_OBJECTS()->LookupObject(handle); if (ev) { int32_t was_signalled = ev->Set(0, false); if (previous_state_ptr) { *previous_state_ptr = static_cast(was_signalled); } } else { result = X_STATUS_INVALID_HANDLE; } return result; } u32 NtSetEvent_entry(u32 handle, mapped_u32 previous_state_ptr) { return xeNtSetEvent(handle, previous_state_ptr); } u32 NtPulseEvent_entry(u32 handle, mapped_u32 previous_state_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto ev = REX_KERNEL_OBJECTS()->LookupObject(handle); if (ev) { int32_t was_signalled = ev->Pulse(0, false); if (previous_state_ptr) { *previous_state_ptr = static_cast(was_signalled); } } else { result = X_STATUS_INVALID_HANDLE; } return result; } uint32_t xeNtClearEvent(uint32_t handle) { X_STATUS result = X_STATUS_SUCCESS; auto ev = REX_KERNEL_OBJECTS()->LookupObject(handle); if (ev) { ev->Reset(); } else { result = X_STATUS_INVALID_HANDLE; } return result; } u32 NtClearEvent_entry(u32 handle) { return xeNtClearEvent(handle); } // https://msdn.microsoft.com/en-us/library/windows/hardware/ff552150(v=vs.85).aspx void KeInitializeSemaphore_entry(ppc_ptr_t semaphore_ptr, u32 count, u32 limit) { semaphore_ptr->header.type = 5; // SemaphoreObject semaphore_ptr->header.signal_state = (uint32_t)count; semaphore_ptr->limit = (uint32_t)limit; auto sem = XObject::GetNativeObject(REX_KERNEL_STATE(), semaphore_ptr, 5 /* SemaphoreObject */); if (!sem) { assert_always(); return; } } uint32_t xeKeReleaseSemaphore(X_KSEMAPHORE* semaphore_ptr, uint32_t increment, uint32_t adjustment, uint32_t wait) { auto sem = XObject::GetNativeObject(REX_KERNEL_STATE(), semaphore_ptr); if (!sem) { assert_always(); return 0; } // TODO(benvanik): increment thread priority? // TODO(benvanik): wait? int32_t previous_count = 0; [[maybe_unused]] bool success = sem->ReleaseSemaphore(static_cast(adjustment), &previous_count); return static_cast(previous_count); } u32 KeReleaseSemaphore_entry(ppc_ptr_t semaphore_ptr, u32 increment, u32 adjustment, u32 wait) { return xeKeReleaseSemaphore(semaphore_ptr, increment, adjustment, wait); } u32 NtCreateSemaphore_entry(mapped_u32 handle_ptr, mapped_void obj_attributes_ptr, u32 count, u32 limit) { // Check for an existing semaphore with the same name. auto existing_object = LookupNamedObject(REX_KERNEL_STATE(), obj_attributes_ptr.guest_address()); if (existing_object) { if (existing_object->type() == XObject::Type::Semaphore) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); } return X_STATUS_OBJECT_NAME_EXISTS; } else { return X_STATUS_INVALID_HANDLE; } } auto sem = object_ref(new XSemaphore(REX_KERNEL_STATE())); if (!sem->Initialize((int32_t)count, (int32_t)limit)) { if (handle_ptr) { *handle_ptr = 0; } sem->ReleaseHandle(); return X_STATUS_INVALID_PARAMETER; } // obj_attributes may have a name inside of it, if != NULL. if (obj_attributes_ptr) { sem->SetAttributes(obj_attributes_ptr.guest_address()); } if (handle_ptr) { *handle_ptr = sem->handle(); } return X_STATUS_SUCCESS; } u32 NtReleaseSemaphore_entry(u32 sem_handle, u32 release_count, mapped_u32 previous_count_ptr) { X_STATUS result = X_STATUS_SUCCESS; int32_t previous_count = 0; auto sem = REX_KERNEL_OBJECTS()->LookupObject(sem_handle); if (sem) { bool success = sem->ReleaseSemaphore(static_cast(release_count), &previous_count); if (!success) { result = X_STATUS_SEMAPHORE_LIMIT_EXCEEDED; } } else { result = X_STATUS_INVALID_HANDLE; } if (previous_count_ptr) { *previous_count_ptr = (uint32_t)previous_count; } return result; } u32 NtCreateMutant_entry(mapped_u32 handle_out, ppc_ptr_t obj_attributes, u32 initial_owner) { // Check for an existing timer with the same name. auto existing_object = LookupNamedObject(REX_KERNEL_STATE(), obj_attributes.guest_address()); if (existing_object) { if (existing_object->type() == XObject::Type::Mutant) { if (handle_out) { existing_object->RetainHandle(); *handle_out = existing_object->handle(); } return X_STATUS_OBJECT_NAME_EXISTS; } else { return X_STATUS_INVALID_HANDLE; } } auto mutant = object_ref(new XMutant(REX_KERNEL_STATE())); mutant->Initialize(initial_owner ? true : false); // obj_attributes may have a name inside of it, if != NULL. if (obj_attributes) { mutant->SetAttributes(obj_attributes.guest_address()); } if (handle_out) { *handle_out = mutant->handle(); } return X_STATUS_SUCCESS; } u32 NtReleaseMutant_entry(u32 mutant_handle, u32 unknown) { // This doesn't seem to be supported. // int32_t previous_count_ptr = SHIM_GET_ARG_32(2); // Whatever arg 1 is all games seem to set it to 0, so whether it's // abandon or wait we just say false. Which is good, cause they are // both ignored. assert_zero(unknown); uint32_t priority_increment = 0; bool abandon = false; bool wait = false; X_STATUS result = X_STATUS_SUCCESS; auto mutant = REX_KERNEL_OBJECTS()->LookupObject(mutant_handle); if (mutant) { result = mutant->ReleaseMutant(priority_increment, abandon, wait); } else { result = X_STATUS_INVALID_HANDLE; } return result; } u32 NtCreateTimer_entry(mapped_u32 handle_ptr, mapped_void obj_attributes_ptr, u32 timer_type) { // timer_type = NotificationTimer (0) or SynchronizationTimer (1) // Check for an existing timer with the same name. auto existing_object = LookupNamedObject(REX_KERNEL_STATE(), obj_attributes_ptr.guest_address()); if (existing_object) { if (existing_object->type() == XObject::Type::Timer) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); } return X_STATUS_OBJECT_NAME_EXISTS; } else { return X_STATUS_INVALID_HANDLE; } } auto timer = object_ref(new XTimer(REX_KERNEL_STATE())); timer->Initialize(timer_type); // obj_attributes may have a name inside of it, if != NULL. if (obj_attributes_ptr) { timer->SetAttributes(obj_attributes_ptr.guest_address()); } if (handle_ptr) { *handle_ptr = timer->handle(); } return X_STATUS_SUCCESS; } void KeInitializeTimerEx_entry(ppc_ptr_t timer_ptr, u32 timer_type, u32 proc_type) { assert_true(timer_type == 0 || timer_type == 1); assert_true(proc_type < 3); // Other fields are unmodified; they must carry through multiple calls. timer_ptr->header.process_type = static_cast(proc_type & 0xFF); timer_ptr->header.inserted = 0; timer_ptr->header.type = static_cast(timer_type + 8); timer_ptr->header.signal_state = 0; // Initialize wait list to point to itself (empty list). uint32_t wait_list_addr = timer_ptr.guest_address() + offsetof(X_DISPATCH_HEADER, wait_list_flink); timer_ptr->header.wait_list_flink = wait_list_addr; timer_ptr->header.wait_list_blink = wait_list_addr; timer_ptr->due_time = 0; timer_ptr->period = 0; } u32 NtSetTimerEx_entry(u32 timer_handle, mapped_u64 due_time_ptr, mapped_void routine_ptr /*PTIMERAPCROUTINE*/, u32 unk_one, mapped_void routine_arg, u32 resume, u32 period_ms, u32 unk_zero) { assert_true(unk_one == 1); assert_true(unk_zero == 0); uint64_t due_time = *due_time_ptr; X_STATUS result = X_STATUS_SUCCESS; auto timer = REX_KERNEL_OBJECTS()->LookupObject(timer_handle); if (timer) { result = timer->SetTimer(due_time, period_ms, routine_ptr.guest_address(), routine_arg.guest_address(), resume ? true : false); } else { result = X_STATUS_INVALID_HANDLE; } return result; } u32 NtCancelTimer_entry(u32 timer_handle, mapped_u32 current_state_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto timer = REX_KERNEL_OBJECTS()->LookupObject(timer_handle); if (timer) { result = timer->Cancel(); } else { result = X_STATUS_INVALID_HANDLE; } if (current_state_ptr) { *current_state_ptr = 0; } return result; } uint32_t xeKeWaitForSingleObject(void* object_ptr, uint32_t wait_reason, uint32_t processor_mode, uint32_t alertable, uint64_t* timeout_ptr) { auto object = XObject::GetNativeObject(REX_KERNEL_STATE(), object_ptr); if (!object) { // The only kind-of failure code (though this should never happen) assert_always(); return X_STATUS_ABANDONED_WAIT_0; } X_STATUS result = object->Wait(wait_reason, processor_mode, alertable, timeout_ptr); if (alertable && result == X_STATUS_USER_APC) { XThread::GetCurrentThread()->DeliverAPCs(); } return result; } u32 KeWaitForSingleObject_entry(mapped_void object_ptr, u32 wait_reason, u32 processor_mode, u32 alertable, mapped_u64 timeout_ptr) { uint64_t timeout = timeout_ptr ? static_cast(*timeout_ptr) : 0u; // REXKRNL_IMPORT_TRACE("KeWaitForSingleObject", "obj={:#x} reason={} mode={} alertable={} // timeout={}", // object_ptr.guest_address(), (uint32_t)wait_reason, //(uint32_t)processor_mode, (uint32_t)alertable, // timeout_ptr ? (int64_t)timeout : -1); auto result = xeKeWaitForSingleObject(object_ptr, wait_reason, processor_mode, alertable, timeout_ptr ? &timeout : nullptr); // REXKRNL_IMPORT_RESULT("KeWaitForSingleObject", "{:#x}", result); return result; } u32 NtWaitForSingleObjectEx_entry(u32 object_handle, u32 wait_mode, u32 alertable, mapped_u64 timeout_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto object = REX_KERNEL_OBJECTS()->LookupObject(object_handle); if (object) { uint64_t timeout = timeout_ptr ? static_cast(*timeout_ptr) : 0u; result = object->Wait(3, wait_mode, alertable, timeout_ptr ? &timeout : nullptr); if (alertable && result == X_STATUS_USER_APC) { XThread::GetCurrentThread()->DeliverAPCs(); } } else { result = X_STATUS_INVALID_HANDLE; } return result; } u32 KeWaitForMultipleObjects_entry(u32 count, mapped_u32 objects_ptr, u32 wait_type, u32 wait_reason, u32 processor_mode, u32 alertable, mapped_u64 timeout_ptr, mapped_void wait_block_array_ptr) { assert_true(wait_type <= 1); std::vector> objects; for (uint32_t n = 0; n < count; n++) { auto object_ptr = REX_KERNEL_MEMORY()->TranslateVirtual(objects_ptr[n]); auto object_ref = XObject::GetNativeObject(REX_KERNEL_STATE(), object_ptr); if (!object_ref) { return X_STATUS_INVALID_PARAMETER; } objects.push_back(std::move(object_ref)); } uint64_t timeout = timeout_ptr ? static_cast(*timeout_ptr) : 0u; X_STATUS result = XObject::WaitMultiple( uint32_t(objects.size()), reinterpret_cast(objects.data()), wait_type, wait_reason, processor_mode, alertable, timeout_ptr ? &timeout : nullptr); if (alertable && result == X_STATUS_USER_APC) { XThread::GetCurrentThread()->DeliverAPCs(); } return result; } uint32_t xeNtWaitForMultipleObjectsEx(uint32_t count, rex::be* handles, uint32_t wait_type, uint32_t wait_mode, uint32_t alertable, uint64_t* timeout_ptr) { assert_true(wait_type <= 1); std::vector> objects; for (uint32_t n = 0; n < count; n++) { uint32_t object_handle = handles[n]; auto object = REX_KERNEL_OBJECTS()->LookupObject(object_handle); if (!object) { return X_STATUS_INVALID_PARAMETER; } objects.push_back(std::move(object)); } auto result = XObject::WaitMultiple(count, reinterpret_cast(objects.data()), wait_type, 6, wait_mode, alertable, timeout_ptr); if (alertable && result == X_STATUS_USER_APC) { XThread::GetCurrentThread()->DeliverAPCs(); } return result; } u32 NtWaitForMultipleObjectsEx_entry(u32 count, mapped_u32 handles, u32 wait_type, u32 wait_mode, u32 alertable, mapped_u64 timeout_ptr) { uint64_t timeout = timeout_ptr ? static_cast(*timeout_ptr) : 0u; return xeNtWaitForMultipleObjectsEx(count, handles, wait_type, wait_mode, alertable, timeout_ptr ? &timeout : nullptr); } u32 NtSignalAndWaitForSingleObjectEx_entry(u32 signal_handle, u32 wait_handle, u32 alertable, u32 r6, mapped_u64 timeout_ptr) { X_STATUS result = X_STATUS_SUCCESS; auto signal_object = REX_KERNEL_OBJECTS()->LookupObject(signal_handle); auto wait_object = REX_KERNEL_OBJECTS()->LookupObject(wait_handle); if (signal_object && wait_object) { uint64_t timeout = timeout_ptr ? static_cast(*timeout_ptr) : 0u; result = XObject::SignalAndWait(signal_object.get(), wait_object.get(), 3, 1, alertable, timeout_ptr ? &timeout : nullptr); } else { result = X_STATUS_INVALID_HANDLE; } if (alertable && result == X_STATUS_USER_APC) { XThread::GetCurrentThread()->DeliverAPCs(); } return result; } // Guest-memory IRQL helpers - read/write current_irql directly from PCR. // Take PPCContext* explicitly so they work from any thread (including host threads // during InitializeGuestObject, dispatch thread creation, etc.). static unsigned char xeKfRaiseIrql(PPCContext* ctx, unsigned char new_irql) { auto* mem = rex::system::kernel_state()->memory(); auto pcr = mem->TranslateVirtual(static_cast(ctx->r13.u64)); uint8_t old_irql = pcr->current_irql; pcr->current_irql = new_irql; return old_irql; } static void xeKfLowerIrql(PPCContext* ctx, unsigned char new_irql) { auto* mem = rex::system::kernel_state()->memory(); auto pcr = mem->TranslateVirtual(static_cast(ctx->r13.u64)); pcr->current_irql = new_irql; } // Guest-memory spinlock helpers - store PCR address as owner (matching xenia). // PPCContext* provides r13 (PCR address) without needing XThread::GetCurrentThread(). uint32_t xeKeKfAcquireSpinLock(PPCContext* ctx, X_KSPINLOCK* lock, bool change_irql) { uint32_t old_irql = change_irql ? xeKfRaiseIrql(ctx, IRQL_DISPATCH) : 0; uint32_t pcr_addr = static_cast(ctx->r13.u64); assert_true(lock->prcb_of_owner != rex::byte_swap(pcr_addr)); // deadlock detection while (!rex::thread::atomic_cas(0u, rex::byte_swap(pcr_addr), &lock->prcb_of_owner.value)) { rex::thread::MaybeYield(); } return old_irql; } void xeKeKfReleaseSpinLock(PPCContext* ctx, X_KSPINLOCK* lock, uint32_t old_irql, bool change_irql) { assert_true(lock->prcb_of_owner == static_cast(ctx->r13.u64)); rex::thread::atomic_store_release(0u, &lock->prcb_of_owner.value); if (change_irql && old_irql < IRQL_DISPATCH) { xeKfLowerIrql(ctx, static_cast(old_irql)); } } // Guest-memory APC helpers void xeKeInitializeApc(XAPC* apc, uint32_t thread_ptr, uint32_t kernel_routine, uint32_t rundown_routine, uint32_t normal_routine, uint32_t apc_mode, uint32_t normal_context) { apc->thread_ptr = thread_ptr; apc->kernel_routine = kernel_routine; apc->rundown_routine = rundown_routine; apc->normal_routine = normal_routine; apc->type = 18; // ApcObject if (normal_routine) { apc->apc_mode = apc_mode; apc->normal_context = normal_context; } else { apc->apc_mode = 0; apc->normal_context = 0; } apc->enqueued = 0; } uint32_t xeKeInsertQueueApc(XAPC* apc, uint32_t arg1, uint32_t arg2, uint32_t priority_increment, PPCContext* ctx) { uint32_t thread_guest_pointer = apc->thread_ptr; if (!thread_guest_pointer) { return 0; } auto* mem = rex::system::kernel_state()->memory(); auto target_thread = mem->TranslateVirtual(thread_guest_pointer); auto old_irql = xeKeKfAcquireSpinLock(ctx, &target_thread->apc_lock); uint32_t result; if (!target_thread->may_queue_apcs || apc->enqueued) { result = 0; } else { apc->arg1 = arg1; apc->arg2 = arg2; auto& which_list = target_thread->apc_lists[apc->apc_mode]; if (apc->normal_routine) { which_list.InsertTail(apc, mem); } else { // Kernel-mode APCs without normal_routine go before those with one. XAPC* insertion_pos = nullptr; for (auto&& sub_apc : which_list.IterateForward(mem)) { insertion_pos = &sub_apc; if (sub_apc.normal_routine) { break; } } if (!insertion_pos) { which_list.InsertHead(apc, mem); } else { util::XeInsertHeadList(insertion_pos->list_entry.blink_ptr, &apc->list_entry, mem); } } apc->enqueued = 1; result = 1; } xeKeKfReleaseSpinLock(ctx, &target_thread->apc_lock, old_irql); return result; } u32 KfAcquireSpinLock_entry(mapped_u32 lock_ptr) { auto lock = reinterpret_cast(lock_ptr.host_address()); return xeKeKfAcquireSpinLock(current_ppc_context(), lock); } void KfReleaseSpinLock_entry(mapped_u32 lock_ptr, u32 old_irql) { auto lock = reinterpret_cast(lock_ptr.host_address()); xeKeKfReleaseSpinLock(current_ppc_context(), lock, old_irql); } void KeAcquireSpinLockAtRaisedIrql_entry(mapped_u32 lock_ptr) { auto lock = reinterpret_cast(lock_ptr.host_address()); xeKeKfAcquireSpinLock(current_ppc_context(), lock, false); } u32 KeTryToAcquireSpinLockAtRaisedIrql_entry(mapped_u32 lock_ptr) { auto lock = reinterpret_cast(lock_ptr.host_address()); auto* ctx = current_ppc_context(); uint32_t pcr_addr = static_cast(ctx->r13.u64); if (!rex::thread::atomic_cas(0u, rex::byte_swap(pcr_addr), &lock->prcb_of_owner.value)) { return 0; } return 1; } void KeReleaseSpinLockFromRaisedIrql_entry(mapped_u32 lock_ptr) { auto lock = reinterpret_cast(lock_ptr.host_address()); xeKeKfReleaseSpinLock(current_ppc_context(), lock, 0, false); } void KeEnterCriticalRegion_entry() { XThread::GetCurrentThread()->EnterCriticalRegion(); } void KeLeaveCriticalRegion_entry() { XThread::GetCurrentThread()->LeaveCriticalRegion(); } u32 KeRaiseIrqlToDpcLevel_entry() { return xeKfRaiseIrql(current_ppc_context(), IRQL_DISPATCH); } void KfLowerIrql_entry(u32 old_value) { xeKfLowerIrql(current_ppc_context(), static_cast(static_cast(old_value))); } u32 KfRaiseIrql_entry(u32 new_irql) { return xeKfRaiseIrql(current_ppc_context(), static_cast(static_cast(new_irql))); } void NtQueueApcThread_entry(u32 thread_handle, mapped_void apc_routine, mapped_void apc_routine_context, mapped_void arg1, mapped_void arg2) { auto mem = REX_KERNEL_MEMORY(); auto thread = REX_KERNEL_OBJECTS()->LookupObject(thread_handle); if (!thread) { REXKRNL_ERROR("NtQueueApcThread: Incorrect thread handle! Might cause crash"); return; } if (!apc_routine) { REXKRNL_ERROR("NtQueueApcThread: Incorrect apc routine! Might cause crash"); return; } uint32_t apc_ptr = mem->SystemHeapAlloc(XAPC::kSize); if (!apc_ptr) { return; } XAPC* apc = mem->TranslateVirtual(apc_ptr); xeKeInitializeApc(apc, thread->guest_object(), XAPC::kDummyKernelRoutine, 0, apc_routine.guest_address(), 1 /* user apc mode */, apc_routine_context.guest_address()); if (!xeKeInsertQueueApc(apc, arg1.guest_address(), arg2.guest_address(), 0, current_ppc_context())) { mem->SystemHeapFree(apc_ptr); return; } // Match Edge/Canary behavior: callback is only a wakeup hint. // APC delivery happens via alertable wait handling. thread->thread()->QueueUserCallback([]() {}); } void KeInitializeApc_entry(ppc_ptr_t apc, mapped_void thread_ptr, mapped_void kernel_routine, mapped_void rundown_routine, mapped_void normal_routine, u32 processor_mode, mapped_void normal_context) { xeKeInitializeApc(apc.host_address(), thread_ptr.guest_address(), kernel_routine.guest_address(), rundown_routine.guest_address(), normal_routine.guest_address(), processor_mode, normal_context.guest_address()); } u32 KeInsertQueueApc_entry(ppc_ptr_t apc, mapped_void arg1, mapped_void arg2, u32 priority_increment) { return xeKeInsertQueueApc(apc.host_address(), arg1.guest_address(), arg2.guest_address(), priority_increment, current_ppc_context()); } u32 KeRemoveQueueApc_entry(ppc_ptr_t apc) { uint32_t thread_guest_pointer = apc->thread_ptr; if (!thread_guest_pointer) { return 0; } auto* ctx = current_ppc_context(); auto mem = REX_KERNEL_MEMORY(); auto target_thread = mem->TranslateVirtual(thread_guest_pointer); auto old_irql = xeKeKfAcquireSpinLock(ctx, &target_thread->apc_lock); bool result = false; if (apc->enqueued) { apc->enqueued = 0; util::XeRemoveEntryList(&apc->list_entry, mem); result = true; } xeKeKfReleaseSpinLock(ctx, &target_thread->apc_lock, old_irql); return result ? 1 : 0; } u32 KiApcNormalRoutineNop_entry(u32 unk0 /* output? */, u32 unk1 /* 0x13 */) { return 0; } void KeInitializeDpc_entry(ppc_ptr_t dpc, mapped_void routine, mapped_void context) { dpc->Initialize(routine.guest_address(), context.guest_address()); } u32 KeInsertQueueDpc_entry(ppc_ptr_t dpc, u32 arg1, u32 arg2) { assert_always("DPC does not dispatch yet; going to hang!"); uint32_t list_entry_ptr = dpc.guest_address() + 4; // Lock dispatcher. auto global_lock = rex::thread::global_critical_region::AcquireDirect(); auto dpc_list = REX_KERNEL_STATE()->dpc_list(); // If already in a queue, abort. if (dpc_list->IsQueued(list_entry_ptr)) { return 0; } // Prep DPC. dpc->arg1 = (uint32_t)arg1; dpc->arg2 = (uint32_t)arg2; dpc_list->Insert(list_entry_ptr); return 1; } u32 KeRemoveQueueDpc_entry(ppc_ptr_t dpc) { bool result = false; uint32_t list_entry_ptr = dpc.guest_address() + 4; auto global_lock = rex::thread::global_critical_region::AcquireDirect(); auto dpc_list = REX_KERNEL_STATE()->dpc_list(); if (dpc_list->IsQueued(list_entry_ptr)) { dpc_list->Remove(list_entry_ptr); result = true; } return result ? 1 : 0; } // https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/blob/51e4dfcaacfdbd1a9692272931a436371492f72d/import/OpenXDK/include/xboxkrnl/xboxkrnl.h#L1372 struct X_ERWLOCK { be lock_count; // 0x0 be writers_waiting_count; // 0x4 be readers_waiting_count; // 0x8 be readers_entry_count; // 0xC X_KEVENT writer_event; // 0x10 X_KSEMAPHORE reader_semaphore; // 0x20 X_KSPINLOCK spin_lock; // 0x34 }; static_assert_size(X_ERWLOCK, 0x38); void ExInitializeReadWriteLock_entry(ppc_ptr_t lock_ptr) { lock_ptr->lock_count = -1; lock_ptr->writers_waiting_count = 0; lock_ptr->readers_waiting_count = 0; lock_ptr->readers_entry_count = 0; // Create GuestPointers to struct members with correct guest addresses ppc_ptr_t event_ptr(&lock_ptr->writer_event, lock_ptr.guest_address() + offsetof(X_ERWLOCK, writer_event)); ppc_ptr_t sem_ptr(&lock_ptr->reader_semaphore, lock_ptr.guest_address() + offsetof(X_ERWLOCK, reader_semaphore)); KeInitializeEvent_entry(event_ptr, 1, 0); KeInitializeSemaphore_entry(sem_ptr, 0, 0x7FFFFFFF); lock_ptr->spin_lock.prcb_of_owner = 0; } void ExAcquireReadWriteLockExclusive_entry(ppc_ptr_t lock_ptr) { auto* ctx = current_ppc_context(); auto old_irql = xeKeKfAcquireSpinLock(ctx, &lock_ptr->spin_lock); int32_t lock_count = ++lock_ptr->lock_count; if (!lock_count) { xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return; } lock_ptr->writers_waiting_count++; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); xeKeWaitForSingleObject(&lock_ptr->writer_event, 7, 0, 0, nullptr); } u32 ExTryToAcquireReadWriteLockExclusive_entry(ppc_ptr_t lock_ptr) { auto* ctx = current_ppc_context(); auto old_irql = xeKeKfAcquireSpinLock(ctx, &lock_ptr->spin_lock); uint32_t result; if (lock_ptr->lock_count < 0) { lock_ptr->lock_count = 0; result = 1; } else { result = 0; } xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return result; } void ExAcquireReadWriteLockShared_entry(ppc_ptr_t lock_ptr) { auto* ctx = current_ppc_context(); auto old_irql = xeKeKfAcquireSpinLock(ctx, &lock_ptr->spin_lock); int32_t lock_count = ++lock_ptr->lock_count; if (!lock_count || (lock_ptr->readers_entry_count && !lock_ptr->writers_waiting_count)) { lock_ptr->readers_entry_count++; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return; } lock_ptr->readers_waiting_count++; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); xeKeWaitForSingleObject(&lock_ptr->reader_semaphore, 7, 0, 0, nullptr); } u32 ExTryToAcquireReadWriteLockShared_entry(ppc_ptr_t lock_ptr) { auto* ctx = current_ppc_context(); auto old_irql = xeKeKfAcquireSpinLock(ctx, &lock_ptr->spin_lock); uint32_t result; if (lock_ptr->lock_count < 0 || (lock_ptr->readers_entry_count && !lock_ptr->writers_waiting_count)) { lock_ptr->lock_count++; lock_ptr->readers_entry_count++; result = 1; } else { result = 0; } xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return result; } void ExReleaseReadWriteLock_entry(ppc_ptr_t lock_ptr) { auto* ctx = current_ppc_context(); auto old_irql = xeKeKfAcquireSpinLock(ctx, &lock_ptr->spin_lock); int32_t lock_count = --lock_ptr->lock_count; if (lock_count < 0) { lock_ptr->readers_entry_count = 0; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return; } if (!lock_ptr->readers_entry_count) { auto readers_waiting_count = lock_ptr->readers_waiting_count; if (readers_waiting_count) { lock_ptr->readers_waiting_count = 0; lock_ptr->readers_entry_count = readers_waiting_count; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); xeKeReleaseSemaphore(&lock_ptr->reader_semaphore, 1, readers_waiting_count, 0); return; } } auto readers_entry_count = --lock_ptr->readers_entry_count; if (readers_entry_count) { xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); return; } lock_ptr->writers_waiting_count--; xeKeKfReleaseSpinLock(ctx, &lock_ptr->spin_lock, old_irql); xeKeSetEvent(&lock_ptr->writer_event, 1, 0); } // NOTE: This function is very commonly inlined, and probably won't be called! u32 InterlockedPushEntrySList_entry(ppc_ptr_t plist_ptr, ppc_ptr_t entry) { assert_not_null(plist_ptr); assert_not_null(entry); alignas(8) X_SLIST_HEADER old_hdr = *plist_ptr; alignas(8) X_SLIST_HEADER new_hdr = {0}; uint32_t old_head = 0; do { old_hdr = *plist_ptr; new_hdr.depth = old_hdr.depth + 1; new_hdr.sequence = old_hdr.sequence + 1; old_head = old_hdr.next.next; entry->next = old_hdr.next.next; new_hdr.next.next = entry.guest_address(); } while (!rex::thread::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr), reinterpret_cast(plist_ptr.host_address()))); return old_head; } u32 InterlockedPopEntrySList_entry(ppc_ptr_t plist_ptr) { assert_not_null(plist_ptr); uint32_t popped = 0; alignas(8) X_SLIST_HEADER old_hdr = {0}; alignas(8) X_SLIST_HEADER new_hdr = {0}; do { old_hdr = *plist_ptr; auto next = REX_KERNEL_MEMORY()->TranslateVirtual(old_hdr.next.next); if (!old_hdr.next.next) { return 0; } popped = old_hdr.next.next; new_hdr.depth = old_hdr.depth - 1; new_hdr.next.next = next->next; new_hdr.sequence = old_hdr.sequence; } while (!rex::thread::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr), reinterpret_cast(plist_ptr.host_address()))); return popped; } u32 InterlockedFlushSList_entry(ppc_ptr_t plist_ptr) { assert_not_null(plist_ptr); alignas(8) X_SLIST_HEADER old_hdr = *plist_ptr; alignas(8) X_SLIST_HEADER new_hdr = {0}; uint32_t first = 0; do { old_hdr = *plist_ptr; first = old_hdr.next.next; new_hdr.next.next = 0; new_hdr.depth = 0; new_hdr.sequence = 0; } while (!rex::thread::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr), reinterpret_cast(plist_ptr.host_address()))); return first; } REX_EXPORT_STUB(__imp__KeInsertByKeyDeviceQueue); REX_EXPORT_STUB(__imp__KeInsertDeviceQueue); REX_EXPORT_STUB(__imp__KeInsertHeadQueue); REX_EXPORT_STUB(__imp__KeInsertQueue); REX_EXPORT_STUB(__imp__KeReleaseMutant); REX_EXPORT_STUB(__imp__KeRemoveByKeyDeviceQueue); REX_EXPORT_STUB(__imp__KeRemoveDeviceQueue); REX_EXPORT_STUB(__imp__KeRemoveEntryDeviceQueue); REX_EXPORT_STUB(__imp__KeRemoveQueue); REX_EXPORT_STUB(__imp__KeSetEventBoostPriority); REX_EXPORT_STUB(__imp__KiApcNormalRoutineNop_); u32 KeSuspendThread_entry(mapped_void kthread_ptr) { auto thread = XObject::GetNativeObject(REX_KERNEL_STATE(), kthread_ptr); uint32_t old_suspend_count = 0; if (thread) { old_suspend_count = thread->suspend_count(); uint32_t discarded = 0; thread->Suspend(&discarded); } return old_suspend_count; } } // namespace rex::kernel::xboxkrnl REX_EXPORT(__imp__ExCreateThread, rex::kernel::xboxkrnl::ExCreateThread_entry) REX_EXPORT(__imp__ExTerminateThread, rex::kernel::xboxkrnl::ExTerminateThread_entry) REX_EXPORT(__imp__NtResumeThread, rex::kernel::xboxkrnl::NtResumeThread_entry) REX_EXPORT(__imp__KeResumeThread, rex::kernel::xboxkrnl::KeResumeThread_entry) REX_EXPORT(__imp__NtSuspendThread, rex::kernel::xboxkrnl::NtSuspendThread_entry) REX_EXPORT(__imp__KeSuspendThread, rex::kernel::xboxkrnl::KeSuspendThread_entry) REX_EXPORT(__imp__KeSetCurrentStackPointers, rex::kernel::xboxkrnl::KeSetCurrentStackPointers_entry) REX_EXPORT(__imp__KeSetAffinityThread, rex::kernel::xboxkrnl::KeSetAffinityThread_entry) REX_EXPORT(__imp__KeQueryBasePriorityThread, rex::kernel::xboxkrnl::KeQueryBasePriorityThread_entry) REX_EXPORT(__imp__KeSetBasePriorityThread, rex::kernel::xboxkrnl::KeSetBasePriorityThread_entry) REX_EXPORT(__imp__KeSetDisableBoostThread, rex::kernel::xboxkrnl::KeSetDisableBoostThread_entry) REX_EXPORT(__imp__KeGetCurrentProcessType, rex::kernel::xboxkrnl::KeGetCurrentProcessType_entry) REX_EXPORT(__imp__KeSetCurrentProcessType, rex::kernel::xboxkrnl::KeSetCurrentProcessType_entry) REX_EXPORT(__imp__KeQueryPerformanceFrequency, rex::kernel::xboxkrnl::KeQueryPerformanceFrequency_entry) REX_EXPORT(__imp__KeDelayExecutionThread, rex::kernel::xboxkrnl::KeDelayExecutionThread_entry) REX_EXPORT(__imp__NtYieldExecution, rex::kernel::xboxkrnl::NtYieldExecution_entry) REX_EXPORT(__imp__KeQuerySystemTime, rex::kernel::xboxkrnl::KeQuerySystemTime_entry) REX_EXPORT(__imp__KeTlsAlloc, rex::kernel::xboxkrnl::KeTlsAlloc_entry) REX_EXPORT(__imp__KeTlsFree, rex::kernel::xboxkrnl::KeTlsFree_entry) REX_EXPORT(__imp__KeTlsGetValue, rex::kernel::xboxkrnl::KeTlsGetValue_entry) REX_EXPORT(__imp__KeTlsSetValue, rex::kernel::xboxkrnl::KeTlsSetValue_entry) REX_EXPORT(__imp__KeInitializeEvent, rex::kernel::xboxkrnl::KeInitializeEvent_entry) REX_EXPORT(__imp__KeSetEvent, rex::kernel::xboxkrnl::KeSetEvent_entry) REX_EXPORT(__imp__KePulseEvent, rex::kernel::xboxkrnl::KePulseEvent_entry) REX_EXPORT(__imp__KeResetEvent, rex::kernel::xboxkrnl::KeResetEvent_entry) REX_EXPORT(__imp__NtCreateEvent, rex::kernel::xboxkrnl::NtCreateEvent_entry) REX_EXPORT(__imp__NtSetEvent, rex::kernel::xboxkrnl::NtSetEvent_entry) REX_EXPORT(__imp__NtPulseEvent, rex::kernel::xboxkrnl::NtPulseEvent_entry) REX_EXPORT(__imp__NtClearEvent, rex::kernel::xboxkrnl::NtClearEvent_entry) REX_EXPORT(__imp__KeInitializeSemaphore, rex::kernel::xboxkrnl::KeInitializeSemaphore_entry) REX_EXPORT(__imp__KeReleaseSemaphore, rex::kernel::xboxkrnl::KeReleaseSemaphore_entry) REX_EXPORT(__imp__NtCreateSemaphore, rex::kernel::xboxkrnl::NtCreateSemaphore_entry) REX_EXPORT(__imp__NtReleaseSemaphore, rex::kernel::xboxkrnl::NtReleaseSemaphore_entry) REX_EXPORT(__imp__NtCreateMutant, rex::kernel::xboxkrnl::NtCreateMutant_entry) REX_EXPORT(__imp__NtReleaseMutant, rex::kernel::xboxkrnl::NtReleaseMutant_entry) REX_EXPORT(__imp__NtCreateTimer, rex::kernel::xboxkrnl::NtCreateTimer_entry) REX_EXPORT(__imp__NtSetTimerEx, rex::kernel::xboxkrnl::NtSetTimerEx_entry) REX_EXPORT(__imp__NtCancelTimer, rex::kernel::xboxkrnl::NtCancelTimer_entry) REX_EXPORT(__imp__KeInitializeTimerEx, rex::kernel::xboxkrnl::KeInitializeTimerEx_entry) REX_EXPORT(__imp__KeWaitForSingleObject, rex::kernel::xboxkrnl::KeWaitForSingleObject_entry) REX_EXPORT(__imp__NtWaitForSingleObjectEx, rex::kernel::xboxkrnl::NtWaitForSingleObjectEx_entry) REX_EXPORT(__imp__KeWaitForMultipleObjects, rex::kernel::xboxkrnl::KeWaitForMultipleObjects_entry) REX_EXPORT(__imp__NtWaitForMultipleObjectsEx, rex::kernel::xboxkrnl::NtWaitForMultipleObjectsEx_entry) REX_EXPORT(__imp__NtSignalAndWaitForSingleObjectEx, rex::kernel::xboxkrnl::NtSignalAndWaitForSingleObjectEx_entry) REX_EXPORT(__imp__KfAcquireSpinLock, rex::kernel::xboxkrnl::KfAcquireSpinLock_entry) REX_EXPORT(__imp__KfReleaseSpinLock, rex::kernel::xboxkrnl::KfReleaseSpinLock_entry) REX_EXPORT(__imp__KeAcquireSpinLockAtRaisedIrql, rex::kernel::xboxkrnl::KeAcquireSpinLockAtRaisedIrql_entry) REX_EXPORT(__imp__KeTryToAcquireSpinLockAtRaisedIrql, rex::kernel::xboxkrnl::KeTryToAcquireSpinLockAtRaisedIrql_entry) REX_EXPORT(__imp__KeReleaseSpinLockFromRaisedIrql, rex::kernel::xboxkrnl::KeReleaseSpinLockFromRaisedIrql_entry) REX_EXPORT(__imp__KeEnterCriticalRegion, rex::kernel::xboxkrnl::KeEnterCriticalRegion_entry) REX_EXPORT(__imp__KeLeaveCriticalRegion, rex::kernel::xboxkrnl::KeLeaveCriticalRegion_entry) REX_EXPORT(__imp__KeRaiseIrqlToDpcLevel, rex::kernel::xboxkrnl::KeRaiseIrqlToDpcLevel_entry) REX_EXPORT(__imp__KfLowerIrql, rex::kernel::xboxkrnl::KfLowerIrql_entry) REX_EXPORT(__imp__NtQueueApcThread, rex::kernel::xboxkrnl::NtQueueApcThread_entry) REX_EXPORT(__imp__KeInitializeApc, rex::kernel::xboxkrnl::KeInitializeApc_entry) REX_EXPORT(__imp__KeInsertQueueApc, rex::kernel::xboxkrnl::KeInsertQueueApc_entry) REX_EXPORT(__imp__KeRemoveQueueApc, rex::kernel::xboxkrnl::KeRemoveQueueApc_entry) REX_EXPORT(__imp__KiApcNormalRoutineNop, rex::kernel::xboxkrnl::KiApcNormalRoutineNop_entry) REX_EXPORT(__imp__KeInitializeDpc, rex::kernel::xboxkrnl::KeInitializeDpc_entry) REX_EXPORT(__imp__KeInsertQueueDpc, rex::kernel::xboxkrnl::KeInsertQueueDpc_entry) REX_EXPORT(__imp__KeRemoveQueueDpc, rex::kernel::xboxkrnl::KeRemoveQueueDpc_entry) REX_EXPORT(__imp__ExInitializeReadWriteLock, rex::kernel::xboxkrnl::ExInitializeReadWriteLock_entry) REX_EXPORT(__imp__ExAcquireReadWriteLockExclusive, rex::kernel::xboxkrnl::ExAcquireReadWriteLockExclusive_entry) REX_EXPORT(__imp__ExTryToAcquireReadWriteLockExclusive, rex::kernel::xboxkrnl::ExTryToAcquireReadWriteLockExclusive_entry) REX_EXPORT(__imp__ExAcquireReadWriteLockShared, rex::kernel::xboxkrnl::ExAcquireReadWriteLockShared_entry) REX_EXPORT(__imp__ExTryToAcquireReadWriteLockShared, rex::kernel::xboxkrnl::ExTryToAcquireReadWriteLockShared_entry) REX_EXPORT(__imp__ExReleaseReadWriteLock, rex::kernel::xboxkrnl::ExReleaseReadWriteLock_entry) REX_EXPORT(__imp__InterlockedPushEntrySList, rex::kernel::xboxkrnl::InterlockedPushEntrySList_entry) REX_EXPORT(__imp__InterlockedPopEntrySList, rex::kernel::xboxkrnl::InterlockedPopEntrySList_entry) REX_EXPORT(__imp__InterlockedFlushSList, rex::kernel::xboxkrnl::InterlockedFlushSList_entry) REX_EXPORT_STUB(__imp__KeAlertResumeThread); REX_EXPORT_STUB(__imp__KeAlertThread); REX_EXPORT_STUB(__imp__KeBoostPriorityThread); REX_EXPORT_STUB(__imp__KeCancelTimer); REX_EXPORT_STUB(__imp__KeConnectInterrupt); REX_EXPORT_STUB(__imp__KeContextFromKframes); REX_EXPORT_STUB(__imp__KeContextToKframes); REX_EXPORT_STUB(__imp__KeDisconnectInterrupt); REX_EXPORT_STUB(__imp__KeFlushCacheRange); REX_EXPORT_STUB(__imp__KeFlushCurrentEntireTb); REX_EXPORT_STUB(__imp__KeFlushEntireTb); REX_EXPORT_STUB(__imp__KeFlushMultipleTb); REX_EXPORT_STUB(__imp__KeFlushUserModeCurrentTb); REX_EXPORT_STUB(__imp__KeFlushUserModeTb); REX_EXPORT_STUB(__imp__KeInitializeDeviceQueue); REX_EXPORT_STUB(__imp__KeInitializeInterrupt); REX_EXPORT_STUB(__imp__KeInitializeMutant); REX_EXPORT_STUB(__imp__KeInitializeQueue); // REX_EXPORT_STUB(__imp__KeInitializeTimerEx); -- implemented below REX_EXPORT_STUB(__imp__KeIpiGenericCall); REX_EXPORT_STUB(__imp__KeQueryBackgroundProcessors); REX_EXPORT_STUB(__imp__KeQueryInterruptTime); REX_EXPORT_STUB(__imp__KeRegisterDriverNotification); REX_EXPORT_STUB(__imp__KeRestoreFloatingPointState); REX_EXPORT_STUB(__imp__KeRestoreVectorUnitState); REX_EXPORT_STUB(__imp__KeRetireDpcList); REX_EXPORT_STUB(__imp__KeRundownQueue); REX_EXPORT_STUB(__imp__KeSaveFloatingPointState); REX_EXPORT_STUB(__imp__KeSaveVectorUnitState); REX_EXPORT_STUB(__imp__KeSetBackgroundProcessors); REX_EXPORT_STUB(__imp__KeSetPriorityClassThread); REX_EXPORT_STUB(__imp__KeSetPriorityThread); REX_EXPORT_STUB(__imp__KeSetTimer); REX_EXPORT_STUB(__imp__KeSetTimerEx); REX_EXPORT_STUB(__imp__KeStallExecutionProcessor); REX_EXPORT_STUB(__imp__KeSweepDcacheRange); REX_EXPORT_STUB(__imp__KeSweepIcacheRange); REX_EXPORT_STUB(__imp__KeTestAlertThread); REX_EXPORT(__imp__KfRaiseIrql, rex::kernel::xboxkrnl::KfRaiseIrql_entry)