/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "el/elemental_forms.h" #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam_private.h" #include "xenia/ui/window.h" #include "xenia/xbox.h" namespace xe { namespace kernel { SHIM_CALL XamIsUIActive_shim(PPCContext* ppc_context, KernelState* kernel_state) { XELOGD("XamIsUIActive()"); SHIM_SET_RETURN_32(el::ModalForm::is_any_visible()); } class MessageBoxWindow : public el::ModalForm { public: explicit MessageBoxWindow(xe::threading::Fence* fence) : ModalForm([fence]() { fence->Signal(); }) {} ~MessageBoxWindow() override = default; // TODO(benvanik): icon. void Show(el::Element* root_element, std::wstring title, std::wstring message, std::vector buttons, uint32_t default_button, uint32_t* out_chosen_button) { title_ = std::move(title); message_ = std::move(message); buttons_ = std::move(buttons); default_button_ = default_button; out_chosen_button_ = out_chosen_button; // In case we are cancelled, always set default button. *out_chosen_button_ = default_button; ModalForm::Show(root_element); EnsureFocus(); } protected: void BuildUI() override { using namespace el::dsl; // NOLINT(build/namespaces) set_text(xe::to_string(title_)); auto button_bar = LayoutBoxNode().distribution_position( LayoutDistributionPosition::kRightBottom); for (int32_t i = 0; i < buttons_.size(); ++i) { button_bar.child(ButtonNode(xe::to_string(buttons_[i]).c_str()) .id(100 + i) .data(100 + i)); } LoadNodeTree( LayoutBoxNode() .axis(Axis::kY) .gravity(Gravity::kAll) .position(LayoutPosition::kLeftTop) .distribution(LayoutDistribution::kAvailable) .distribution_position(LayoutDistributionPosition::kLeftTop) .child(LabelNode(xe::to_string(message_).c_str())) .child(button_bar)); auto default_button = GetElementById(100 + default_button_); el::Button::set_auto_focus_state(true); default_button->set_focus(el::FocusReason::kNavigation); } bool OnEvent(const el::Event& ev) override { if (ev.target->IsOfType() && (ev.type == el::EventType::kClick || ev.special_key == el::SpecialKey::kEnter)) { int data_value = ev.target->data.as_integer(); if (data_value) { *out_chosen_button_ = data_value - 100; } Die(); return true; } return ModalForm::OnEvent(ev); } std::wstring title_; std::wstring message_; std::vector buttons_; uint32_t default_button_ = 0; uint32_t* out_chosen_button_ = nullptr; }; // http://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/?jdfwkey=sb0vm SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, KernelState* kernel_state) { uint32_t user_index = SHIM_GET_ARG_32(0); uint32_t title_ptr = SHIM_GET_ARG_32(1); uint32_t text_ptr = SHIM_GET_ARG_32(2); uint32_t button_count = SHIM_GET_ARG_32(3); uint32_t button_ptrs = SHIM_GET_ARG_32(4); uint32_t active_button = SHIM_GET_ARG_32(5); uint32_t flags = SHIM_GET_ARG_32(6); uint32_t result_ptr = SHIM_GET_ARG_32(7); uint32_t overlapped_ptr = SHIM_GET_ARG_32(8); auto title = xe::load_and_swap(SHIM_MEM_ADDR(title_ptr)); auto text = xe::load_and_swap(SHIM_MEM_ADDR(text_ptr)); std::vector buttons; std::wstring all_buttons; for (uint32_t j = 0; j < button_count; ++j) { uint32_t button_ptr = SHIM_MEM_32(button_ptrs + j * 4); auto button = xe::load_and_swap(SHIM_MEM_ADDR(button_ptr)); all_buttons.append(button); if (j + 1 < button_count) { all_buttons.append(L" | "); } buttons.push_back(button); } XELOGD( "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_ptr); uint32_t chosen_button; if (FLAGS_headless) { // Auto-pick the focused button. chosen_button = active_button; } else { auto display_window = kernel_state->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { auto root_element = display_window->root_element(); auto window = new MessageBoxWindow(&fence); switch (flags & 0xF) { case 0: // config.pszMainIcon = nullptr; break; case 1: // config.pszMainIcon = TD_ERROR_ICON; break; case 2: // config.pszMainIcon = TD_WARNING_ICON; break; case 3: // config.pszMainIcon = TD_INFORMATION_ICON; break; } window->Show(root_element, title, text, buttons, active_button, &chosen_button); }); fence.Wait(); } SHIM_SET_MEM_32(result_ptr, chosen_button); kernel_state->CompleteOverlappedImmediate(overlapped_ptr, X_ERROR_SUCCESS); SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); } class KeyboardInputWindow : public el::ModalForm { public: explicit KeyboardInputWindow(xe::threading::Fence* fence) : ModalForm([fence]() { fence->Signal(); }) {} ~KeyboardInputWindow() override = default; // TODO(benvanik): icon. void Show(el::Element* root_element, std::wstring title, std::wstring description, std::wstring default_text, std::wstring* out_text, size_t max_length) { title_ = std::move(title); description_ = std::move(description); default_text_ = std::move(default_text); out_text_ = out_text; max_length_ = max_length; // Incase we're cancelled, set as the default text. if (out_text) { *out_text = default_text; } ModalForm::Show(root_element); EnsureFocus(); } protected: void BuildUI() override { using namespace el::dsl; // NOLINT(build/namespaces) set_text(xe::to_string(title_)); LoadNodeTree( LayoutBoxNode() .axis(Axis::kY) .gravity(Gravity::kAll) .position(LayoutPosition::kLeftTop) .min_width(300) .distribution(LayoutDistribution::kAvailable) .distribution_position(LayoutDistributionPosition::kLeftTop) .child(LabelNode(xe::to_string(description_).c_str())) .child(LayoutBoxNode() .axis(Axis::kX) .distribution(LayoutDistribution::kGravity) .child(TextBoxNode() .id("text_input") .gravity(Gravity::kLeftRight) .placeholder(xe::to_string(default_text_)) .min_width(150) .autofocus(true))) .child(LayoutBoxNode() .distribution_position( LayoutDistributionPosition::kRightBottom) .child(ButtonNode("OK").id("ok_button")) .child(ButtonNode("Cancel").id("cancel_button")))); } bool OnEvent(const el::Event& ev) override { if (ev.special_key == el::SpecialKey::kEnter || (ev.target->id() == TBIDC("ok_button") && ev.type == el::EventType::kClick)) { // Pressed enter or clicked OK // Grab the text. if (out_text_) { *out_text_ = xe::to_wstring(GetElementById("text_input")->text()); } Die(); return true; } else if (ev.target->id() == TBIDC("cancel_button") && ev.type == el::EventType::kClick) { // Cancel. Die(); return true; } return ModalForm::OnEvent(ev); } std::wstring title_; std::wstring description_; std::wstring default_text_; std::wstring* out_text_ = nullptr; size_t max_length_ = 0; }; // http://www.se7ensins.com/forums/threads/release-how-to-use-xshowkeyboardui-release.906568/ dword_result_t XamShowKeyboardUI(dword_t r3, dword_t flags, lpwstring_t default_text, lpwstring_t title, lpwstring_t description, lpwstring_t buffer, dword_t buffer_length, pointer_t overlapped) { // Unknown parameters. I've only seen zero. assert_zero(r3); if (!buffer) { return X_ERROR_INVALID_PARAMETER; } if (FLAGS_headless) { // Redirect default_text back into the buffer. std::memset(buffer, 0, buffer_length * 2); if (default_text) { xe::store_and_swap(buffer, default_text.value()); } if (overlapped) { kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); return X_ERROR_IO_PENDING; } else { return X_ERROR_SUCCESS; } } std::wstring out_text; auto display_window = kernel_state()->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { auto root_element = display_window->root_element(); auto window = new KeyboardInputWindow(&fence); window->Show(root_element, title ? title.value() : L"", description ? description.value() : L"", default_text ? default_text.value() : L"", &out_text, buffer_length); }); fence.Wait(); // 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); if (overlapped) { kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); return X_ERROR_IO_PENDING; } else { return X_ERROR_SUCCESS; } } DECLARE_XAM_EXPORT(XamShowKeyboardUI, ExportTag::kImplemented); dword_result_t XamShowDeviceSelectorUI(dword_t user_index, dword_t content_type, dword_t content_flags, 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; } if (overlapped) { kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS); return X_ERROR_IO_PENDING; } else { return X_ERROR_SUCCESS; } } DECLARE_XAM_EXPORT(XamShowDeviceSelectorUI, ExportTag::kImplemented); class DirtyDiscWindow : public el::ModalForm { public: explicit DirtyDiscWindow(xe::threading::Fence* fence) : ModalForm([fence]() { fence->Signal(); }) {} ~DirtyDiscWindow() override = default; protected: void BuildUI() override { using namespace el::dsl; // NOLINT(build/namespaces) set_text("Disc Read Error"); LoadNodeTree( LayoutBoxNode() .axis(Axis::kY) .gravity(Gravity::kAll) .position(LayoutPosition::kLeftTop) .distribution(LayoutDistribution::kAvailable) .distribution_position(LayoutDistributionPosition::kLeftTop) .child(LabelNode( "There's been an issue reading content from the game disc.")) .child(LabelNode( "This is likely caused by bad or unimplemented file IO " "calls.")) .child(LayoutBoxNode() .distribution_position( LayoutDistributionPosition::kRightBottom) .child(ButtonNode("Exit").id("exit_button")))); } bool OnEvent(const el::Event& ev) override { if (ev.target->id() == TBIDC("exit_button") && ev.type == el::EventType::kClick) { Die(); return true; } return ModalForm::OnEvent(ev); } }; SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context, KernelState* kernel_state) { uint32_t user_index = SHIM_GET_ARG_32(0); XELOGD("XamShowDirtyDiscErrorUI(%d)", user_index); if (FLAGS_headless) { assert_always(); exit(1); return; } auto display_window = kernel_state->emulator()->display_window(); xe::threading::Fence fence; display_window->loop()->PostSynchronous([&]() { auto root_element = display_window->root_element(); auto window = new DirtyDiscWindow(&fence); window->Show(root_element); }); fence.Wait(); // This is death, and should never return. // TODO(benvanik): cleaner exit. exit(1); } } // namespace kernel } // namespace xe void xe::kernel::xam::RegisterUIExports( xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) { SHIM_SET_MAPPING("xam.xex", XamIsUIActive, state); SHIM_SET_MAPPING("xam.xex", XamShowMessageBoxUI, state); SHIM_SET_MAPPING("xam.xex", XamShowDirtyDiscErrorUI, state); }