/** ****************************************************************************** * 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/xam/xdbf/xdbf_io.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 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(); xam_state_ = std::make_unique(emulator, this); smc_ = std::make_unique(); InitializeKernelGuestGlobals(); kernel_version_ = KernelVersion(cvars::kernel_build_version); // 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); } 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; } 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; } bool KernelState::UpdateSpaData(vfs::Entry* spa_file_update) { vfs::File* file; if (spa_file_update->Open(vfs::FileAccess::kFileReadData, &file) != X_STATUS_SUCCESS) { return false; } std::vector data(spa_file_update->size()); size_t read_bytes = 0; if (file->ReadSync(data.data(), spa_file_update->size(), 0, &read_bytes) != X_STATUS_SUCCESS) { return false; } xam::SpaInfo new_spa_data(std::span(data.data(), data.size())); xam_state_->LoadSpaInfo(&new_spa_data); emulator_->game_info_database()->Update(&new_spa_data); return true; } 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); } // 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); } // 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->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 }; 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("game:", mount_path)) { return nullptr; } if (!module->path().starts_with(mount_path)) { return nullptr; } std::string resolved_path = ""; if (!file_system()->FindSymbolicLink("UPDATE:", 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::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() & kXNotifySystem) { has_notified_startup_ = true; // XN_SYS_UI (on, off) listener->EnqueueNotification(kXNotificationSystemUI, 1); listener->EnqueueNotification(kXNotificationSystemUI, 0); // XN_SYS_SIGNINCHANGED x2 listener->EnqueueNotification(kXNotificationSystemSignInChanged, 1); listener->EnqueueNotification(kXNotificationSystemSignInChanged, 1); listener->EnqueueNotification(kXNotificationDvdDriveTrayStateChanged, X_DVD_DISC_STATE::XBOX_360_GAME_DISC); } } 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 {}", 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); } 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::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