From f0dbd992b55206f1d87260c950de67e7c85aa7ec Mon Sep 17 00:00:00 2001 From: Cancerous Date: Wed, 27 Nov 2019 21:31:03 -0500 Subject: [PATCH] [Kernel, UI] threaded UI notifications from emoose Issue 1296 added fixups for hdd detection in more games (wheelman for example) changed filter that stops notification spam --- src/xenia/kernel/xam/xam_ui.cc | 248 ++++++++++++++++++++-------- src/xenia/kernel/xnotifylistener.cc | 10 +- 2 files changed, 187 insertions(+), 71 deletions(-) diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index e2e01d015..507579145 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -14,6 +14,7 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_private.h" +#include "xenia/kernel/xthread.h" #include "xenia/ui/imgui_dialog.h" #include "xenia/ui/window.h" #include "xenia/xbox.h" @@ -111,16 +112,27 @@ dword_result_t XamShowMessageBoxUI(dword_t user_index, lpwstring_t title_ptr, buttons.push_back(button); } + XELOGI( + "XamShowMessageBoxUI(%d, %.8X(%S), %.8X(%S), %d, %.8X(%S), %d, %X, %.8X, " + "%.8X)", + user_index, title_ptr, title.c_str(), text_ptr, text.c_str(), + button_count, button_ptrs, all_buttons.c_str(), active_button, flags, + result_ptr, overlapped); + + // Set overlapped result to X_ERROR_IO_PENDING + if (overlapped) { + XOverlappedSetResult((void*)overlapped.host_address(), X_ERROR_IO_PENDING); + } + // Broadcast XN_SYS_UI = true kernel_state()->BroadcastNotification(0x9, true); - uint32_t chosen_button; - if (cvars::headless) { - // Auto-pick the focused button. - chosen_button = active_button; - } else { + // Auto-pick the focused button. + //uint32_t chosen_button = active_button; + + if (!cvars::headless) { auto display_window = kernel_state()->emulator()->display_window(); - xe::threading::Fence fence; + ++xam_dialogs_shown_; display_window->loop()->PostSynchronous([&]() { // TODO(benvanik): setup icon states. switch (flags & 0xF) { @@ -137,25 +149,57 @@ dword_result_t XamShowMessageBoxUI(dword_t user_index, lpwstring_t title_ptr, // config.pszMainIcon = TD_INFORMATION_ICON; break; } + + auto chosen_button = new uint32_t(); + auto fence = new xe::threading::Fence(); (new MessageBoxDialog(display_window, title, text, buttons, active_button, - &chosen_button)) - ->Then(&fence); + chosen_button)) + ->Then(fence); + + // The function to be run once dialog has finished + auto ui_fn = [fence, result_ptr, chosen_button, overlapped]() { + fence->Wait(); + delete fence; + --xam_dialogs_shown_; + + *result_ptr = *chosen_button; + delete chosen_button; + + if (overlapped) { + // TODO: this will set overlapped's context to ui_threads thread + // ID, is that a good idea? + kernel_state()->CompleteOverlappedImmediate(overlapped, + X_ERROR_SUCCESS); + } + + // Broadcast XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, false); + + return 0; + }; + + // Create a host thread to run the function above + auto ui_thread = kernel::object_ref( + new kernel::XHostThread(kernel_state(), 128 * 1024, 0, ui_fn)); + ui_thread->set_name("XamShowMessageBoxUI Thread"); + ui_thread->Create(); }); - ++xam_dialogs_shown_; - fence.Wait(); - --xam_dialogs_shown_; - } - *result_ptr = chosen_button; - - // Broadcast XN_SYS_UI = false - kernel_state()->BroadcastNotification(0x9, false); - - if (overlapped) { - kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); - return X_ERROR_IO_PENDING; } else { - return X_ERROR_SUCCESS; + // Auto-pick the focused button. + *result_ptr = (uint32_t)active_button; + + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); + } + + // Broadcast XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, false); } + uint32_t result = X_ERROR_SUCCESS; + if (overlapped) { + return X_ERROR_IO_PENDING; + } + return result; } DECLARE_XAM_EXPORT1(XamShowMessageBoxUI, kUI, kImplemented); @@ -234,8 +278,13 @@ dword_result_t XamShowKeyboardUI(dword_t user_index, dword_t flags, lpwstring_t description, lpwstring_t buffer, dword_t buffer_length, pointer_t overlapped) { - if (!buffer) { + // overlapped should always be set, xam seems to check for this specifically + if (!overlapped) { + assert_always(); return X_ERROR_INVALID_PARAMETER; + } else { + // Set overlapped result to X_ERROR_IO_PENDING + XOverlappedSetResult((void*)overlapped.host_address(), X_ERROR_IO_PENDING); } // Broadcast XN_SYS_UI = true @@ -248,48 +297,77 @@ dword_result_t XamShowKeyboardUI(dword_t user_index, dword_t flags, xe::store_and_swap(buffer, default_text.value()); } + // TODO: we should probably setup a thread to complete the overlapped a few + // seconds after this returns, to simulate the user taking a few seconds to + // enter text + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); + } + // Broadcast XN_SYS_UI = false kernel_state()->BroadcastNotification(0x9, false); - if (overlapped) { - kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); - return X_ERROR_IO_PENDING; - } else { - return X_ERROR_SUCCESS; - } + return X_ERROR_IO_PENDING; } + + // Instead of waiting for the keyboard dialog to finish before returning, + // we'll create a thread that'll wait for it instead, and return immediately. + // This way we can let the game run any "code-to-run-while-UI-is-active" code + // that it might need to. - std::wstring out_text; + ++xam_dialogs_shown_; auto display_window = kernel_state()->emulator()->display_window(); - xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { + auto out_text = new std::wstring(); + auto fence = new xe::threading::Fence(); + + // Create the dialog (new KeyboardInputDialog(display_window, title ? title.value() : L"", description ? description.value() : L"", default_text ? default_text.value() : L"", - &out_text, buffer_length)) - ->Then(&fence); + out_text, buffer_length)) + ->Then(fence); + + // The function to be run once dialog has finished + auto ui_fn = [fence, out_text, buffer, buffer_length, overlapped]() { + fence->Wait(); + delete fence; + --xam_dialogs_shown_; + + // Zero the output buffer. + std::memset(buffer, 0, buffer_length * 2); + + // Copy the string. + size_t size = buffer_length; + if (size > out_text->size()) { + size = out_text->size(); + } + + xe::copy_and_swap((wchar_t*)buffer.host_address(), out_text->c_str(), + size); + delete out_text; + + // TODO: this will set overlapped's context to ui_threads thread ID + // is that a good idea? + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); + } + + // Broadcast XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, false); + + return 0; + }; + + // Create a host thread to run the function above + auto ui_thread = kernel::object_ref( + new kernel::XHostThread(kernel_state(), 128 * 1024, 0, ui_fn)); + ui_thread->set_name("XamShowKeyboardUI Thread"); + ui_thread->Create(); }); - ++xam_dialogs_shown_; - fence.Wait(); - --xam_dialogs_shown_; - // Zero the output buffer. - std::memset(buffer, 0, buffer_length * 2); - - // Truncate the string. - out_text = out_text.substr(0, buffer_length - 1); - xe::store_and_swap(buffer, out_text); - - // Broadcast XN_SYS_UI = false - kernel_state()->BroadcastNotification(0x9, false); - - if (overlapped) { - kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); - return X_ERROR_IO_PENDING; - } else { - return X_ERROR_SUCCESS; - } + return X_ERROR_IO_PENDING; } DECLARE_XAM_EXPORT1(XamShowKeyboardUI, kUI, kImplemented); @@ -298,31 +376,63 @@ dword_result_t XamShowDeviceSelectorUI(dword_t user_index, dword_t content_type, qword_t total_requested, lpdword_t device_id_ptr, pointer_t overlapped) { - // NOTE: 0xF00D0000 magic from xam_content.cc - switch (content_type) { - case 1: // save game - *device_id_ptr = 0xF00D0000 | 0x0001; - break; - case 2: // marketplace - *device_id_ptr = 0xF00D0000 | 0x0002; - break; - case 3: // title/publisher update? - *device_id_ptr = 0xF00D0000 | 0x0003; - break; - default: - assert_unhandled_case(content_type); - *device_id_ptr = 0xF00D0000 | 0x0001; - break; + + // Set overlapped to X_ERROR_IO_PENDING + if (overlapped) { + XOverlappedSetResult((void*)overlapped.host_address(), X_ERROR_IO_PENDING); } - // Broadcast XN_SYS_UI = true followed by XN_SYS_UI = false + // Broadcast XN_SYS_UI = true kernel_state()->BroadcastNotification(0x9, true); - kernel_state()->BroadcastNotification(0x9, false); + + auto ui_fn = [content_type, device_id_ptr, overlapped]() { + XELOGI("XamShowDeviceSelectorUI Content_type:(%X) device_id_ptr: %.8X overlapped:(%X)", + content_type, device_id_ptr, (bool)overlapped); + + // NOTE: 0xF00D0000 magic from xam_content.cc + switch (content_type) { + case 1: // save game + *device_id_ptr = 0xF00D0000 | 0x0001; + break; + case 2: // marketplace + *device_id_ptr = 0xF00D0000 | 0x0002; + break; + case 3: // title/publisher update? + *device_id_ptr = 0xF00D0000 | 0x0003; + break; + default: + assert_unhandled_case(content_type); + *device_id_ptr = 0xF00D0000 | 0x0001; + break; + } + + if (overlapped) { + kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); + } + + // Sleep for 1 second, act like user is making a choice + xe::threading::Sleep(std::chrono::milliseconds(500)); + + // Broadcast XN_SYS_UI = true followed by XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, true); + kernel_state()->BroadcastNotification(0x9, false); + + // return 0; + return X_ERROR_SUCCESS; + }; if (overlapped) { - kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); + // Create a host thread to run the function above + auto ui_thread = kernel::object_ref( + new kernel::XHostThread(kernel_state(), 128 * 1024, 0, ui_fn)); + ui_thread->set_name("XamShowDeviceSelectorUI Thread"); + ui_thread->Create(); + while (ui_thread->last_error() != X_ERROR_SUCCESS) { + xe::threading::Sleep(std::chrono::milliseconds(101)); + } return X_ERROR_IO_PENDING; } else { + ui_fn(); return X_ERROR_SUCCESS; } } diff --git a/src/xenia/kernel/xnotifylistener.cc b/src/xenia/kernel/xnotifylistener.cc index ed890eebb..26644c235 100644 --- a/src/xenia/kernel/xnotifylistener.cc +++ b/src/xenia/kernel/xnotifylistener.cc @@ -11,6 +11,7 @@ #include "xenia/base/byte_stream.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/base/logging.h" namespace xe { namespace kernel { @@ -32,12 +33,17 @@ void XNotifyListener::Initialize(uint64_t mask) { void XNotifyListener::EnqueueNotification(XNotificationID id, uint32_t data) { // Ignore if the notification doesn't match our mask. // TODO(Gliniak): (confirm) mask 0x01 means accept all - if ((mask_ & ((id >> 25) & 0x3F)) == 0 && mask_ != 0x01) { + XELOGI("XnotifyListener::EnqueueNotification( Mask: %.8X ID: %.8X Drop?:(%X) )", + mask_, id, + ((mask_ & ((id >> 25) & 0x3F)) == 0 && (mask_ != 0x01) && ((id >> 5) != 0))); + if ((mask_ & ((id >> 25) & 0x3F)) == 0 && (mask_ != 0x01) && ((id >> 5) != 0)) { return; } auto global_lock = global_critical_region_.Acquire(); - notifications_.push_back(std::pair(id, data)); + // notifications_.push_back(std::pair(id, data)); + // TEST + notifications_.emplace_back(id, data); wait_handle_->Set(); }