- The guest will check for 0x40000000 and replace it with 0xb7 (ERROR_ALREADY_EXISTS), which is the correct return value. For example, see: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexa
1399 lines
44 KiB
C++
1399 lines
44 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include "xenia/base/atomic.h"
|
|
#include "xenia/base/clock.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/mutex.h"
|
|
#include "xenia/cpu/processor.h"
|
|
#include "xenia/kernel/kernel_state.h"
|
|
#include "xenia/kernel/user_module.h"
|
|
#include "xenia/kernel/util/shim_utils.h"
|
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h"
|
|
#include "xenia/kernel/xevent.h"
|
|
#include "xenia/kernel/xmutant.h"
|
|
#include "xenia/kernel/xsemaphore.h"
|
|
#include "xenia/kernel/xthread.h"
|
|
#include "xenia/kernel/xtimer.h"
|
|
#include "xenia/xbox.h"
|
|
|
|
namespace xe {
|
|
namespace kernel {
|
|
namespace xboxkrnl {
|
|
|
|
// 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 <typename T>
|
|
object_ref<T> 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<X_OBJECT_ATTRIBUTES*>(
|
|
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<T>(handle);
|
|
if (obj) {
|
|
// The caller will do as it likes.
|
|
obj->ReleaseHandle();
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
dword_result_t ExCreateThread_entry(lpdword_t handle_ptr, dword_t stack_size,
|
|
lpdword_t thread_id_ptr,
|
|
dword_t xapi_thread_startup,
|
|
lpvoid_t start_address,
|
|
lpvoid_t start_context,
|
|
dword_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?
|
|
|
|
// Inherit default stack size
|
|
uint32_t actual_stack_size = stack_size;
|
|
|
|
if (actual_stack_size == 0) {
|
|
actual_stack_size = 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<XThread>(
|
|
new XThread(kernel_state(), actual_stack_size, xapi_thread_startup,
|
|
start_address.guest_address(), start_context.guest_address(),
|
|
creation_flags, true));
|
|
|
|
X_STATUS result = thread->Create();
|
|
if (XFAILED(result)) {
|
|
// Failed!
|
|
XELOGE("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();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExCreateThread, kThreading, kImplemented);
|
|
|
|
dword_result_t ExTerminateThread_entry(dword_t exit_code) {
|
|
XThread* thread = XThread::GetCurrentThread();
|
|
|
|
// NOTE: this kills us right now. We won't return from it.
|
|
return thread->Exit(exit_code);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExTerminateThread, kThreading, kImplemented);
|
|
|
|
dword_result_t NtResumeThread_entry(dword_t handle,
|
|
lpdword_t suspend_count_ptr) {
|
|
X_RESULT result = X_STATUS_INVALID_HANDLE;
|
|
uint32_t suspend_count = 0;
|
|
|
|
auto thread = kernel_state()->object_table()->LookupObject<XThread>(handle);
|
|
if (thread) {
|
|
result = thread->Resume(&suspend_count);
|
|
}
|
|
if (suspend_count_ptr) {
|
|
*suspend_count_ptr = suspend_count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtResumeThread, kThreading, kImplemented);
|
|
|
|
dword_result_t KeResumeThread_entry(lpvoid_t thread_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
auto thread = XObject::GetNativeObject<XThread>(kernel_state(), thread_ptr);
|
|
if (thread) {
|
|
result = thread->Resume();
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeResumeThread, kThreading, kImplemented);
|
|
|
|
dword_result_t NtSuspendThread_entry(dword_t handle,
|
|
lpdword_t suspend_count_ptr) {
|
|
X_RESULT result = X_STATUS_SUCCESS;
|
|
uint32_t suspend_count = 0;
|
|
|
|
auto thread = kernel_state()->object_table()->LookupObject<XThread>(handle);
|
|
if (thread) {
|
|
result = thread->Suspend(&suspend_count);
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
if (suspend_count_ptr) {
|
|
*suspend_count_ptr = suspend_count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtSuspendThread, kThreading, kImplemented);
|
|
|
|
void KeSetCurrentStackPointers_entry(lpvoid_t stack_ptr,
|
|
pointer_t<X_KTHREAD> thread,
|
|
lpvoid_t stack_alloc_base,
|
|
lpvoid_t stack_base,
|
|
lpvoid_t stack_limit) {
|
|
auto current_thread = XThread::GetCurrentThread();
|
|
auto context = current_thread->thread_state()->context();
|
|
auto pcr = kernel_memory()->TranslateVirtual<X_KPCR*>(
|
|
static_cast<uint32_t>(context->r[13]));
|
|
|
|
thread->stack_alloc_base = stack_alloc_base.value();
|
|
thread->stack_base = stack_base.value();
|
|
thread->stack_limit = stack_limit.value();
|
|
pcr->stack_base_ptr = stack_base.guest_address();
|
|
pcr->stack_end_ptr = stack_limit.guest_address();
|
|
context->r[1] = stack_ptr.guest_address();
|
|
|
|
// If a fiber is set, and the thread matches, reenter to avoid issues with
|
|
// host stack overflowing.
|
|
if (thread->fiber_ptr &&
|
|
current_thread->guest_object() == thread.guest_address()) {
|
|
current_thread->Reenter(static_cast<uint32_t>(context->lr));
|
|
}
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeSetCurrentStackPointers, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
dword_result_t KeSetAffinityThread_entry(lpvoid_t thread_ptr, dword_t affinity,
|
|
lpdword_t previous_affinity_ptr) {
|
|
// 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<XThread>(kernel_state(), thread_ptr);
|
|
if (thread) {
|
|
if (previous_affinity_ptr) {
|
|
*previous_affinity_ptr = uint32_t(1) << thread->active_cpu();
|
|
}
|
|
thread->SetAffinity(affinity);
|
|
}
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeSetAffinityThread, kThreading, kImplemented);
|
|
|
|
dword_result_t KeQueryBasePriorityThread_entry(lpvoid_t thread_ptr) {
|
|
int32_t priority = 0;
|
|
|
|
auto thread = XObject::GetNativeObject<XThread>(kernel_state(), thread_ptr);
|
|
if (thread) {
|
|
priority = thread->QueryPriority();
|
|
}
|
|
|
|
return priority;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeQueryBasePriorityThread, kThreading, kImplemented);
|
|
|
|
dword_result_t KeSetBasePriorityThread_entry(lpvoid_t thread_ptr,
|
|
dword_t increment) {
|
|
int32_t prev_priority = 0;
|
|
auto thread = XObject::GetNativeObject<XThread>(kernel_state(), thread_ptr);
|
|
|
|
if (thread) {
|
|
prev_priority = thread->QueryPriority();
|
|
thread->SetPriority(increment);
|
|
}
|
|
|
|
return prev_priority;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeSetBasePriorityThread, kThreading, kImplemented);
|
|
|
|
dword_result_t KeSetDisableBoostThread_entry(lpvoid_t thread_ptr,
|
|
dword_t disabled) {
|
|
auto thread = XObject::GetNativeObject<XThread>(kernel_state(), thread_ptr);
|
|
if (thread) {
|
|
// Uhm?
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeSetDisableBoostThread, kThreading, kImplemented);
|
|
|
|
dword_result_t KeGetCurrentProcessType_entry() {
|
|
return kernel_state()->process_type();
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeGetCurrentProcessType, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
void KeSetCurrentProcessType_entry(dword_t type) {
|
|
// One of X_PROCTYPE_?
|
|
|
|
assert_true(type <= 2);
|
|
|
|
kernel_state()->set_process_type(type);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeSetCurrentProcessType, kThreading, kImplemented);
|
|
|
|
dword_result_t KeQueryPerformanceFrequency_entry() {
|
|
uint64_t result = Clock::guest_tick_frequency();
|
|
return static_cast<uint32_t>(result);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeQueryPerformanceFrequency, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
dword_result_t KeDelayExecutionThread_entry(dword_t processor_mode,
|
|
dword_t alertable,
|
|
lpqword_t interval_ptr) {
|
|
XThread* thread = XThread::GetCurrentThread();
|
|
X_STATUS result = thread->Delay(processor_mode, alertable, *interval_ptr);
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(KeDelayExecutionThread, kThreading, kImplemented,
|
|
kBlocking, kHighFrequency);
|
|
|
|
dword_result_t NtYieldExecution_entry() {
|
|
auto thread = XThread::GetCurrentThread();
|
|
thread->Delay(0, 0, 0);
|
|
return 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(NtYieldExecution, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
void KeQuerySystemTime_entry(lpqword_t time_ptr) {
|
|
uint64_t time = Clock::QueryGuestSystemTime();
|
|
if (time_ptr) {
|
|
*time_ptr = time;
|
|
}
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeQuerySystemTime, kThreading, kImplemented);
|
|
|
|
// https://msdn.microsoft.com/en-us/library/ms686801
|
|
dword_result_t KeTlsAlloc_entry() {
|
|
uint32_t slot = kernel_state()->AllocateTLS();
|
|
XThread::GetCurrentThread()->SetTLSValue(slot, 0);
|
|
|
|
return slot;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeTlsAlloc, kThreading, kImplemented);
|
|
|
|
// https://msdn.microsoft.com/en-us/library/ms686804
|
|
dword_result_t KeTlsFree_entry(dword_t tls_index) {
|
|
if (tls_index == X_TLS_OUT_OF_INDEXES) {
|
|
return 0;
|
|
}
|
|
|
|
kernel_state()->FreeTLS(tls_index);
|
|
return 1;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeTlsFree, kThreading, kImplemented);
|
|
|
|
// https://msdn.microsoft.com/en-us/library/ms686812
|
|
dword_result_t KeTlsGetValue_entry(dword_t 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;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeTlsGetValue, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
// https://msdn.microsoft.com/en-us/library/ms686818
|
|
dword_result_t KeTlsSetValue_entry(dword_t tls_index, dword_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)) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeTlsSetValue, kThreading, kImplemented);
|
|
|
|
void KeInitializeEvent_entry(pointer_t<X_KEVENT> event_ptr, dword_t event_type,
|
|
dword_t initial_state) {
|
|
event_ptr.Zero();
|
|
event_ptr->header.type = event_type;
|
|
event_ptr->header.signal_state = (uint32_t)initial_state;
|
|
auto ev =
|
|
XObject::GetNativeObject<XEvent>(kernel_state(), event_ptr, event_type);
|
|
if (!ev) {
|
|
assert_always();
|
|
return;
|
|
}
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeInitializeEvent, kThreading, kImplemented);
|
|
|
|
uint32_t xeKeSetEvent(X_KEVENT* event_ptr, uint32_t increment, uint32_t wait) {
|
|
auto ev = XObject::GetNativeObject<XEvent>(kernel_state(), event_ptr);
|
|
if (!ev) {
|
|
assert_always();
|
|
return 0;
|
|
}
|
|
|
|
return ev->Set(increment, !!wait);
|
|
}
|
|
|
|
dword_result_t KeSetEvent_entry(pointer_t<X_KEVENT> event_ptr,
|
|
dword_t increment, dword_t wait) {
|
|
return xeKeSetEvent(event_ptr, increment, wait);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeSetEvent, kThreading, kImplemented, kHighFrequency);
|
|
|
|
dword_result_t KePulseEvent_entry(pointer_t<X_KEVENT> event_ptr,
|
|
dword_t increment, dword_t wait) {
|
|
auto ev = XObject::GetNativeObject<XEvent>(kernel_state(), event_ptr);
|
|
if (!ev) {
|
|
assert_always();
|
|
return 0;
|
|
}
|
|
|
|
return ev->Pulse(increment, !!wait);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KePulseEvent, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
dword_result_t KeResetEvent_entry(pointer_t<X_KEVENT> event_ptr) {
|
|
auto ev = XObject::GetNativeObject<XEvent>(kernel_state(), event_ptr);
|
|
if (!ev) {
|
|
assert_always();
|
|
return 0;
|
|
}
|
|
|
|
return ev->Reset();
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeResetEvent, kThreading, kImplemented);
|
|
|
|
dword_result_t NtCreateEvent_entry(
|
|
lpdword_t handle_ptr, pointer_t<X_OBJECT_ATTRIBUTES> obj_attributes_ptr,
|
|
dword_t event_type, dword_t initial_state) {
|
|
// Check for an existing timer with the same name.
|
|
auto existing_object =
|
|
LookupNamedObject<XEvent>(kernel_state(), obj_attributes_ptr);
|
|
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<XEvent>(new XEvent(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);
|
|
}
|
|
|
|
if (handle_ptr) {
|
|
*handle_ptr = ev->handle();
|
|
}
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtCreateEvent, kThreading, kImplemented);
|
|
|
|
uint32_t xeNtSetEvent(uint32_t handle, xe::be<uint32_t>* previous_state_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto ev = kernel_state()->object_table()->LookupObject<XEvent>(handle);
|
|
if (ev) {
|
|
int32_t was_signalled = ev->Set(0, false);
|
|
if (previous_state_ptr) {
|
|
*previous_state_ptr = static_cast<uint32_t>(was_signalled);
|
|
}
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
dword_result_t NtSetEvent_entry(dword_t handle, lpdword_t previous_state_ptr) {
|
|
return xeNtSetEvent(handle, previous_state_ptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(NtSetEvent, kThreading, kImplemented, kHighFrequency);
|
|
|
|
dword_result_t NtPulseEvent_entry(dword_t handle,
|
|
lpdword_t previous_state_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto ev = kernel_state()->object_table()->LookupObject<XEvent>(handle);
|
|
if (ev) {
|
|
int32_t was_signalled = ev->Pulse(0, false);
|
|
if (previous_state_ptr) {
|
|
*previous_state_ptr = static_cast<uint32_t>(was_signalled);
|
|
}
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(NtPulseEvent, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
uint32_t xeNtClearEvent(uint32_t handle) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto ev = kernel_state()->object_table()->LookupObject<XEvent>(handle);
|
|
if (ev) {
|
|
ev->Reset();
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
dword_result_t NtClearEvent_entry(dword_t handle) {
|
|
return xeNtClearEvent(handle);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(NtClearEvent, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff552150(v=vs.85).aspx
|
|
void KeInitializeSemaphore_entry(pointer_t<X_KSEMAPHORE> semaphore_ptr,
|
|
dword_t count, dword_t 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<XSemaphore>(kernel_state(), semaphore_ptr,
|
|
5 /* SemaphoreObject */);
|
|
if (!sem) {
|
|
assert_always();
|
|
return;
|
|
}
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeInitializeSemaphore, kThreading, kImplemented);
|
|
|
|
uint32_t xeKeReleaseSemaphore(X_KSEMAPHORE* semaphore_ptr, uint32_t increment,
|
|
uint32_t adjustment, uint32_t wait) {
|
|
auto sem =
|
|
XObject::GetNativeObject<XSemaphore>(kernel_state(), semaphore_ptr);
|
|
if (!sem) {
|
|
assert_always();
|
|
return 0;
|
|
}
|
|
|
|
// TODO(benvanik): increment thread priority?
|
|
// TODO(benvanik): wait?
|
|
|
|
return sem->ReleaseSemaphore(adjustment);
|
|
}
|
|
|
|
dword_result_t KeReleaseSemaphore_entry(pointer_t<X_KSEMAPHORE> semaphore_ptr,
|
|
dword_t increment, dword_t adjustment,
|
|
dword_t wait) {
|
|
return xeKeReleaseSemaphore(semaphore_ptr, increment, adjustment, wait);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeReleaseSemaphore, kThreading, kImplemented);
|
|
|
|
dword_result_t NtCreateSemaphore_entry(lpdword_t handle_ptr,
|
|
lpvoid_t obj_attributes_ptr,
|
|
dword_t count, dword_t limit) {
|
|
// Check for an existing semaphore with the same name.
|
|
auto existing_object =
|
|
LookupNamedObject<XSemaphore>(kernel_state(), obj_attributes_ptr);
|
|
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<XSemaphore>(new XSemaphore(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);
|
|
}
|
|
|
|
if (handle_ptr) {
|
|
*handle_ptr = sem->handle();
|
|
}
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtCreateSemaphore, kThreading, kImplemented);
|
|
|
|
dword_result_t NtReleaseSemaphore_entry(dword_t sem_handle,
|
|
dword_t release_count,
|
|
lpdword_t previous_count_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
int32_t previous_count = 0;
|
|
|
|
auto sem =
|
|
kernel_state()->object_table()->LookupObject<XSemaphore>(sem_handle);
|
|
if (sem) {
|
|
previous_count = sem->ReleaseSemaphore((int32_t)release_count);
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
if (previous_count_ptr) {
|
|
*previous_count_ptr = (uint32_t)previous_count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(NtReleaseSemaphore, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
dword_result_t NtCreateMutant_entry(
|
|
lpdword_t handle_out, pointer_t<X_OBJECT_ATTRIBUTES> obj_attributes,
|
|
dword_t initial_owner) {
|
|
// Check for an existing timer with the same name.
|
|
auto existing_object = LookupNamedObject<XMutant>(
|
|
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<XMutant>(new XMutant(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);
|
|
}
|
|
|
|
if (handle_out) {
|
|
*handle_out = mutant->handle();
|
|
}
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtCreateMutant, kThreading, kImplemented);
|
|
|
|
dword_result_t NtReleaseMutant_entry(dword_t mutant_handle, dword_t 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 =
|
|
kernel_state()->object_table()->LookupObject<XMutant>(mutant_handle);
|
|
if (mutant) {
|
|
result = mutant->ReleaseMutant(priority_increment, abandon, wait);
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtReleaseMutant, kThreading, kImplemented);
|
|
|
|
dword_result_t NtCreateTimer_entry(lpdword_t handle_ptr,
|
|
lpvoid_t obj_attributes_ptr,
|
|
dword_t timer_type) {
|
|
// timer_type = NotificationTimer (0) or SynchronizationTimer (1)
|
|
|
|
// Check for an existing timer with the same name.
|
|
auto existing_object =
|
|
LookupNamedObject<XTimer>(kernel_state(), obj_attributes_ptr);
|
|
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<XTimer>(new XTimer(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);
|
|
}
|
|
|
|
if (handle_ptr) {
|
|
*handle_ptr = timer->handle();
|
|
}
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtCreateTimer, kThreading, kImplemented);
|
|
|
|
dword_result_t NtSetTimerEx_entry(dword_t timer_handle, lpqword_t due_time_ptr,
|
|
lpvoid_t routine_ptr /*PTIMERAPCROUTINE*/,
|
|
dword_t unk_one, lpvoid_t routine_arg,
|
|
dword_t resume, dword_t period_ms,
|
|
dword_t 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 =
|
|
kernel_state()->object_table()->LookupObject<XTimer>(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;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtSetTimerEx, kThreading, kImplemented);
|
|
|
|
dword_result_t NtCancelTimer_entry(dword_t timer_handle,
|
|
lpdword_t current_state_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto timer =
|
|
kernel_state()->object_table()->LookupObject<XTimer>(timer_handle);
|
|
if (timer) {
|
|
result = timer->Cancel();
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
if (current_state_ptr) {
|
|
*current_state_ptr = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtCancelTimer, kThreading, kImplemented);
|
|
|
|
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<XObject>(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);
|
|
|
|
return result;
|
|
}
|
|
|
|
dword_result_t KeWaitForSingleObject_entry(lpvoid_t object_ptr,
|
|
dword_t wait_reason,
|
|
dword_t processor_mode,
|
|
dword_t alertable,
|
|
lpqword_t timeout_ptr) {
|
|
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
|
return xeKeWaitForSingleObject(object_ptr, wait_reason, processor_mode,
|
|
alertable, timeout_ptr ? &timeout : nullptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(KeWaitForSingleObject, kThreading, kImplemented,
|
|
kBlocking, kHighFrequency);
|
|
|
|
dword_result_t NtWaitForSingleObjectEx_entry(dword_t object_handle,
|
|
dword_t wait_mode,
|
|
dword_t alertable,
|
|
lpqword_t timeout_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto object =
|
|
kernel_state()->object_table()->LookupObject<XObject>(object_handle);
|
|
if (object) {
|
|
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
|
result =
|
|
object->Wait(3, wait_mode, alertable, timeout_ptr ? &timeout : nullptr);
|
|
} else {
|
|
result = X_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(NtWaitForSingleObjectEx, kThreading, kImplemented,
|
|
kBlocking, kHighFrequency);
|
|
|
|
dword_result_t KeWaitForMultipleObjects_entry(
|
|
dword_t count, lpdword_t objects_ptr, dword_t wait_type,
|
|
dword_t wait_reason, dword_t processor_mode, dword_t alertable,
|
|
lpqword_t timeout_ptr, lpvoid_t wait_block_array_ptr) {
|
|
assert_true(wait_type <= 1);
|
|
|
|
std::vector<object_ref<XObject>> objects;
|
|
for (uint32_t n = 0; n < count; n++) {
|
|
auto object_ptr = kernel_memory()->TranslateVirtual(objects_ptr[n]);
|
|
auto object_ref =
|
|
XObject::GetNativeObject<XObject>(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<uint64_t>(*timeout_ptr) : 0u;
|
|
return XObject::WaitMultiple(uint32_t(objects.size()),
|
|
reinterpret_cast<XObject**>(objects.data()),
|
|
wait_type, wait_reason, processor_mode,
|
|
alertable, timeout_ptr ? &timeout : nullptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(KeWaitForMultipleObjects, kThreading, kImplemented,
|
|
kBlocking, kHighFrequency);
|
|
|
|
uint32_t xeNtWaitForMultipleObjectsEx(uint32_t count, xe::be<uint32_t>* handles,
|
|
uint32_t wait_type, uint32_t wait_mode,
|
|
uint32_t alertable,
|
|
uint64_t* timeout_ptr) {
|
|
assert_true(wait_type <= 1);
|
|
|
|
std::vector<object_ref<XObject>> objects;
|
|
for (uint32_t n = 0; n < count; n++) {
|
|
uint32_t object_handle = handles[n];
|
|
auto object =
|
|
kernel_state()->object_table()->LookupObject<XObject>(object_handle);
|
|
if (!object) {
|
|
return X_STATUS_INVALID_PARAMETER;
|
|
}
|
|
objects.push_back(std::move(object));
|
|
}
|
|
|
|
return XObject::WaitMultiple(count,
|
|
reinterpret_cast<XObject**>(objects.data()),
|
|
wait_type, 6, wait_mode, alertable, timeout_ptr);
|
|
}
|
|
|
|
dword_result_t NtWaitForMultipleObjectsEx_entry(
|
|
dword_t count, lpdword_t handles, dword_t wait_type, dword_t wait_mode,
|
|
dword_t alertable, lpqword_t timeout_ptr) {
|
|
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
|
return xeNtWaitForMultipleObjectsEx(count, handles, wait_type, wait_mode,
|
|
alertable,
|
|
timeout_ptr ? &timeout : nullptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(NtWaitForMultipleObjectsEx, kThreading, kImplemented,
|
|
kBlocking, kHighFrequency);
|
|
|
|
dword_result_t NtSignalAndWaitForSingleObjectEx_entry(dword_t signal_handle,
|
|
dword_t wait_handle,
|
|
dword_t alertable,
|
|
dword_t r6,
|
|
lpqword_t timeout_ptr) {
|
|
X_STATUS result = X_STATUS_SUCCESS;
|
|
|
|
auto signal_object =
|
|
kernel_state()->object_table()->LookupObject<XObject>(signal_handle);
|
|
auto wait_object =
|
|
kernel_state()->object_table()->LookupObject<XObject>(wait_handle);
|
|
if (signal_object && wait_object) {
|
|
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*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;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(NtSignalAndWaitForSingleObjectEx, kThreading,
|
|
kImplemented, kBlocking, kHighFrequency);
|
|
|
|
uint32_t xeKeKfAcquireSpinLock(uint32_t* lock) {
|
|
// XELOGD(
|
|
// "KfAcquireSpinLock({:08X})",
|
|
// lock_ptr);
|
|
|
|
// Lock.
|
|
while (!xe::atomic_cas(0, 1, lock)) {
|
|
// Spin!
|
|
// TODO(benvanik): error on deadlock?
|
|
xe::threading::MaybeYield();
|
|
}
|
|
|
|
// Raise IRQL to DISPATCH.
|
|
XThread* thread = XThread::GetCurrentThread();
|
|
auto old_irql = thread->RaiseIrql(2);
|
|
|
|
return old_irql;
|
|
}
|
|
|
|
dword_result_t KfAcquireSpinLock_entry(lpdword_t lock_ptr) {
|
|
auto lock = reinterpret_cast<uint32_t*>(lock_ptr.host_address());
|
|
return xeKeKfAcquireSpinLock(lock);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(KfAcquireSpinLock, kThreading, kImplemented, kBlocking,
|
|
kHighFrequency);
|
|
|
|
void xeKeKfReleaseSpinLock(uint32_t* lock, dword_t old_irql) {
|
|
// Restore IRQL.
|
|
XThread* thread = XThread::GetCurrentThread();
|
|
thread->LowerIrql(old_irql);
|
|
|
|
// Unlock.
|
|
xe::atomic_dec(lock);
|
|
}
|
|
|
|
void KfReleaseSpinLock_entry(lpdword_t lock_ptr, dword_t old_irql) {
|
|
auto lock = reinterpret_cast<uint32_t*>(lock_ptr.host_address());
|
|
xeKeKfReleaseSpinLock(lock, old_irql);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KfReleaseSpinLock, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
void KeAcquireSpinLockAtRaisedIrql_entry(lpdword_t lock_ptr) {
|
|
// Lock.
|
|
auto lock = reinterpret_cast<uint32_t*>(lock_ptr.host_address());
|
|
while (!xe::atomic_cas(0, 1, lock)) {
|
|
// Spin!
|
|
// TODO(benvanik): error on deadlock?
|
|
}
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT3(KeAcquireSpinLockAtRaisedIrql, kThreading,
|
|
kImplemented, kBlocking, kHighFrequency);
|
|
|
|
dword_result_t KeTryToAcquireSpinLockAtRaisedIrql_entry(lpdword_t lock_ptr) {
|
|
// Lock.
|
|
auto lock = reinterpret_cast<uint32_t*>(lock_ptr.host_address());
|
|
if (!xe::atomic_cas(0, 1, lock)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT4(KeTryToAcquireSpinLockAtRaisedIrql, kThreading,
|
|
kImplemented, kBlocking, kHighFrequency, kSketchy);
|
|
|
|
void KeReleaseSpinLockFromRaisedIrql_entry(lpdword_t lock_ptr) {
|
|
// Unlock.
|
|
auto lock = reinterpret_cast<uint32_t*>(lock_ptr.host_address());
|
|
xe::atomic_dec(lock);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeReleaseSpinLockFromRaisedIrql, kThreading,
|
|
kImplemented, kHighFrequency);
|
|
|
|
void KeEnterCriticalRegion_entry() {
|
|
XThread::GetCurrentThread()->EnterCriticalRegion();
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeEnterCriticalRegion, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
void KeLeaveCriticalRegion_entry() {
|
|
XThread::GetCurrentThread()->LeaveCriticalRegion();
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeLeaveCriticalRegion, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
dword_result_t KeRaiseIrqlToDpcLevel_entry() {
|
|
auto old_value = kernel_state()->processor()->RaiseIrql(cpu::Irql::DPC);
|
|
return (uint32_t)old_value;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeRaiseIrqlToDpcLevel, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
void KfLowerIrql_entry(dword_t old_value) {
|
|
kernel_state()->processor()->LowerIrql(
|
|
static_cast<cpu::Irql>((uint32_t)old_value));
|
|
|
|
XThread::GetCurrentThread()->CheckApcs();
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KfLowerIrql, kThreading, kImplemented, kHighFrequency);
|
|
|
|
void NtQueueApcThread_entry(dword_t thread_handle, lpvoid_t apc_routine,
|
|
lpvoid_t apc_routine_context, lpvoid_t arg1,
|
|
lpvoid_t arg2) {
|
|
auto thread =
|
|
kernel_state()->object_table()->LookupObject<XThread>(thread_handle);
|
|
|
|
if (!thread) {
|
|
XELOGE("NtQueueApcThread: Incorrect thread handle! Might cause crash");
|
|
return;
|
|
}
|
|
|
|
if (!apc_routine) {
|
|
XELOGE("NtQueueApcThread: Incorrect apc routine! Might cause crash");
|
|
return;
|
|
}
|
|
|
|
thread->EnqueueApc(apc_routine, apc_routine_context, arg1, arg2);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(NtQueueApcThread, kThreading, kImplemented);
|
|
|
|
void KeInitializeApc_entry(pointer_t<XAPC> apc, lpvoid_t thread_ptr,
|
|
lpvoid_t kernel_routine, lpvoid_t rundown_routine,
|
|
lpvoid_t normal_routine, dword_t processor_mode,
|
|
lpvoid_t normal_context) {
|
|
apc->Initialize();
|
|
apc->processor_mode = processor_mode;
|
|
apc->thread_ptr = thread_ptr.guest_address();
|
|
apc->kernel_routine = kernel_routine.guest_address();
|
|
apc->rundown_routine = rundown_routine.guest_address();
|
|
apc->normal_routine = normal_routine.guest_address();
|
|
apc->normal_context =
|
|
normal_routine.guest_address() ? normal_context.guest_address() : 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeInitializeApc, kThreading, kImplemented);
|
|
|
|
dword_result_t KeInsertQueueApc_entry(pointer_t<XAPC> apc, lpvoid_t arg1,
|
|
lpvoid_t arg2,
|
|
dword_t priority_increment) {
|
|
auto thread = XObject::GetNativeObject<XThread>(
|
|
kernel_state(),
|
|
kernel_state()->memory()->TranslateVirtual(apc->thread_ptr));
|
|
if (!thread) {
|
|
return 0;
|
|
}
|
|
|
|
// Lock thread.
|
|
thread->LockApc();
|
|
|
|
// Fail if already inserted.
|
|
if (apc->enqueued) {
|
|
thread->UnlockApc(false);
|
|
return 0;
|
|
}
|
|
|
|
// Prep APC.
|
|
apc->arg1 = arg1.guest_address();
|
|
apc->arg2 = arg2.guest_address();
|
|
apc->enqueued = 1;
|
|
|
|
auto apc_list = thread->apc_list();
|
|
|
|
uint32_t list_entry_ptr = apc.guest_address() + 8;
|
|
apc_list->Insert(list_entry_ptr);
|
|
|
|
// Unlock thread.
|
|
thread->UnlockApc(true);
|
|
|
|
return 1;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeInsertQueueApc, kThreading, kImplemented);
|
|
|
|
dword_result_t KeRemoveQueueApc_entry(pointer_t<XAPC> apc) {
|
|
bool result = false;
|
|
|
|
auto thread = XObject::GetNativeObject<XThread>(
|
|
kernel_state(),
|
|
kernel_state()->memory()->TranslateVirtual(apc->thread_ptr));
|
|
if (!thread) {
|
|
return 0;
|
|
}
|
|
|
|
thread->LockApc();
|
|
|
|
if (!apc->enqueued) {
|
|
thread->UnlockApc(false);
|
|
return 0;
|
|
}
|
|
|
|
auto apc_list = thread->apc_list();
|
|
uint32_t list_entry_ptr = apc.guest_address() + 8;
|
|
if (apc_list->IsQueued(list_entry_ptr)) {
|
|
apc_list->Remove(list_entry_ptr);
|
|
result = true;
|
|
}
|
|
|
|
thread->UnlockApc(true);
|
|
|
|
return result ? 1 : 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeRemoveQueueApc, kThreading, kImplemented);
|
|
|
|
dword_result_t KiApcNormalRoutineNop_entry(dword_t unk0 /* output? */,
|
|
dword_t unk1 /* 0x13 */) {
|
|
return 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KiApcNormalRoutineNop, kThreading, kStub);
|
|
|
|
typedef struct {
|
|
xe::be<uint32_t> unknown;
|
|
xe::be<uint32_t> flink;
|
|
xe::be<uint32_t> blink;
|
|
xe::be<uint32_t> routine;
|
|
xe::be<uint32_t> context;
|
|
xe::be<uint32_t> arg1;
|
|
xe::be<uint32_t> arg2;
|
|
} XDPC;
|
|
|
|
void KeInitializeDpc_entry(pointer_t<XDPC> dpc, lpvoid_t routine,
|
|
lpvoid_t context) {
|
|
// KDPC (maybe) 0x18 bytes?
|
|
uint32_t type = 19; // DpcObject
|
|
uint32_t importance = 0;
|
|
uint32_t number = 0; // ?
|
|
dpc->unknown = (type << 24) | (importance << 16) | (number);
|
|
dpc->flink = 0;
|
|
dpc->blink = 0;
|
|
dpc->routine = routine.guest_address();
|
|
dpc->context = context.guest_address();
|
|
dpc->arg1 = 0;
|
|
dpc->arg2 = 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeInitializeDpc, kThreading, kImplemented, kSketchy);
|
|
|
|
dword_result_t KeInsertQueueDpc_entry(pointer_t<XDPC> dpc, dword_t arg1,
|
|
dword_t 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 = xe::global_critical_region::AcquireDirect();
|
|
auto dpc_list = 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;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(KeInsertQueueDpc, kThreading, kStub, kSketchy);
|
|
|
|
dword_result_t KeRemoveQueueDpc_entry(pointer_t<XDPC> dpc) {
|
|
bool result = false;
|
|
|
|
uint32_t list_entry_ptr = dpc.guest_address() + 4;
|
|
|
|
auto global_lock = xe::global_critical_region::AcquireDirect();
|
|
auto dpc_list = kernel_state()->dpc_list();
|
|
if (dpc_list->IsQueued(list_entry_ptr)) {
|
|
dpc_list->Remove(list_entry_ptr);
|
|
result = true;
|
|
}
|
|
|
|
return result ? 1 : 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(KeRemoveQueueDpc, kThreading, kImplemented);
|
|
|
|
// https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/blob/51e4dfcaacfdbd1a9692272931a436371492f72d/import/OpenXDK/include/xboxkrnl/xboxkrnl.h#L1372
|
|
struct X_ERWLOCK {
|
|
be<int32_t> lock_count; // 0x0
|
|
be<uint32_t> writers_waiting_count; // 0x4
|
|
be<uint32_t> readers_waiting_count; // 0x8
|
|
be<uint32_t> readers_entry_count; // 0xC
|
|
X_KEVENT writer_event; // 0x10
|
|
X_KSEMAPHORE reader_semaphore; // 0x20
|
|
uint32_t spin_lock; // 0x34
|
|
};
|
|
static_assert_size(X_ERWLOCK, 0x38);
|
|
|
|
void ExInitializeReadWriteLock_entry(pointer_t<X_ERWLOCK> 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;
|
|
KeInitializeEvent_entry(&lock_ptr->writer_event, 1, 0);
|
|
KeInitializeSemaphore_entry(&lock_ptr->reader_semaphore, 0, 0x7FFFFFFF);
|
|
lock_ptr->spin_lock = 0;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExInitializeReadWriteLock, kThreading, kImplemented);
|
|
|
|
void ExAcquireReadWriteLockExclusive_entry(pointer_t<X_ERWLOCK> lock_ptr) {
|
|
auto old_irql = xeKeKfAcquireSpinLock(&lock_ptr->spin_lock);
|
|
|
|
int32_t lock_count = ++lock_ptr->lock_count;
|
|
if (!lock_count) {
|
|
xeKeKfReleaseSpinLock(&lock_ptr->spin_lock, old_irql);
|
|
return;
|
|
}
|
|
|
|
lock_ptr->writers_waiting_count++;
|
|
|
|
xeKeKfReleaseSpinLock(&lock_ptr->spin_lock, old_irql);
|
|
xeKeWaitForSingleObject(&lock_ptr->writer_event, 7, 0, 0, nullptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(ExAcquireReadWriteLockExclusive, kThreading,
|
|
kImplemented, kBlocking);
|
|
|
|
dword_result_t ExTryToAcquireReadWriteLockExclusive_entry(
|
|
pointer_t<X_ERWLOCK> lock_ptr) {
|
|
auto old_irql = xeKeKfAcquireSpinLock(&lock_ptr->spin_lock);
|
|
|
|
uint32_t result;
|
|
if (lock_ptr->lock_count < 0) {
|
|
lock_ptr->lock_count = 0;
|
|
result = 1;
|
|
} else {
|
|
result = 0;
|
|
}
|
|
|
|
xeKeKfReleaseSpinLock(&lock_ptr->spin_lock, old_irql);
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExTryToAcquireReadWriteLockExclusive, kThreading,
|
|
kImplemented);
|
|
|
|
void ExAcquireReadWriteLockShared_entry(pointer_t<X_ERWLOCK> lock_ptr) {
|
|
auto old_irql = xeKeKfAcquireSpinLock(&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(&lock_ptr->spin_lock, old_irql);
|
|
return;
|
|
}
|
|
|
|
lock_ptr->readers_waiting_count++;
|
|
|
|
xeKeKfReleaseSpinLock(&lock_ptr->spin_lock, old_irql);
|
|
xeKeWaitForSingleObject(&lock_ptr->reader_semaphore, 7, 0, 0, nullptr);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(ExAcquireReadWriteLockShared, kThreading, kImplemented,
|
|
kBlocking);
|
|
|
|
dword_result_t ExTryToAcquireReadWriteLockShared_entry(
|
|
pointer_t<X_ERWLOCK> lock_ptr) {
|
|
auto old_irql = xeKeKfAcquireSpinLock(&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(&lock_ptr->spin_lock, old_irql);
|
|
return result;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExTryToAcquireReadWriteLockShared, kThreading,
|
|
kImplemented);
|
|
|
|
void ExReleaseReadWriteLock_entry(pointer_t<X_ERWLOCK> lock_ptr) {
|
|
auto old_irql = xeKeKfAcquireSpinLock(&lock_ptr->spin_lock);
|
|
|
|
int32_t lock_count = --lock_ptr->lock_count;
|
|
|
|
if (lock_count < 0) {
|
|
lock_ptr->readers_entry_count = 0;
|
|
xeKeKfReleaseSpinLock(&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(&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(&lock_ptr->spin_lock, old_irql);
|
|
return;
|
|
}
|
|
|
|
lock_ptr->writers_waiting_count--;
|
|
xeKeKfReleaseSpinLock(&lock_ptr->spin_lock, old_irql);
|
|
xeKeSetEvent(&lock_ptr->writer_event, 1, 0);
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(ExReleaseReadWriteLock, kThreading, kImplemented);
|
|
|
|
// NOTE: This function is very commonly inlined, and probably won't be called!
|
|
pointer_result_t InterlockedPushEntrySList_entry(
|
|
pointer_t<X_SLIST_HEADER> plist_ptr, pointer_t<X_SINGLE_LIST_ENTRY> 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 (
|
|
!xe::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr),
|
|
reinterpret_cast<uint64_t*>(plist_ptr.host_address())));
|
|
|
|
return old_head;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(InterlockedPushEntrySList, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
pointer_result_t InterlockedPopEntrySList_entry(
|
|
pointer_t<X_SLIST_HEADER> 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 = kernel_memory()->TranslateVirtual<X_SINGLE_LIST_ENTRY*>(
|
|
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 (
|
|
!xe::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr),
|
|
reinterpret_cast<uint64_t*>(plist_ptr.host_address())));
|
|
|
|
return popped;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT2(InterlockedPopEntrySList, kThreading, kImplemented,
|
|
kHighFrequency);
|
|
|
|
pointer_result_t InterlockedFlushSList_entry(
|
|
pointer_t<X_SLIST_HEADER> 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 (
|
|
!xe::atomic_cas(*(uint64_t*)(&old_hdr), *(uint64_t*)(&new_hdr),
|
|
reinterpret_cast<uint64_t*>(plist_ptr.host_address())));
|
|
|
|
return first;
|
|
}
|
|
DECLARE_XBOXKRNL_EXPORT1(InterlockedFlushSList, kThreading, kImplemented);
|
|
|
|
} // namespace xboxkrnl
|
|
} // namespace kernel
|
|
} // namespace xe
|
|
|
|
DECLARE_XBOXKRNL_EMPTY_REGISTER_EXPORTS(Threading);
|