/** ****************************************************************************** * 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 "xenia/kernel/xobject.h" #include #include "xenia/base/byte_stream.h" #include "xenia/base/clock.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xenumerator.h" #include "xenia/kernel/xevent.h" #include "xenia/kernel/xfile.h" #include "xenia/kernel/xmodule.h" #include "xenia/kernel/xmutant.h" #include "xenia/kernel/xnotifylistener.h" #include "xenia/kernel/xsemaphore.h" #include "xenia/kernel/xsymboliclink.h" #include "xenia/kernel/xthread.h" #include "xenia/xbox.h" namespace xe { namespace kernel { XObject::XObject(Type type) : kernel_state_(nullptr), pointer_ref_count_(1), type_(type) { handles_.reserve(10); } XObject::XObject(KernelState* kernel_state, Type type, bool host_object) : kernel_state_(kernel_state), type_(type), pointer_ref_count_(1), guest_object_ptr_(0), allocated_guest_object_(false), host_object_(host_object) { handles_.reserve(10); // TODO: Assert kernel_state != nullptr in this constructor. if (kernel_state) { kernel_state->object_table()->AddHandle(this, nullptr); } } XObject::~XObject() { assert_true(handles_.empty()); assert_zero(pointer_ref_count_); if (allocated_guest_object_) { uint32_t ptr = guest_object_ptr_ - sizeof(X_OBJECT_HEADER); auto header = memory()->TranslateVirtual(ptr); // Free the object creation info if (header->object_type_ptr) { memory()->SystemHeapFree(header->object_type_ptr); } memory()->SystemHeapFree(ptr); } } Emulator* XObject::emulator() const { return kernel_state_->emulator_; } KernelState* XObject::kernel_state() const { return kernel_state_; } Memory* XObject::memory() const { return kernel_state_->memory(); } XObject::Type XObject::type() const { return type_; } void XObject::RetainHandle() { kernel_state_->object_table()->RetainHandle(handles_[0]); } bool XObject::ReleaseHandle() { // FIXME: Return true when handle is actually released. return kernel_state_->object_table()->ReleaseHandle(handles_[0]) == X_STATUS_SUCCESS; } void XObject::Retain() { ++pointer_ref_count_; } void XObject::Release() { if (--pointer_ref_count_ == 0) { delete this; } } X_STATUS XObject::Delete() { if (kernel_state_ == nullptr) { // Fake return value for api-scanner return X_STATUS_SUCCESS; } else { if (!name_.empty()) { kernel_state_->object_table()->RemoveNameMapping(name_); } return kernel_state_->object_table()->RemoveHandle(handles_[0]); } } bool XObject::SaveObject(ByteStream* stream) { stream->Write(allocated_guest_object_); stream->Write(guest_object_ptr_); stream->Write(uint32_t(handles_.size())); stream->Write(&handles_[0], handles_.size() * sizeof(X_HANDLE)); return true; } bool XObject::RestoreObject(ByteStream* stream) { allocated_guest_object_ = stream->Read() > 0; guest_object_ptr_ = stream->Read(); handles_.resize(stream->Read()); stream->Read(&handles_[0], handles_.size() * sizeof(X_HANDLE)); // Restore our pointer to our handles in the object table. for (size_t i = 0; i < handles_.size(); i++) { kernel_state_->object_table()->RestoreHandle(handles_[i], this); } return true; } object_ref XObject::Restore(KernelState* kernel_state, Type type, ByteStream* stream) { switch (type) { case Type::Enumerator: break; case Type::Event: return XEvent::Restore(kernel_state, stream); case Type::File: return XFile::Restore(kernel_state, stream); case Type::IOCompletion: break; case Type::Module: return XModule::Restore(kernel_state, stream); case Type::Mutant: return XMutant::Restore(kernel_state, stream); case Type::NotifyListener: return XNotifyListener::Restore(kernel_state, stream); case Type::Semaphore: return XSemaphore::Restore(kernel_state, stream); case Type::Session: break; case Type::Socket: break; case Type::SymbolicLink: return XSymbolicLink::Restore(kernel_state, stream); case Type::Thread: return XThread::Restore(kernel_state, stream); case Type::Timer: break; case Type::Undefined: break; } assert_always("No restore handler exists for this object!"); return nullptr; } void XObject::SetAttributes(uint32_t obj_attributes_ptr) { if (!obj_attributes_ptr) { return; } auto name = util::TranslateAnsiStringAddress( memory(), xe::load_and_swap( memory()->TranslateVirtual(obj_attributes_ptr + 4))); if (!name.empty()) { name_ = std::string(name); kernel_state_->object_table()->AddNameMapping(name_, handles_[0]); } } uint32_t XObject::TimeoutTicksToMs(int64_t timeout_ticks) { if (timeout_ticks > 0) { // NetDll_WSAWaitForMultipleEvents provides timeout in form of MS. return (uint32_t)timeout_ticks; } else if (timeout_ticks < 0) { // Relative time. return (uint32_t)(-timeout_ticks / 10000); // Ticks -> MS } else { return 0; } } X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode, uint32_t alertable, uint64_t* opt_timeout) { auto wait_handle = GetWaitHandle(); if (!wait_handle) { // Object doesn't support waiting. return X_STATUS_SUCCESS; } auto timeout_ms = opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis( TimeoutTicksToMs(*opt_timeout))) : std::chrono::milliseconds::max(); auto result = xe::threading::Wait(wait_handle, alertable ? true : false, timeout_ms); switch (result) { case xe::threading::WaitResult::kSuccess: WaitCallback(); return X_STATUS_SUCCESS; case xe::threading::WaitResult::kUserCallback: // Or X_STATUS_ALERTED? return X_STATUS_USER_APC; case xe::threading::WaitResult::kTimeout: xe::threading::MaybeYield(); return X_STATUS_TIMEOUT; default: case xe::threading::WaitResult::kAbandoned: case xe::threading::WaitResult::kFailed: return X_STATUS_ABANDONED_WAIT_0; } } X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object, uint32_t wait_reason, uint32_t processor_mode, uint32_t alertable, uint64_t* opt_timeout) { auto timeout_ms = opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis( TimeoutTicksToMs(*opt_timeout))) : std::chrono::milliseconds::max(); auto result = xe::threading::SignalAndWait( signal_object->GetWaitHandle(), wait_object->GetWaitHandle(), alertable ? true : false, timeout_ms); switch (result) { case xe::threading::WaitResult::kSuccess: wait_object->WaitCallback(); return X_STATUS_SUCCESS; case xe::threading::WaitResult::kUserCallback: // Or X_STATUS_ALERTED? return X_STATUS_USER_APC; case xe::threading::WaitResult::kTimeout: xe::threading::MaybeYield(); return X_STATUS_TIMEOUT; default: case xe::threading::WaitResult::kAbandoned: case xe::threading::WaitResult::kFailed: return X_STATUS_ABANDONED_WAIT_0; } } X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects, uint32_t wait_type, uint32_t wait_reason, uint32_t processor_mode, uint32_t alertable, uint64_t* opt_timeout) { xe::threading::WaitHandle* wait_handles[64]; for (size_t i = 0; i < count; ++i) { wait_handles[i] = objects[i]->GetWaitHandle(); assert_not_null(wait_handles[i]); } auto timeout_ms = opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis( TimeoutTicksToMs(*opt_timeout))) : std::chrono::milliseconds::max(); if (wait_type) { auto result = xe::threading::WaitAny(wait_handles, count, alertable ? true : false, timeout_ms); switch (result.first) { case xe::threading::WaitResult::kSuccess: objects[result.second]->WaitCallback(); return X_STATUS(result.second); case xe::threading::WaitResult::kUserCallback: // Or X_STATUS_ALERTED? return X_STATUS_USER_APC; case xe::threading::WaitResult::kTimeout: xe::threading::MaybeYield(); return X_STATUS_TIMEOUT; default: case xe::threading::WaitResult::kAbandoned: return X_STATUS(X_STATUS_ABANDONED_WAIT_0 + result.second); case xe::threading::WaitResult::kFailed: return X_STATUS_UNSUCCESSFUL; } } else { auto result = xe::threading::WaitAll(wait_handles, count, alertable ? true : false, timeout_ms); switch (result) { case xe::threading::WaitResult::kSuccess: for (uint32_t i = 0; i < count; i++) { objects[i]->WaitCallback(); } return X_STATUS_SUCCESS; case xe::threading::WaitResult::kUserCallback: // Or X_STATUS_ALERTED? return X_STATUS_USER_APC; case xe::threading::WaitResult::kTimeout: xe::threading::MaybeYield(); return X_STATUS_TIMEOUT; default: case xe::threading::WaitResult::kAbandoned: case xe::threading::WaitResult::kFailed: return X_STATUS_ABANDONED_WAIT_0; } } } uint8_t* XObject::CreateNative(uint32_t size) { auto global_lock = xe::global_critical_region::AcquireDirect(); uint32_t total_size = size + sizeof(X_OBJECT_HEADER); auto mem = memory()->SystemHeapAlloc(total_size); if (!mem) { // Out of memory! return nullptr; } allocated_guest_object_ = true; memory()->Zero(mem, total_size); SetNativePointer(mem + sizeof(X_OBJECT_HEADER), true); auto header = memory()->TranslateVirtual(mem); auto object_type = memory()->SystemHeapAlloc(sizeof(X_OBJECT_TYPE)); if (object_type) { // Set it up in the header. // Some kernel method is accessing this struct and dereferencing a member // @ offset 0x14 header->object_type_ptr = object_type; } return memory()->TranslateVirtual(guest_object_ptr_); } void XObject::SetNativePointer(uint32_t native_ptr, bool uninitialized) { auto global_lock = xe::global_critical_region::AcquireDirect(); // If hit: We've already setup the native ptr with CreateNative! assert_zero(guest_object_ptr_); auto header = kernel_state_->memory()->TranslateVirtual(native_ptr); // Memory uninitialized, so don't bother with the check. if (!uninitialized) { assert_true(!(header->wait_list_blink & 0x1)); } // Stash pointer in struct. // FIXME: This assumes the object has a dispatch header (some don't!) StashHandle(header, handle()); guest_object_ptr_ = native_ptr; } object_ref XObject::GetNativeObject(KernelState* kernel_state, void* native_ptr, int32_t as_type, bool already_locked) { assert_not_null(native_ptr); // Unfortunately the XDK seems to inline some KeInitialize calls, meaning // we never see it and just randomly start getting passed events/timers/etc. // Luckily it seems like all other calls (Set/Reset/Wait/etc) are used and // we don't have to worry about PPC code poking the struct. Because of that, // we init on first use, store our handle in the struct, and dereference it // each time. // We identify this by setting wait_list_flink to a magic value. When set, // wait_list_blink will hold a handle to our object. if (!already_locked) { global_critical_region::mutex().lock(); } XObject* result; auto header = reinterpret_cast(native_ptr); if (as_type == -1) { as_type = header->type; } if (header->wait_list_flink == kXObjSignature) { // Already initialized. // TODO: assert if the type of the object != as_type uint32_t handle = header->wait_list_blink; result = kernel_state->object_table() ->LookupObject(handle, true) .release(); } else { // First use, create new. // https://www.nirsoft.net/kernel_struct/vista/KOBJECTS.html XObject* object = nullptr; switch (as_type) { case 0: // EventNotificationObject case 1: // EventSynchronizationObject { auto ev = new XEvent(kernel_state); ev->InitializeNative(native_ptr, header); object = ev; } break; case 2: // MutantObject { auto mutant = new XMutant(kernel_state); mutant->InitializeNative(native_ptr, header); object = mutant; } break; case 5: // SemaphoreObject { auto sem = new XSemaphore(kernel_state); auto success = sem->InitializeNative(native_ptr, header); // Can't report failure to the guest at late initialization: assert_true(success); object = sem; } break; case 3: // ProcessObject case 4: // QueueObject case 6: // ThreadObject case 7: // GateObject case 8: // TimerNotificationObject case 9: // TimerSynchronizationObject case 18: // ApcObject case 19: // DpcObject case 20: // DeviceQueueObject case 21: // EventPairObject case 22: // InterruptObject case 23: // ProfileObject case 24: // ThreadedDpcObject default: assert_always(); result = nullptr; } // Stash pointer in struct. // FIXME: This assumes the object contains a dispatch header (some don't!) if (object) { StashHandle(header, object->handle()); } result = object; } if (!already_locked) { global_critical_region::mutex().unlock(); } return object_ref(result); } } // namespace kernel } // namespace xe