/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2013 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 "xenia/base/assert.h" #include "xenia/base/string.h" #include "xenia/cpu/processor.h" #include "xenia/emulator.h" #include "xenia/kernel/apps/apps.h" #include "xenia/kernel/dispatcher.h" #include "xenia/kernel/objects/xevent.h" #include "xenia/kernel/objects/xmodule.h" #include "xenia/kernel/objects/xnotify_listener.h" #include "xenia/kernel/objects/xthread.h" #include "xenia/kernel/objects/xuser_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam_module.h" #include "xenia/kernel/xboxkrnl_module.h" #include "xenia/kernel/xboxkrnl_private.h" #include "xenia/kernel/xobject.h" DEFINE_bool(headless, false, "Don't display any UI, using defaults for prompts as needed."); DEFINE_string(content_root, "content", "Root path for content (save/etc) storage."); namespace xe { namespace kernel { // 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()), object_table_(nullptr), has_notified_startup_(false), process_type_(X_PROCTYPE_USER), process_info_block_address_(0) { processor_ = emulator->processor(); file_system_ = emulator->file_system(); dispatcher_ = new Dispatcher(this); app_manager_ = std::make_unique(); user_profile_ = std::make_unique(); auto content_root = xe::to_wstring(FLAGS_content_root); content_root = xe::to_absolute_path(content_root); content_manager_ = std::make_unique(this, content_root); object_table_ = new ObjectTable(); assert_null(shared_kernel_state_); shared_kernel_state_ = this; process_info_block_address_ = memory_->SystemHeapAlloc(0x60); auto pib = memory_->TranslateVirtual(process_info_block_address_); // TODO(benvanik): figure out what this list is. pib->unk_04 = pib->unk_08 = 0; pib->unk_0C = 0x0000007F; pib->unk_10 = 0x001F0000; pib->thread_count = 0; pib->unk_1B = 0x06; pib->kernel_stack_size = 16 * 1024; pib->process_type = process_type_; // TODO(benvanik): figure out what this list is. pib->unk_54 = pib->unk_58 = 0; apps::RegisterApps(this, app_manager_.get()); } KernelState::~KernelState() { SetExecutableModule(nullptr); if (process_info_block_address_) { memory_->SystemHeapFree(process_info_block_address_); } executable_module_.reset(); user_modules_.clear(); kernel_modules_.clear(); // Delete all objects. delete object_table_; // Shutdown apps. app_manager_.reset(); delete dispatcher_; 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_); return executable_module_->xex_header()->execution_info.title_id; } uint32_t KernelState::process_type() const { auto pib = memory_->TranslateVirtual(process_info_block_address_); return pib->process_type; } void KernelState::set_process_type(uint32_t value) { auto pib = memory_->TranslateVirtual(process_info_block_address_); pib->process_type = uint8_t(value); } void KernelState::RegisterModule(XModule* module) {} void KernelState::UnregisterModule(XModule* module) {} bool KernelState::IsKernelModule(const char* name) { if (!name) { // Executing module isn't a kernel module. return false; } std::lock_guard lock(object_mutex_); for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return true; } } return false; } object_ref KernelState::GetModule(const char* name) { if (!name) { // NULL name = self. // TODO(benvanik): lookup module from caller address. return GetExecutableModule(); } else if (strcasecmp(name, "kernel32.dll") == 0) { // Some games request this, for some reason. wtf. return nullptr; } std::lock_guard lock(object_mutex_); for (auto kernel_module : kernel_modules_) { if (kernel_module->Matches(name)) { return retain_object(kernel_module.get()); } } for (auto user_module : user_modules_) { if (user_module->Matches(name)) { return retain_object(user_module.get()); } } return nullptr; } 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); auto header = executable_module_->xex_header(); if (header) { auto pib = memory_->TranslateVirtual( process_info_block_address_); pib->tls_data_size = header->tls_info.data_size; pib->tls_raw_data_size = header->tls_info.raw_data_size; pib->tls_slot_size = header->tls_info.slot_count * 4; } // Setup the kernel's XexExecutableModuleHandle field auto exp = processor()->export_resolver()->GetExportByOrdinal( "xboxkrnl.exe", ordinals::XexExecutableModuleHandle); if (exp) { auto variable_ptr = memory()->TranslateVirtual*>(exp->variable_ptr); *variable_ptr = module->hmodule_ptr(); } } void KernelState::LoadKernelModule(object_ref kernel_module) { std::lock_guard lock(object_mutex_); kernel_modules_.push_back(std::move(kernel_module)); } object_ref KernelState::LoadUserModule(const char* raw_name) { // Some games try to load relative to launch module, others specify full path. std::string name = xe::find_name_from_path(raw_name); std::string path(raw_name); if (name == raw_name) { path = xe::join_paths(xe::find_base_path(executable_module_->path()), name); } object_ref module; { std::lock_guard lock(object_mutex_); // See if we've already loaded it for (auto& existing_module : user_modules_) { if (existing_module->path() == path) { existing_module->Retain(); return retain_object(existing_module.get()); } } // Module wasn't loaded, so load it. module = object_ref(new XUserModule(this, path.c_str())); X_STATUS status = module->LoadFromFile(path); if (XFAILED(status)) { return nullptr; } // Retain when putting into the listing. module->Retain(); user_modules_.push_back(module); } module->Dump(); auto xex_header = module->xex_header(); if (xex_header->exe_entry_point) { // 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, xex_header->exe_entry_point, args, xe::countof(args)); } return module; } void KernelState::RegisterThread(XThread* thread) { std::lock_guard lock(object_mutex_); threads_by_id_[thread->thread_id()] = thread; auto pib = memory_->TranslateVirtual(process_info_block_address_); pib->thread_count = pib->thread_count + 1; } void KernelState::UnregisterThread(XThread* thread) { std::lock_guard lock(object_mutex_); auto it = threads_by_id_.find(thread->thread_id()); if (it != threads_by_id_.end()) { threads_by_id_.erase(it); } auto pib = memory_->TranslateVirtual(process_info_block_address_); pib->thread_count = pib->thread_count - 1; } void KernelState::OnThreadExecute(XThread* thread) { std::lock_guard lock(object_mutex_); // 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_) { auto xex_header = user_module->xex_header(); if (xex_header->exe_entry_point) { uint64_t args[] = { user_module->handle(), 2, // DLL_THREAD_ATTACH 0, // 0 because always dynamic }; processor()->Execute(thread_state, xex_header->exe_entry_point, args, xe::countof(args)); } } } void KernelState::OnThreadExit(XThread* thread) { std::lock_guard lock(object_mutex_); // 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_) { auto xex_header = user_module->xex_header(); if (xex_header->exe_entry_point) { uint64_t args[] = { user_module->handle(), 3, // DLL_THREAD_DETACH 0, // 0 because always dynamic }; processor()->Execute(thread_state, xex_header->exe_entry_point, args, xe::countof(args)); } } } object_ref KernelState::GetThreadByID(uint32_t thread_id) { std::lock_guard lock(object_mutex_); 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) { std::lock_guard lock(object_mutex_); notify_listeners_.push_back(retain_object(listener)); // Games seem to expect a few notifications on startup, only for the first // listener. // http://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); // XN_SYS_INPUTDEVICESCHANGED x2 listener->EnqueueNotification(0x00000012, 0); listener->EnqueueNotification(0x00000012, 0); // XN_SYS_INPUTDEVICECONFIGCHANGED x2 listener->EnqueueNotification(0x00000013, 0); listener->EnqueueNotification(0x00000013, 0); } } void KernelState::UnregisterNotifyListener(XNotifyListener* listener) { std::lock_guard lock(object_mutex_); 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) { std::lock_guard lock(object_mutex_); for (auto it = notify_listeners_.begin(); it != notify_listeners_.end(); ++it) { (*it)->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); if (ev) { ev->Set(0, false); } } if (XOverlappedGetCompletionRoutine(ptr)) { X_HANDLE thread_handle = XOverlappedGetContext(ptr); auto thread = object_table()->LookupObject(thread_handle); if (thread) { uint32_t routine = XOverlappedGetCompletionRoutine(ptr); uint64_t args[] = { result, length, overlapped_ptr, }; // TODO(benvanik): queue APC on the thread that requested the overlapped // operation. assert_always(); // THIS IS WRONG, for testing only: processor()->Execute(XThread::GetCurrentThread()->thread_state(), routine, args, xe::countof(args)); } } } void KernelState::CompleteOverlappedImmediate(uint32_t overlapped_ptr, X_RESULT result) { CompleteOverlappedImmediateEx(overlapped_ptr, result, result, 0); } 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); } } // namespace kernel } // namespace xe