/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include #include "xenia/kernel/kernel_state.h" #include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/hid/input_system.h" #include "xenia/kernel/kernel_flags.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_memory.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_ob.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" #include "xenia/kernel/xevent.h" #include "xenia/kernel/xmodule.h" #include "xenia/kernel/xnotifylistener.h" #include "xenia/kernel/xobject.h" #include "xenia/kernel/xthread.h" #include "xenia/ui/imgui_host_notification.h" #include "third_party/crypto/TinySHA1.hpp" DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel"); DEFINE_bool(allow_incompatible_title_update, true, "Allow title updates with mismatched signatures to be applied.", "Kernel"); DEFINE_uint32(kernel_build_version, 1888, "Define current kernel version", "Kernel"); DECLARE_string(cl); namespace xe { namespace kernel { constexpr std::chrono::milliseconds kDeferredOverlappedDelayMillis(25); // This is a global object initialized with the XboxkrnlModule. // It references the current kernel state object that all kernel methods should // be using to stash their variables. KernelState* shared_kernel_state_ = nullptr; KernelState* kernel_state() { return shared_kernel_state_; } KernelState::KernelState(Emulator* emulator) : emulator_(emulator), memory_(emulator->memory()), dispatch_thread_running_(false), dpc_list_(emulator->memory()), kernel_trampoline_group_(emulator->processor()->backend()) { assert_null(shared_kernel_state_); shared_kernel_state_ = this; processor_ = emulator->processor(); file_system_ = emulator->file_system(); xam_state_ = std::make_unique(emulator, this); smc_ = std::make_unique(); xconfig_ = std::make_unique(emulator->storage_root() / "xconfig.settings"); InitializeKernelGuestGlobals(); kernel_version_ = KernelVersion(cvars::kernel_build_version); auto hc_loc_heap = memory_->LookupHeap(strange_hardcoded_page_); bool fixed_alloc_worked = hc_loc_heap->AllocFixed( strange_hardcoded_page_, 65536, 0, kMemoryAllocationCommit | kMemoryAllocationReserve, kMemoryProtectRead | kMemoryProtectWrite); xenia_assert(fixed_alloc_worked); } KernelState::~KernelState() { SetExecutableModule(nullptr); if (dispatch_thread_running_) { dispatch_thread_running_ = false; dispatch_cond_.notify_all(); dispatch_thread_->Wait(0, 0, 0, nullptr); } executable_module_.reset(); user_modules_.clear(); kernel_modules_.clear(); // Delete all objects. object_table_.Reset(); xam_state_.reset(); assert_true(shared_kernel_state_ == this); shared_kernel_state_ = nullptr; } KernelState* KernelState::shared() { return shared_kernel_state_; } uint32_t KernelState::title_id() const { if (!executable_module_) { return 0; } assert_not_null(executable_module_); xex2_opt_execution_info* exec_info = 0; executable_module_->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &exec_info); if (exec_info) { return exec_info->title_id; } return 0; } bool KernelState::is_title_open() const { return emulator_->is_title_open(); } const std::unique_ptr KernelState::title_xdbf() const { return module_xdbf(executable_module_); } const std::unique_ptr KernelState::module_xdbf( object_ref exec_module) const { assert_not_null(exec_module); uint32_t resource_data = 0; uint32_t resource_size = 0; if (XSUCCEEDED(exec_module->GetSection( fmt::format("{:08X}", exec_module->title_id()).c_str(), &resource_data, &resource_size))) { return std::make_unique(std::span( memory()->TranslateVirtual(resource_data), resource_size)); } return nullptr; } uint32_t KernelState::AllocateTLS(cpu::ppc::PPCContext* context) { auto globals = memory()->TranslateVirtual(GetKernelGuestGlobals()); auto tls_lock = &globals->tls_lock; auto old_irql = xboxkrnl::xeKeKfAcquireSpinLock(context, tls_lock); int result = -1; auto current_thread = XThread::GetCurrentThread(); if (!current_thread) { XELOGE("AllocateTLS: No current thread"); xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); return X_TLS_OUT_OF_INDEXES; } auto process_ptr = memory()->TranslateVirtual( current_thread->guest_object()->process); if (!process_ptr) { XELOGE("AllocateTLS: Failed to translate process pointer"); xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); return X_TLS_OUT_OF_INDEXES; } // Search for a free TLS slot in the process bitmap // Bitmap format: 1 = free, 0 = allocated // 8 x 32-bit words = 256 total TLS slots for (xe::be* i = &process_ptr->tls_slot_bitmap[0]; i < &process_ptr->tls_slot_bitmap[8]; ++i) { // Read bitmap value (handles big-endian conversion) uint32_t bitmap_value = static_cast(*i); // Find highest free slot using lzcnt (leading zero count) // Returns 0-31 if a bit is set, 32 if no bits are set uint32_t leading_zeros = xe::lzcnt(bitmap_value); if (leading_zeros != 32) { // Calculate absolute slot index from bitmap position and bit offset // Each bitmap word represents 32 slots size_t bitmap_index = i - &process_ptr->tls_slot_bitmap[0]; uint32_t base_slot = static_cast(bitmap_index) * 32; int calculated_slot = base_slot + leading_zeros; // Validate slot is within Xbox 360 TLS range if (calculated_slot >= 0 && calculated_slot < 256) { result = calculated_slot; // Clear the bit to mark as allocated // lzcnt returns 0 for bit 31, 31 for bit 0 uint32_t bit_index = 31 - leading_zeros; *i = bitmap_value & ~(1U << bit_index); break; } else { XELOGE("AllocateTLS: Invalid slot calculation: {}", calculated_slot); } } } if (result == -1) { XELOGW("AllocateTLS: All TLS slots exhausted for current process"); } xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); return static_cast(result); } void KernelState::FreeTLS(cpu::ppc::PPCContext* context, uint32_t slot) { if (slot >= 256) { XELOGE("FreeTLS: Invalid slot index {}", slot); return; } auto current_thread = XThread::GetCurrentThread(); if (!current_thread) { XELOGE("FreeTLS: No current thread"); return; } auto current_kthread = current_thread->guest_object(); if (!current_kthread) { XELOGE("FreeTLS: Failed to get guest thread object"); return; } auto process_ptr = memory()->TranslateVirtual(current_kthread->process); if (!process_ptr) { XELOGE("FreeTLS: Failed to translate process pointer"); return; } auto globals = memory()->TranslateVirtual(GetKernelGuestGlobals()); auto tls_lock = &globals->tls_lock; auto old_irql = xboxkrnl::xeKeKfAcquireSpinLock(context, tls_lock); uint32_t bitmap_index = slot / 32; uint32_t bit_mask = 1U << (31 - (slot % 32)); uint32_t bitmap_value = static_cast(process_ptr->tls_slot_bitmap[bitmap_index]); if (bitmap_value & bit_mask) { XELOGW("FreeTLS: Slot {} is already free", slot); xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); return; } // Clear TLS values in all threads of this process const std::vector> threads = object_table()->GetObjectsByType(); uint32_t current_process_ptr = current_kthread->process.m_ptr; for (const object_ref& thread : threads) { if (!thread || !thread->is_guest_thread()) { continue; } auto thread_kthread = thread->guest_object(); if (thread_kthread && thread_kthread->process.m_ptr == current_process_ptr) { thread->SetTLSValue(slot, 0); } } // Mark slot as free in bitmap process_ptr->tls_slot_bitmap[bitmap_index] = bitmap_value | bit_mask; xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); } void KernelState::RegisterTitleTerminateNotification(uint32_t routine, uint32_t priority) { TerminateNotification notify; notify.guest_routine = routine; notify.priority = priority; terminate_notifications_.push_back(notify); } void KernelState::RemoveTitleTerminateNotification(uint32_t routine) { for (auto it = terminate_notifications_.begin(); it != terminate_notifications_.end(); it++) { if (it->guest_routine == routine) { terminate_notifications_.erase(it); break; } } } void KernelState::RegisterModule(XModule* module) {} void KernelState::UnregisterModule(XModule* module) {} bool KernelState::RegisterUserModule(object_ref module) { auto lock = global_critical_region_.Acquire(); for (auto user_module : user_modules_) { if (user_module->path() == module->path()) { // Already loaded. return false; } } user_modules_.push_back(module); return true; } void KernelState::UnregisterUserModule(UserModule* module) { auto lock = global_critical_region_.Acquire(); for (auto it = user_modules_.begin(); it != user_modules_.end(); it++) { if ((*it)->path() == module->path()) { user_modules_.erase(it); return; } } } bool KernelState::IsKernelModule(const std::string_view name) { if (name.empty()) { // Executing module isn't a kernel module. return false; } // NOTE: no global lock required as the kernel module list is static. for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return true; } } return false; } bool KernelState::IsModuleLoaded(const std::string_view name) { if (name.empty()) { return true; } for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return true; } } auto global_lock = global_critical_region_.Acquire(); for (auto user_module : user_modules_) { if (user_module->Matches(name)) { return true; } } return false; } object_ref KernelState::GetKernelModule( const std::string_view name) { assert_true(IsKernelModule(name)); for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return retain_object(kernel_module.get()); } } return nullptr; } object_ref KernelState::GetModule(const std::string_view name, bool user_only) { if (name.empty()) { // NULL name = self. // TODO(benvanik): lookup module from caller address. return GetExecutableModule(); } else if (xe::utf8::equal_case(name, "kernel32.dll")) { // Some games request this, for some reason. wtf. return nullptr; } auto global_lock = global_critical_region_.Acquire(); if (!user_only) { for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return retain_object(kernel_module.get()); } } } auto path(name); // Resolve the path to an absolute path. auto entry = file_system_->ResolvePath(name); if (entry) { path = entry->absolute_path(); } for (auto user_module : user_modules_) { if (user_module->Matches(path)) { return retain_object(user_module.get()); } } return nullptr; } object_ref KernelState::LaunchModule(object_ref module) { if (!module->is_executable()) { return nullptr; } SetExecutableModule(module); XELOGI("KernelState: Launching module..."); // Create a thread to run in. // We start suspended so we can run the debugger prep. auto thread = object_ref( new XThread(kernel_state(), module->stack_size(), 0, module->entry_point(), 0, X_CREATE_SUSPENDED, true, true)); // We know this is the 'main thread'. thread->set_name("Main XThread"); X_STATUS result = thread->Create(); if (XFAILED(result)) { XELOGE("Could not create launch thread: {:08X}", result); return nullptr; } // Waits for a debugger client, if desired. emulator()->processor()->PreLaunch(); return thread; } object_ref KernelState::GetExecutableModule() { if (!executable_module_) { return nullptr; } return executable_module_; } void KernelState::SetExecutableModule(object_ref module) { if (module.get() == executable_module_.get()) { return; } executable_module_ = std::move(module); if (!executable_module_) { return; } auto title_process = memory_->TranslateVirtual(GetTitleProcess()); InitializeProcess(title_process, X_PROCTYPE_TITLE, 10, 13, 17); xex2_opt_tls_info* tls_header = nullptr; executable_module_->GetOptHeader(XEX_HEADER_TLS_INFO, &tls_header); if (tls_header) { title_process->tls_static_data_address = tls_header->raw_data_address; title_process->tls_data_size = tls_header->data_size; title_process->tls_raw_data_size = tls_header->raw_data_size; title_process->tls_slot_size = tls_header->slot_count * 4; SetProcessTLSVars(title_process, tls_header->slot_count, tls_header->data_size, tls_header->raw_data_address); } uint32_t kernel_stacksize = 0; executable_module_->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, &kernel_stacksize); if (kernel_stacksize) { kernel_stacksize = (kernel_stacksize + 4095) & 0xFFFFF000; if (kernel_stacksize < 0x4000) { kernel_stacksize = 0x4000; } title_process->kernel_stack_size = kernel_stacksize; } // Setup the kernel's XexExecutableModuleHandle field. auto export_entry = processor()->export_resolver()->GetExportByOrdinal( "xboxkrnl.exe", ordinals::XexExecutableModuleHandle); if (export_entry) { assert_not_zero(export_entry->variable_ptr); auto variable_ptr = memory()->TranslateVirtual*>( export_entry->variable_ptr); *variable_ptr = executable_module_->hmodule_ptr(); } // Setup the kernel's ExLoadedImageName field export_entry = processor()->export_resolver()->GetExportByOrdinal( "xboxkrnl.exe", ordinals::ExLoadedImageName); if (export_entry) { char* variable_ptr = memory()->TranslateVirtual(export_entry->variable_ptr); xe::string_util::copy_truncating( variable_ptr, executable_module_->path(), xboxkrnl::XboxkrnlModule::kExLoadedImageNameSize); } // Setup the kernel's ExLoadedCommandLine field export_entry = processor()->export_resolver()->GetExportByOrdinal( "xboxkrnl.exe", ordinals::ExLoadedCommandLine); if (export_entry) { char* variable_ptr = memory()->TranslateVirtual(export_entry->variable_ptr); std::string module_name = fmt::format("\"{}.xex\"", executable_module_->name()); if (!cvars::cl.empty()) { module_name += " " + cvars::cl; } xe::string_util::copy_truncating( variable_ptr, module_name, xboxkrnl::XboxkrnlModule::kExLoadedCommandLineSize); } // Initialize file I/O hooks for XMP volume title-specific patches. InitXmpVolumePatch(); // Spin up deferred dispatch worker. // TODO(benvanik): move someplace more appropriate (out of ctor, but around // here). if (!dispatch_thread_running_) { dispatch_thread_running_ = true; dispatch_thread_ = object_ref(new XHostThread( this, 128 * 1024, 0, [this]() { // As we run guest callbacks the debugger must be able to suspend us. dispatch_thread_->set_can_debugger_suspend(true); auto global_lock = global_critical_region_.AcquireDeferred(); while (dispatch_thread_running_) { global_lock.lock(); if (dispatch_queue_.empty()) { dispatch_cond_.wait(global_lock); if (!dispatch_thread_running_) { global_lock.unlock(); break; } } auto fn = std::move(dispatch_queue_.front()); dispatch_queue_.pop_front(); global_lock.unlock(); fn(); } return 0; }, GetSystemProcess())); // don't think an equivalent exists on real hw dispatch_thread_->set_name("Kernel Dispatch"); dispatch_thread_->Create(); } } void KernelState::LoadKernelModule(object_ref kernel_module) { auto global_lock = global_critical_region_.Acquire(); kernel_modules_.push_back(std::move(kernel_module)); } object_ref KernelState::LoadUserModule( const std::string_view raw_name, bool call_entry) { // Some games try to load relative to launch module, others specify full path. auto name = xe::utf8::find_name_from_guest_path(raw_name); std::string path(raw_name); if (name == raw_name) { if (!executable_module_) { path = xe::utf8::join_guest_paths( xe::utf8::find_base_guest_path((*user_modules_.cbegin())->path()), name); } else { path = xe::utf8::join_guest_paths( xe::utf8::find_base_guest_path(executable_module_->path()), name); } } object_ref module; { auto global_lock = global_critical_region_.Acquire(); // See if we've already loaded it for (auto& existing_module : user_modules_) { if (existing_module->Matches(path)) { return existing_module; } } global_lock.unlock(); // Module wasn't loaded, so load it. module = object_ref(new UserModule(this)); X_STATUS status = module->LoadFromFile(path); if (XFAILED(status)) { object_table()->ReleaseHandle(module->handle()); return nullptr; } global_lock.lock(); // Putting into the listing automatically retains. user_modules_.push_back(module); } return module; } object_ref KernelState::LoadUserModuleFromMemory( const std::string_view raw_name, const void* addr, const size_t length) { auto name = xe::utf8::find_base_name_from_guest_path(raw_name); object_ref module; { auto global_lock = global_critical_region_.Acquire(); // See if we've already loaded it for (auto& existing_module : user_modules_) { if (existing_module->Matches(name)) { return existing_module; } } global_lock.unlock(); // Module wasn't loaded, so load it. module = object_ref(new UserModule(this)); X_STATUS status = module->LoadFromMemoryNamed(name, addr, length); if (XFAILED(status)) { object_table()->ReleaseHandle(module->handle()); return nullptr; } global_lock.lock(); // Putting into the listing automatically retains. user_modules_.push_back(module); } return module; } X_RESULT KernelState::FinishLoadingUserModule( const object_ref module, bool call_entry) { // TODO(Gliniak): Apply custom patches here X_RESULT result = module->LoadContinue(); if (XFAILED(result)) { return result; } module->Dump(); emulator_->patcher()->ApplyPatchesForTitle(memory_, module->title_id(), module->hash()); emulator_->on_patch_apply(); if (module->xex_module()) { module->xex_module()->Precompile(); } if (module->is_dll_module() && module->entry_point() && call_entry) { // Call DllMain(DLL_PROCESS_ATTACH): // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx uint64_t args[] = { module->handle(), 1, // DLL_PROCESS_ATTACH 0, // 0 because always dynamic }; module->is_attached_ = true; auto thread_state = XThread::GetCurrentThread()->thread_state(); processor()->Execute(thread_state, module->entry_point(), args, xe::countof(args)); } return result; } X_RESULT KernelState::ApplyTitleUpdate( const object_ref title_module) { const auto title_updates = FindTitleUpdate(title_module->title_id()); if (title_updates.empty()) { return X_STATUS_SUCCESS; } auto patch_module = LoadTitleUpdate(&title_updates.front(), title_module); if (!patch_module) { return X_STATUS_SUCCESS; } if (!patch_module->xex_module()->is_patch()) { return X_STATUS_UNSUCCESSFUL; } if (!IsPatchSignatureProper(title_module, patch_module)) { if (!cvars::allow_incompatible_title_update) { XELOGW( "Skipping incompatible title update for {} due to signature mismatch", title_module->name()); return X_STATUS_SUCCESS; } // First module that is loaded is always main executable. That way we can // prevent random message spam in case of loading/unloading. if (!GetExecutableModule()) { emulator_->display_window()->app_context().CallInUIThread([&]() { new xe::ui::HostNotificationWindow( emulator_->imgui_drawer(), "Warning!", "Title Update signature doesn't match. This can cause unexpected " "issues or crashes!", 0); }); } } return ApplyTitleUpdate(title_module, patch_module); } std::vector KernelState::FindTitleUpdate( const uint32_t title_id) const { if (!cvars::apply_title_update) { return {}; } return xam_state_->content_manager()->ListContent( 1, 0, title_id, xe::XContentType::kInstaller); } const object_ref KernelState::LoadTitleUpdate( const xam::XCONTENT_AGGREGATE_DATA* title_update, const object_ref module) { uint32_t disc_number = -1; if (module->is_multi_disc_title()) { disc_number = module->disc_number(); } uint32_t content_license = 0; X_RESULT open_status = content_manager()->OpenContent( "UPDATE", 0, *title_update, content_license, disc_number); std::string mount_path = ""; if (!file_system()->FindSymbolicLink(kDefaultGameSymbolicLink, mount_path)) { return nullptr; } if (!module->path().starts_with(mount_path)) { return nullptr; } std::string resolved_path = ""; if (!file_system()->FindSymbolicLink(kDefaultUpdateSymbolicLink, resolved_path)) { return nullptr; } const std::string relative_path = module->path().substr(mount_path.size() + 1) + 'p'; xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath(resolved_path + relative_path); if (!patch_entry) { return nullptr; } const std::string patch_path = patch_entry->absolute_path(); XELOGI("Loading XEX patch from {}", patch_path); auto patch_module = object_ref(new UserModule(this)); X_RESULT result = patch_module->LoadFromFile(patch_path); if (result != X_STATUS_SUCCESS) { XELOGE("Failed to load XEX patch, code: {}", result); return nullptr; } return patch_module; } bool KernelState::IsPatchSignatureProper( const object_ref title_module, const object_ref patch_module) const { xex2_opt_delta_patch_descriptor* patch_header = nullptr; patch_module->GetOptHeader(XEX_HEADER_DELTA_PATCH_DESCRIPTOR, reinterpret_cast(&patch_header)); assert_not_null(patch_header); // Compare hash inside delta descriptor to base XEX signature uint8_t digest[0x14]; sha1::SHA1 s; s.processBytes(title_module->xex_module()->xex_security_info()->rsa_signature, 0x100); s.finalize(digest); if (memcmp(digest, patch_header->digest_source, 0x14) != 0) { XELOGW( "XEX patch signature hash doesn't match base XEX signature hash, patch " "will likely fail!"); return false; } return true; } X_RESULT KernelState::ApplyTitleUpdate( const object_ref title_module, const object_ref patch_module) { if (!title_module) { XELOGE("{}: No title_module provided!", __FUNCTION__); return X_STATUS_UNSUCCESSFUL; } if (!patch_module) { XELOGE("{}: No patch_module provided!", __FUNCTION__); return X_STATUS_UNSUCCESSFUL; } X_STATUS result = patch_module->xex_module()->ApplyPatch(title_module->xex_module()); if (result != X_STATUS_SUCCESS) { XELOGE("Failed to apply XEX patch, code: {}", result); } return result; } void KernelState::UnloadUserModule(const object_ref& module, bool call_entry) { auto global_lock = global_critical_region_.Acquire(); if (module->is_dll_module() && module->entry_point() && call_entry) { // Call DllMain(DLL_PROCESS_DETACH): // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx uint64_t args[] = { module->handle(), 0, // DLL_PROCESS_DETACH 0, // 0 for now, assume XexUnloadImage is like FreeLibrary }; auto thread_state = XThread::GetCurrentThread()->thread_state(); processor()->Execute(thread_state, module->entry_point(), args, xe::countof(args)); } auto iter = std::find_if( user_modules_.begin(), user_modules_.end(), [&module](const auto& e) { return e->path() == module->path(); }); assert_true(iter != user_modules_.end()); // Unloading an unregistered module // is probably really bad user_modules_.erase(iter); // Ensure this module was not somehow registered twice assert_true(std::find_if(user_modules_.begin(), user_modules_.end(), [&module](const auto& e) { return e->path() == module->path(); }) == user_modules_.end()); object_table()->ReleaseHandleInLock(module->handle()); } void KernelState::InitXmpVolumePatch() { xmp_volume_patch_ = XmpVolumePatch::CreateForTitle(title_id(), this); } void KernelState::TerminateTitle() { XELOGD("KernelState::TerminateTitle"); xmp_volume_patch_.reset(); auto global_lock = global_critical_region_.Acquire(); // Call terminate routines. // TODO(benvanik): these might take arguments. // FIXME: Calling these will send some threads into kernel code and they'll // hold the lock when terminated! Do we need to wait for all threads to exit? /* if (from_guest_thread) { for (auto routine : terminate_notifications_) { auto thread_state = XThread::GetCurrentThread()->thread_state(); processor()->Execute(thread_state, routine.guest_routine); } } terminate_notifications_.clear(); */ // Kill all guest threads. for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) { if (!XThread::IsInThread(it->second) && it->second->is_guest_thread()) { auto thread = it->second; if (thread->is_running()) { // Need to step the thread to a safe point (returns it to guest code // so it's guaranteed to not be holding any locks / in host kernel // code / etc). Can't do that properly if we have the lock. if (!emulator_->is_paused()) { thread->thread()->Suspend(); } global_lock.unlock(); processor_->StepToGuestSafePoint(thread->thread_id()); thread->Terminate(0); global_lock.lock(); } // Erase it from the thread list. it = threads_by_id_.erase(it); } else { ++it; } } // Third: Unload all user modules (including the executable). for (size_t i = 0; i < user_modules_.size(); i++) { user_modules_[i]->ReleaseHandle(); } user_modules_.clear(); // Release all objects in the object table. object_table_.PurgeAllObjects(); // Unregister all notify listeners. notify_listeners_.clear(); // Unset the executable module. executable_module_ = nullptr; if (XThread::IsInThread()) { threads_by_id_.erase(XThread::GetCurrentThread()->thread_id()); // Now commit suicide (using Terminate, because we can't call into guest // code anymore). global_lock.unlock(); XThread::GetCurrentThread()->Terminate(0); } } void KernelState::RegisterThread(XThread* thread) { auto global_lock = global_critical_region_.Acquire(); threads_by_id_[thread->thread_id()] = thread; } void KernelState::UnregisterThread(XThread* thread) { auto global_lock = global_critical_region_.Acquire(); auto it = threads_by_id_.find(thread->thread_id()); if (it != threads_by_id_.end()) { threads_by_id_.erase(it); } } void KernelState::OnThreadExecute(XThread* thread) { auto global_lock = global_critical_region_.Acquire(); // Must be called on executing thread. assert_true(XThread::GetCurrentThread() == thread); // Call DllMain(DLL_THREAD_ATTACH) for each user module: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx auto thread_state = thread->thread_state(); for (auto user_module : user_modules_) { if (user_module->is_dll_module() && user_module->entry_point()) { uint64_t args[] = { user_module->handle(), user_module->is_attached() ? static_cast(2) // DLL_THREAD_ATTACH - Used to call // DLL for each thread created. : static_cast(1), // DLL_PROCESS_ATTACH - Used only // once for initialization. 0, // 0 because always dynamic }; user_module->is_attached_ = true; processor()->Execute(thread_state, user_module->entry_point(), args, xe::countof(args)); } } } void KernelState::OnThreadExit(XThread* thread) { auto global_lock = global_critical_region_.Acquire(); // Must be called on executing thread. assert_true(XThread::GetCurrentThread() == thread); // Call DllMain(DLL_THREAD_DETACH) for each user module: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx auto thread_state = thread->thread_state(); for (auto user_module : user_modules_) { if (user_module->is_dll_module() && user_module->entry_point()) { uint64_t args[] = { user_module->handle(), 3, // DLL_THREAD_DETACH 0, // 0 because always dynamic }; processor()->Execute(thread_state, user_module->entry_point(), args, xe::countof(args)); } } emulator()->processor()->OnThreadExit(thread->thread_id()); } object_ref KernelState::GetThreadByID(uint32_t thread_id) { auto global_lock = global_critical_region_.Acquire(); XThread* thread = nullptr; auto it = threads_by_id_.find(thread_id); if (it != threads_by_id_.end()) { thread = it->second; } return retain_object(thread); } std::vector KernelState::GetAllThreadIDs() { auto global_lock = global_critical_region_.Acquire(); auto thread_ids_view = threads_by_id_ | std::views::transform([](const auto& pair) { return pair.first; }); std::vector thread_ids(thread_ids_view.begin(), thread_ids_view.end()); return thread_ids; } void KernelState::RegisterNotifyListener(XNotifyListener* listener) { auto global_lock = global_critical_region_.Acquire(); notify_listeners_.push_back(retain_object(listener)); // Games seem to expect a few notifications on startup, only for the first // listener. // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=resident+evil+5&start=375 if (!has_notified_startup_ && listener->mask() & kXNotifySystem) { has_notified_startup_ = true; listener->EnqueueNotification(kXNotificationSystemUI, xam_state()->IsUIActive()); listener->EnqueueNotification(kXNotificationSystemSignInChanged, 1); } if (!has_notified_live_startup_ && listener->mask() & kXNotifyLive) { has_notified_live_startup_ = true; // X_ONLINE_S_LOGON_DISCONNECTED listener->EnqueueNotification(kXNotificationLiveConnectionChanged, 0x001510F1L); listener->EnqueueNotification(kXNotificationLiveLinkStateChanged, 0); } } void KernelState::UnregisterNotifyListener(XNotifyListener* listener) { auto global_lock = global_critical_region_.Acquire(); for (auto it = notify_listeners_.begin(); it != notify_listeners_.end(); ++it) { if ((*it).get() == listener) { notify_listeners_.erase(it); break; } } } void KernelState::BroadcastNotification(XNotificationID id, uint32_t data) { auto global_lock = global_critical_region_.Acquire(); for (const auto& notify_listener : notify_listeners_) { notify_listener->EnqueueNotification(id, data); } } void KernelState::CompleteOverlapped(uint32_t overlapped_ptr, X_RESULT result) { CompleteOverlappedEx(overlapped_ptr, result, result, 0); } void KernelState::CompleteOverlappedEx(uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length) { auto ptr = memory()->TranslateVirtual(overlapped_ptr); XOverlappedSetResult(ptr, result); XOverlappedSetExtendedError(ptr, extended_error); XOverlappedSetLength(ptr, length); X_HANDLE event_handle = XOverlappedGetEvent(ptr); if (event_handle) { auto ev = object_table()->LookupObject(event_handle); assert_not_null(ev); if (ev) { ev->Set(0, false); } } if (XOverlappedGetCompletionRoutine(ptr)) { X_HANDLE thread_handle = XOverlappedGetContext(ptr); auto thread = object_table()->LookupObject(thread_handle); if (thread) { // Queue APC on the thread that requested the overlapped operation. uint32_t routine = XOverlappedGetCompletionRoutine(ptr); thread->EnqueueApc(routine, result, length, overlapped_ptr); } } } void KernelState::CompleteOverlappedImmediate(uint32_t overlapped_ptr, X_RESULT result) { // TODO(gibbed): there are games that check 'length' of overlapped as // an indication of success. WTF? // Setting length to -1 when not success seems to be helping. uint32_t length = !result ? 0 : 0xFFFFFFFF; CompleteOverlappedImmediateEx(overlapped_ptr, result, result, length); } void KernelState::CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length) { auto ptr = memory()->TranslateVirtual(overlapped_ptr); XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle()); CompleteOverlappedEx(overlapped_ptr, result, extended_error, length); } void KernelState::CompleteOverlappedDeferred( std::function completion_callback, uint32_t overlapped_ptr, X_RESULT result, std::function pre_callback, std::function post_callback) { CompleteOverlappedDeferredEx(std::move(completion_callback), overlapped_ptr, result, result, 0, pre_callback, post_callback); } void KernelState::CompleteOverlappedDeferredEx( std::function completion_callback, uint32_t overlapped_ptr, X_RESULT result, uint32_t extended_error, uint32_t length, std::function pre_callback, std::function post_callback) { CompleteOverlappedDeferredEx( [completion_callback, result, extended_error, length]( uint32_t& cb_extended_error, uint32_t& cb_length) -> X_RESULT { completion_callback(); cb_extended_error = extended_error; cb_length = length; return result; }, overlapped_ptr, pre_callback, post_callback); } void KernelState::CompleteOverlappedDeferred( std::function completion_callback, uint32_t overlapped_ptr, std::function pre_callback, std::function post_callback) { CompleteOverlappedDeferredEx( [completion_callback](uint32_t& extended_error, uint32_t& length) -> X_RESULT { auto result = completion_callback(); extended_error = static_cast(result); length = 0; return result; }, overlapped_ptr, pre_callback, post_callback); } void KernelState::CompleteOverlappedDeferredEx( std::function completion_callback, uint32_t overlapped_ptr, std::function pre_callback, std::function post_callback) { auto ptr = memory()->TranslateVirtual(overlapped_ptr); XOverlappedSetResult(ptr, X_ERROR_IO_PENDING); XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle()); X_HANDLE event_handle = XOverlappedGetEvent(ptr); if (event_handle) { auto ev = object_table()->LookupObject(event_handle); assert_not_null(ev); if (ev && ev->type() == XObject::Type::Event) { ev.get()->Reset(); } } auto global_lock = global_critical_region_.Acquire(); dispatch_queue_.push_back([this, completion_callback, overlapped_ptr, pre_callback, post_callback]() { if (pre_callback) { pre_callback(); } // 5454082B infinitely loads free roam in netplay without sleep. xe::threading::Sleep(kDeferredOverlappedDelayMillis); uint32_t extended_error, length; auto result = completion_callback(extended_error, length); CompleteOverlappedEx(overlapped_ptr, result, extended_error, length); if (post_callback) { post_callback(); } }); dispatch_cond_.notify_all(); } bool KernelState::Save(ByteStream* stream) { XELOGD("Serializing the kernel..."); stream->Write(kKernelSaveSignature); // Save the object table object_table_.Save(stream); // Write the TLS allocation bitmap // We save XThreads absolutely first, as they will execute code upon save // (which could modify the kernel state) auto threads = object_table_.GetObjectsByType(); uint32_t* num_threads_ptr = reinterpret_cast(stream->data() + stream->offset()); stream->Write(static_cast(threads.size())); size_t num_threads = threads.size(); XELOGD("Serializing {} threads...", threads.size()); for (auto thread : threads) { if (!thread->is_guest_thread()) { // Don't save host threads. They can be reconstructed on startup. num_threads--; continue; } if (!thread->Save(stream)) { XELOGD("Failed to save thread \"{}\"", thread->name()); num_threads--; } } *num_threads_ptr = static_cast(num_threads); // Save all other objects auto objects = object_table_.GetAllObjects(); uint32_t* num_objects_ptr = reinterpret_cast(stream->data() + stream->offset()); stream->Write(static_cast(objects.size())); size_t num_objects = objects.size(); XELOGD("Serializing {} objects...", num_objects); for (auto object : objects) { auto prev_offset = stream->offset(); if (object->is_host_object() || object->type() == XObject::Type::Thread) { // Don't save host objects or save XThreads again num_objects--; continue; } stream->Write(static_cast(object->type())); if (!object->Save(stream)) { XELOGD("Did not save object of type {}", static_cast(object->type())); assert_always(); // Revert backwards and overwrite if a save failed. stream->set_offset(prev_offset); num_objects--; } } *num_objects_ptr = static_cast(num_objects); return true; } // this only gets triggered once per ms at most, so fields other than tick count // will probably not be updated in a timely manner for guest code that uses them void KernelState::UpdateKeTimestampBundle() { X_TIME_STAMP_BUNDLE* lpKeTimeStampBundle = memory_->TranslateVirtual(ke_timestamp_bundle_ptr_); uint32_t uptime_ms = Clock::QueryGuestUptimeMillis(); xe::store_and_swap(&lpKeTimeStampBundle->interrupt_time, Clock::QueryGuestInterruptTime()); xe::store_and_swap(&lpKeTimeStampBundle->system_time, Clock::QueryGuestSystemTime()); xe::store_and_swap(&lpKeTimeStampBundle->tick_count, uptime_ms); // Every 20 ticks (~20ms), decay priority on running guest threads. // This simulates the Xenon decrementer-driven quantum expiration. if (++quantum_timer_counter_ >= 20) { quantum_timer_counter_ = 0; auto global_lock = global_critical_region_.Acquire(); for (auto& [id, thread] : threads_by_id_) { if (thread->is_running()) { thread->CheckQuantumAndDecay(); } } } } uint32_t KernelState::GetKeTimestampBundle() { XE_LIKELY_IF(ke_timestamp_bundle_ptr_) { return ke_timestamp_bundle_ptr_; } else { global_critical_region::PrepareToAcquire(); return CreateKeTimestampBundle(); } } XE_NOINLINE XE_COLD uint32_t KernelState::CreateKeTimestampBundle() { auto crit = global_critical_region::Acquire(); const uint32_t pKeTimeStampBundle = 0x80240EE0; X_TIME_STAMP_BUNDLE* lpKeTimeStampBundle = memory_->TranslateVirtual(pKeTimeStampBundle); xe::store_and_swap(&lpKeTimeStampBundle->interrupt_time, Clock::QueryGuestInterruptTime()); xe::store_and_swap(&lpKeTimeStampBundle->system_time, Clock::QueryGuestSystemTime()); xe::store_and_swap(&lpKeTimeStampBundle->tick_count, Clock::QueryGuestUptimeMillis()); xe::store_and_swap(&lpKeTimeStampBundle->padding, 0); ke_timestamp_bundle_ptr_ = pKeTimeStampBundle; timestamp_timer_ = xe::threading::HighResolutionTimer::CreateRepeating( std::chrono::milliseconds(1), [this]() { this->UpdateKeTimestampBundle(); }); return pKeTimeStampBundle; } bool KernelState::Restore(ByteStream* stream) { // Check the magic value. if (stream->Read() != kKernelSaveSignature) { return false; } // Restore the object table object_table_.Restore(stream); // TLS bitmap is now stored per-process in X_KPROCESS structures (in guest // memory) Skip reading old global TLS bitmap if present in old save files auto num_bitmap_entries = stream->Read(); for (uint32_t i = 0; i < num_bitmap_entries; i++) { stream->Read(); // Discard old data } uint32_t num_threads = stream->Read(); XELOGD("Loading {} threads...", num_threads); for (uint32_t i = 0; i < num_threads; i++) { auto thread = XObject::Restore(this, XObject::Type::Thread, stream); if (!thread) { // Can't continue the restore or we risk misalignment. assert_always(); return false; } } uint32_t num_objects = stream->Read(); XELOGD("Loading {} objects...", num_objects); for (uint32_t i = 0; i < num_objects; i++) { uint32_t type = stream->Read(); auto obj = XObject::Restore(this, XObject::Type(type), stream); if (!obj) { // Can't continue the restore or we risk misalignment. assert_always(); return false; } } return true; } std::bitset<4> KernelState::GetConnectedUsers() const { auto input_sys = emulator_->input_system(); auto lock = input_sys->lock(); return input_sys->GetConnectedSlots(); } // todo: definitely need to do more to pretend to be in a dpc void KernelState::BeginDPCImpersonation(cpu::ppc::PPCContext* context, DPCImpersonationScope& scope) { auto kpcr = context->TranslateVirtualGPR(context->r[13]); xenia_assert(kpcr->prcb_data.dpc_active == 0); scope.previous_irql_ = kpcr->current_irql; kpcr->current_irql = 2; kpcr->prcb_data.dpc_active = 1; } void KernelState::EndDPCImpersonation(cpu::ppc::PPCContext* context, DPCImpersonationScope& end_scope) { auto kpcr = context->TranslateVirtualGPR(context->r[13]); xenia_assert(kpcr->prcb_data.dpc_active == 1); kpcr->current_irql = end_scope.previous_irql_; kpcr->prcb_data.dpc_active = 0; } void KernelState::EmulateCPInterruptDPC(uint32_t interrupt_callback, uint32_t interrupt_callback_data, uint32_t source, uint32_t cpu) { if (!interrupt_callback) { return; } if (cvars::audit_handle_lifecycle) { XELOGKERNEL( "AUDIT-HLC EmulateCPInterruptDPC callback={:08X} data={:08X} source={} " "cpu={}", interrupt_callback, interrupt_callback_data, source, cpu); } auto thread = kernel::XThread::GetCurrentThread(); assert_not_null(thread); // Pick a CPU, if needed. We're going to guess 2. Because. if (cpu == 0xFFFFFFFF) { cpu = 2; } thread->SetActiveCpu(cpu); /* in reality, our interrupt is a callback that is called in a dpc which is scheduled by the actual interrupt we need to impersonate a dpc */ auto current_context = thread->thread_state()->context(); auto kthread = memory()->TranslateVirtual(thread->guest_object()); auto pcr = memory()->TranslateVirtual(thread->pcr_ptr()); DPCImpersonationScope dpc_scope{}; BeginDPCImpersonation(current_context, dpc_scope); // todo: check VdGlobalXamDevice here. if VdGlobalXamDevice is nonzero, should // set X_PROCTYPE_SYSTEM xboxkrnl::xeKeSetCurrentProcessType(X_PROCTYPE_TITLE, current_context); uint64_t args[] = {source, interrupt_callback_data}; processor_->Execute(thread->thread_state(), interrupt_callback, args, xe::countof(args)); xboxkrnl::xeKeSetCurrentProcessType(X_PROCTYPE_IDLE, current_context); EndDPCImpersonation(current_context, dpc_scope); } void KernelState::InitializeProcess(X_KPROCESS* process, uint32_t type, char priority_class, char default_priority, char max_dynamic_priority) { uint32_t guest_kprocess = memory()->HostToGuestVirtual(process); uint32_t thread_list_guest_ptr = guest_kprocess + offsetof(X_KPROCESS, thread_list); process->process_priority_class = priority_class; process->default_thread_priority = default_priority; process->max_dynamic_priority = max_dynamic_priority; util::XeInitializeListHead(&process->thread_list, thread_list_guest_ptr); process->quantum = 60; // doubt any guest code uses this ptr, which i think probably has something to // do with the page table process->clrdataa_masked_ptr = 0; // clrdataa_ & ~(1U << 31); process->thread_count = 0; process->disable_quantum_decay = 0x06; process->kernel_stack_size = 16 * 1024; process->tls_slot_size = 0x80; process->process_type = type; uint32_t unk_list_guest_ptr = guest_kprocess + offsetof(X_KPROCESS, unk_54); // TODO(benvanik): figure out what this list is. util::XeInitializeListHead(&process->unk_54, unk_list_guest_ptr); } void KernelState::SetProcessTLSVars(X_KPROCESS* process, int num_slots, int tls_data_size, int tls_static_data_address) { uint32_t slots_padded = (num_slots + 3) & 0xFFFFFFFC; process->tls_data_size = tls_data_size; process->tls_raw_data_size = tls_data_size; process->tls_static_data_address = tls_static_data_address; process->tls_slot_size = 4 * slots_padded; uint32_t count_div32 = slots_padded / 32; for (unsigned word_index = 0; word_index < count_div32; ++word_index) { process->tls_slot_bitmap[word_index] = -1; } // set remainder of bitset if (((num_slots + 3) & 0x1C) != 0) { process->tls_slot_bitmap[count_div32] = -1 << (32 - ((num_slots + 3) & 0x1C)); } } void AllocateThread(PPCContext* context) { uint32_t thread_mem_size = static_cast(context->r[3]); uint32_t a2 = static_cast(context->r[4]); uint32_t a3 = static_cast(context->r[5]); if (thread_mem_size <= 0xFD8) { thread_mem_size += 8; } uint32_t result = xboxkrnl::xeAllocatePoolTypeWithTag(context, thread_mem_size, a2, a3); if (((unsigned short)result & 0xFFF) != 0) { result += 2; } context->r[3] = static_cast(result); } void FreeThread(PPCContext* context) { uint32_t thread_memory = static_cast(context->r[3]); if ((thread_memory & 0xFFF) != 0) { thread_memory -= 8; } xboxkrnl::xeFreePool(context, thread_memory); } void SimpleForwardAllocatePoolTypeWithTag(PPCContext* context) { uint32_t a1 = static_cast(context->r[3]); uint32_t a2 = static_cast(context->r[4]); uint32_t a3 = static_cast(context->r[5]); context->r[3] = static_cast( xboxkrnl::xeAllocatePoolTypeWithTag(context, a1, a2, a3)); } void SimpleForwardFreePool(PPCContext* context) { xboxkrnl::xeFreePool(context, static_cast(context->r[3])); } void DeleteMutant(PPCContext* context) { // todo: this should call kereleasemutant with some specific args xe::FatalError("DeleteMutant - need KeReleaseMutant(mutant, 1, 1, 0) "); } void DeleteTimer(PPCContext* context) { // todo: this should call KeCancelTimer xe::FatalError("DeleteTimer - need KeCancelTimer(mutant, 1, 1, 0) "); } void DeleteIoCompletion(PPCContext* context) {} void UnknownProcIoDevice(PPCContext* context) {} void CloseFileProc(PPCContext* context) {} void DeleteFileProc(PPCContext* context) {} void UnknownFileProc(PPCContext* context) {} void DeleteSymlink(PPCContext* context) { X_KSYMLINK* lnk = context->TranslateVirtualGPR(context->r[3]); context->r[3] = lnk->refed_object_maybe; xboxkrnl::xeObDereferenceObject(context, lnk->refed_object_maybe); } void KernelState::InitializeKernelGuestGlobals() { kernel_guest_globals_ = memory_->SystemHeapAlloc(sizeof(KernelGuestGlobals)); KernelGuestGlobals* block = memory_->TranslateVirtual(kernel_guest_globals_); memset(block, 0, sizeof(KernelGuestGlobals)); auto idle_process = memory()->TranslateVirtual(GetIdleProcess()); InitializeProcess(idle_process, X_PROCTYPE_IDLE, 0, 0, 0); idle_process->quantum = 0x7F; auto system_process = memory()->TranslateVirtual(GetSystemProcess()); InitializeProcess(system_process, X_PROCTYPE_SYSTEM, 2, 5, 9); SetProcessTLSVars(system_process, 32, 0, 0); uint32_t oddobject_offset = kernel_guest_globals_ + offsetof(KernelGuestGlobals, XboxKernelDefaultObject); // init unknown object block->XboxKernelDefaultObject.type = DISPATCHER_AUTO_RESET_EVENT; block->XboxKernelDefaultObject.signal_state = 1; block->XboxKernelDefaultObject.wait_list.flink_ptr = oddobject_offset + offsetof(X_DISPATCH_HEADER, wait_list.flink_ptr); block->XboxKernelDefaultObject.wait_list.blink_ptr = block->XboxKernelDefaultObject.wait_list.flink_ptr; // init thread object block->ExThreadObjectType.pool_tag = 0x65726854; block->ExThreadObjectType.allocate_proc = kernel_trampoline_group_.NewLongtermTrampoline(AllocateThread); block->ExThreadObjectType.free_proc = kernel_trampoline_group_.NewLongtermTrampoline(FreeThread); // several object types just call freepool/allocatepool uint32_t trampoline_allocatepool = kernel_trampoline_group_.NewLongtermTrampoline( SimpleForwardAllocatePoolTypeWithTag); uint32_t trampoline_freepool = kernel_trampoline_group_.NewLongtermTrampoline(SimpleForwardFreePool); // init event object block->ExEventObjectType.pool_tag = 0x76657645; block->ExEventObjectType.allocate_proc = trampoline_allocatepool; block->ExEventObjectType.free_proc = trampoline_freepool; // init mutant object block->ExMutantObjectType.pool_tag = 0x6174754D; block->ExMutantObjectType.allocate_proc = trampoline_allocatepool; block->ExMutantObjectType.free_proc = trampoline_freepool; block->ExMutantObjectType.delete_proc = kernel_trampoline_group_.NewLongtermTrampoline(DeleteMutant); // init semaphore obj block->ExSemaphoreObjectType.pool_tag = 0x616D6553; block->ExSemaphoreObjectType.allocate_proc = trampoline_allocatepool; block->ExSemaphoreObjectType.free_proc = trampoline_freepool; // init timer obj block->ExTimerObjectType.pool_tag = 0x656D6954; block->ExTimerObjectType.allocate_proc = trampoline_allocatepool; block->ExTimerObjectType.free_proc = trampoline_freepool; block->ExTimerObjectType.delete_proc = kernel_trampoline_group_.NewLongtermTrampoline(DeleteTimer); // iocompletion object block->IoCompletionObjectType.pool_tag = 0x706D6F43; block->IoCompletionObjectType.allocate_proc = trampoline_allocatepool; block->IoCompletionObjectType.free_proc = trampoline_freepool; block->IoCompletionObjectType.delete_proc = kernel_trampoline_group_.NewLongtermTrampoline(DeleteIoCompletion); block->IoCompletionObjectType.unknown_size_or_object_ = oddobject_offset; // iodevice object block->IoDeviceObjectType.pool_tag = 0x69766544; block->IoDeviceObjectType.allocate_proc = trampoline_allocatepool; block->IoDeviceObjectType.free_proc = trampoline_freepool; block->IoDeviceObjectType.unknown_size_or_object_ = oddobject_offset; block->IoDeviceObjectType.unknown_proc = kernel_trampoline_group_.NewLongtermTrampoline(UnknownProcIoDevice); // file object block->IoFileObjectType.pool_tag = 0x656C6946; block->IoFileObjectType.allocate_proc = trampoline_allocatepool; block->IoFileObjectType.free_proc = trampoline_freepool; block->IoFileObjectType.unknown_size_or_object_ = 0x38; // sizeof fileobject, i believe block->IoFileObjectType.close_proc = kernel_trampoline_group_.NewLongtermTrampoline(CloseFileProc); block->IoFileObjectType.delete_proc = kernel_trampoline_group_.NewLongtermTrampoline(DeleteFileProc); block->IoFileObjectType.unknown_proc = kernel_trampoline_group_.NewLongtermTrampoline(UnknownFileProc); // directory object block->ObDirectoryObjectType.pool_tag = 0x65726944; block->ObDirectoryObjectType.allocate_proc = trampoline_allocatepool; block->ObDirectoryObjectType.free_proc = trampoline_freepool; block->ObDirectoryObjectType.unknown_size_or_object_ = oddobject_offset; // symlink object block->ObSymbolicLinkObjectType.pool_tag = 0x626D7953; block->ObSymbolicLinkObjectType.allocate_proc = trampoline_allocatepool; block->ObSymbolicLinkObjectType.free_proc = trampoline_freepool; block->ObSymbolicLinkObjectType.unknown_size_or_object_ = oddobject_offset; block->ObSymbolicLinkObjectType.delete_proc = kernel_trampoline_group_.NewLongtermTrampoline(DeleteSymlink); #define offsetof32(s, m) static_cast(offsetof(s, m)) host_object_type_enum_to_guest_object_type_ptr_ = { {XObject::Type::Event, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, ExEventObjectType)}, {XObject::Type::Semaphore, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, ExSemaphoreObjectType)}, {XObject::Type::Thread, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, ExThreadObjectType)}, {XObject::Type::File, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, IoFileObjectType)}, {XObject::Type::Mutant, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, ExMutantObjectType)}, {XObject::Type::Device, kernel_guest_globals_ + offsetof32(KernelGuestGlobals, IoDeviceObjectType)}}; xboxkrnl::xeKeSetEvent(&block->UsbdBootEnumerationDoneEvent, 1, 0); } void KernelState::InitializeXbdmCpuCounters() { constexpr uint32_t counters_base_address = 0x91F00000; // These are not confirmed and there seems to be multiple types of counters, // but no idea how they're switched. For now this seems to be good enough. constexpr std::array xbdm_counters = { "load-hit-stores (S)", "instructions committed", "i-cache miss cycles", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "core 0 L2 data misses", "Bad counter number - must be 0-15."}; auto xbdm_range = memory_->LookupHeap(counters_base_address); if (!xbdm_range->AllocFixed( counters_base_address, 0x1000, 0, kMemoryAllocationCommit | kMemoryAllocationReserve, kMemoryProtectRead | kMemoryProtectWrite)) { return; } uint32_t address = counters_base_address; for (size_t i = 0; i < xbdm_counters.size(); i++) { xbdm_counters_address[i] = address; const std::string entry = xbdm_counters[i]; std::memcpy(memory_->TranslateVirtual(address), entry.c_str(), entry.size()); address += static_cast(entry.size()) + 1; } } } // namespace kernel } // namespace xe