/** ****************************************************************************** * 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 "xenia/kernel/kernel_state.h" #include #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" #include "xenia/base/string.h" #include "xenia/cpu/processor.h" #include "xenia/emulator.h" #include "xenia/hid/input_system.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_module.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" DEFINE_bool(apply_title_update, true, "Apply title updates.", "Kernel"); DEFINE_uint32(max_signed_profiles, 4, "Limits how many profiles can be assigned. Possible values: 1-4", "Kernel"); DEFINE_uint32(kernel_build_version, 1888, "Define current kernel version", "Kernel"); namespace xe { namespace kernel { constexpr uint32_t kDeferredOverlappedDelayMillis = 100; // 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(); app_manager_ = std::make_unique(); achievement_manager_ = std::make_unique(); user_profiles_.emplace(0, std::make_unique(0)); InitializeKernelGuestGlobals(); kernel_version_ = KernelVersion(cvars::kernel_build_version); auto content_root = emulator_->content_root(); if (!content_root.empty()) { content_root = std::filesystem::absolute(content_root); } content_manager_ = std::make_unique(this, content_root); // Hardcoded maximum of 2048 TLS slots. tls_bitmap_.Resize(2048); 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); xam::AppManager::RegisterApps(this, app_manager_.get()); } 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(); // Shutdown apps. app_manager_.reset(); assert_true(shared_kernel_state_ == this); shared_kernel_state_ = nullptr; } KernelState* KernelState::shared() { return shared_kernel_state_; } uint32_t KernelState::title_id() const { 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_system_type(uint32_t title_id) { if (!title_id) { return true; } if ((title_id & 0xFF000000) == 0x58000000u) { return (title_id & 0xFF0000) != 0x410000; // if 'X' but not 'XA' (XBLA) } return (title_id >> 16) == 0xFFFE; } util::XdbfGameData KernelState::title_xdbf() const { return module_xdbf(executable_module_); } util::XdbfGameData 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))) { util::XdbfGameData db(memory()->TranslateVirtual(resource_data), resource_size); return db; } return util::XdbfGameData(nullptr, resource_size); } uint32_t KernelState::AllocateTLS() { return uint32_t(tls_bitmap_.Acquire()); } void KernelState::FreeTLS(uint32_t slot) { const std::vector> threads = object_table()->GetObjectsByType(); for (const object_ref& thread : threads) { if (thread->is_guest_thread()) { thread->SetTLSValue(slot, 0); } } tls_bitmap_.Release(slot); } 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; } 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(); // Resume the thread now. // If the debugger has requested a suspend this will just decrement the // suspend count without resuming it until the debugger wants. thread->Resume(); 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); } // 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) { assert_not_null(executable_module_); 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->path() == 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; } 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 }; 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 module) { X_RESULT result = X_STATUS_SUCCESS; if (!cvars::apply_title_update) { return result; } std::vector tu_list = content_manager()->ListContent(1, xe::XContentType::kInstaller, module->title_id()); if (tu_list.empty()) { return result; } uint32_t disc_number = -1; if (module->is_multi_disc_title()) { disc_number = module->disc_number(); } // TODO(Gliniak): Support for selecting from multiple TUs const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front(); X_RESULT open_status = content_manager()->OpenContent("UPDATE", title_update, disc_number); // Use the corresponding patch for the launch module std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name()); std::string resolved_path = ""; file_system()->FindSymbolicLink("UPDATE:", resolved_path); xe::vfs::Entry* patch_entry = kernel_state()->file_system()->ResolvePath( resolved_path + patch_xexp.generic_string()); if (patch_entry) { const std::string patch_path = patch_entry->absolute_path(); XELOGI("Loading XEX patch from {}", patch_path); auto patch_module = object_ref(new UserModule(this)); result = patch_module->LoadFromFile(patch_path); if (result != X_STATUS_SUCCESS) { XELOGE("Failed to load XEX patch, code: {}", result); return X_STATUS_UNSUCCESSFUL; } result = patch_module->xex_module()->ApplyPatch(module->xex_module()); if (result != X_STATUS_SUCCESS) { XELOGE("Failed to apply XEX patch, code: {}", result); return X_STATUS_UNSUCCESSFUL; } } 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::TerminateTitle() { XELOGD("KernelState::TerminateTitle"); 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++) { X_STATUS status = user_modules_[i]->Unload(); assert_true(XSUCCEEDED(status)); object_table_.RemoveHandle(user_modules_[i]->handle()); } user_modules_.clear(); // Release all objects in the object table. object_table_.PurgeAllObjects(); // Unregister all notify listeners. notify_listeners_.clear(); // Clear the TLS map. tls_bitmap_.Reset(); // 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(), 2, // DLL_THREAD_ATTACH 0, // 0 because always dynamic }; 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); } 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() & 0x00000001) { has_notified_startup_ = true; // XN_SYS_UI (on, off) listener->EnqueueNotification(0x00000009, 1); listener->EnqueueNotification(0x00000009, 0); // XN_SYS_SIGNINCHANGED x2 listener->EnqueueNotification(0x0000000A, 1); listener->EnqueueNotification(0x0000000A, 1); } } 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(); } xe::threading::Sleep( std::chrono::milliseconds(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 auto tls_bitmap = tls_bitmap_.data(); stream->Write(uint32_t(tls_bitmap.size())); for (size_t i = 0; i < tls_bitmap.size(); i++) { stream->Write(tls_bitmap[i]); } // 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 {}", 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); } 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(); uint32_t pKeTimeStampBundle = memory_->SystemHeapAlloc(sizeof(X_TIME_STAMP_BUNDLE)); 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); // Read the TLS allocation bitmap auto num_bitmap_entries = stream->Read(); auto& tls_bitmap = tls_bitmap_.data(); tls_bitmap.resize(num_bitmap_entries); for (uint32_t i = 0; i < num_bitmap_entries; i++) { tls_bitmap[i] = stream->Read(); } 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; } 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::UpdateUsedUserProfiles() { const std::bitset<4> used_slots = GetConnectedUsers(); for (uint32_t i = 1; i < cvars::max_signed_profiles; i++) { bool is_used = used_slots.test(i); if (IsUserSignedIn(i) && !is_used) { user_profiles_.erase(i); BroadcastNotification(0x12, 0); } if (!IsUserSignedIn(i) && is_used) { user_profiles_.emplace(i, std::make_unique(i)); BroadcastNotification(0x12, 0); } } } void KernelState::InitializeProcess(X_KPROCESS* process, uint32_t type, char unk_18, char unk_19, char unk_1A) { uint32_t guest_kprocess = memory()->HostToGuestVirtual(process); uint32_t thread_list_guest_ptr = guest_kprocess + offsetof(X_KPROCESS, thread_list); process->unk_18 = unk_18; process->unk_19 = unk_19; process->unk_1A = unk_1A; util::XeInitializeListHead(&process->thread_list, thread_list_guest_ptr); process->unk_0C = 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->unk_1B = 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->bitmap[word_index] = -1; } // set remainder of bitset if (((num_slots + 3) & 0x1C) != 0) process->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->unk_0C = 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, OddObj); // init unknown object block->OddObj.field0 = 0x1000000; block->OddObj.field4 = 1; block->OddObj.points_to_self = oddobject_offset + offsetof(X_UNKNOWN_TYPE_REFED, points_to_self); block->OddObj.points_to_prior = block->OddObj.points_to_self; // 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); } } // namespace kernel } // namespace xe