diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index 3ebcae453..a1450ed99 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -26,6 +26,8 @@ DEFINE_bool(storage_selection_dialog, false, "Show storage device selection dialog when the game requests it.", "UI"); +DECLARE_int32(license_mask); + namespace xe { namespace kernel { namespace xam { @@ -208,6 +210,49 @@ X_RESULT xeXamDispatchHeadlessEx( } } +template +X_RESULT xeXamDispatchDialogAsync(T* dialog, + std::function close_callback) { + // Broadcast XN_SYS_UI = true + kernel_state()->BroadcastNotification(0x9, true); + ++xam_dialogs_shown_; + + // Important to pass captured vars by value here since we return from this + // without waiting for the dialog to close so the original local vars will be + // destroyed. + // FIXME: Probably not the best idea to call Sleep in UI thread. + dialog->set_close_callback([dialog, close_callback]() { + close_callback(dialog); + + --xam_dialogs_shown_; + + xe::threading::Sleep(std::chrono::milliseconds(100)); + // Broadcast XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, false); + }); + + return X_ERROR_SUCCESS; +} + +X_RESULT xeXamDispatchHeadlessAsync(std::function run_callback) { + // Broadcast XN_SYS_UI = true + kernel_state()->BroadcastNotification(0x9, true); + ++xam_dialogs_shown_; + + auto display_window = kernel_state()->emulator()->display_window(); + display_window->app_context().CallInUIThread([run_callback]() { + run_callback(); + + --xam_dialogs_shown_; + + xe::threading::Sleep(std::chrono::milliseconds(100)); + // Broadcast XN_SYS_UI = false + kernel_state()->BroadcastNotification(0x9, false); + }); + + return X_ERROR_SUCCESS; +} + dword_result_t XamIsUIActive_entry() { return xeXamIsUIActive(); } DECLARE_XAM_EXPORT2(XamIsUIActive, kUI, kImplemented, kHighFrequency); @@ -620,6 +665,83 @@ dword_result_t XamGetDashContext_entry(const ppc_context_t& ctx) { DECLARE_XAM_EXPORT1(XamGetDashContext, kNone, kImplemented); +dword_result_t XamShowMarketplaceUI_entry(dword_t user_index, dword_t ui_type, + qword_t offer_id, dword_t unk_dword) { + // ui_type: + // 0 - view all content for the current title + // 1 - view content specified by offer id + // unk_dword: + // always -1? check more games + if (user_index >= 4) { + return X_ERROR_INVALID_PARAMETER; + } + + if (!kernel_state()->IsUserSignedIn(user_index)) { + return X_ERROR_NO_SUCH_USER; + } + + if (cvars::headless) { + return xeXamDispatchHeadlessAsync([]() {}); + } + + auto close = [ui_type](MessageBoxDialog* dialog) -> void { + if (ui_type == 1) { + uint32_t button = dialog->chosen_button(); + if (button == 0) { + cvars::license_mask = 1; + + // XN_LIVE_CONTENT_INSTALLED + kernel_state()->BroadcastNotification(0x2000007, 0); + } + } + }; + + std::string title = "Xbox Marketplace"; + std::string desc = ""; + cxxopts::OptionNames buttons; + + switch (ui_type) { + case 0: + desc = + "Game requested to open marketplace page with all content for the " + "current title ID."; + break; + case 1: + desc = fmt::format( + "Game requested to open marketplace page for offer ID 0x{:016X}.", + offer_id); + break; + default: + desc = fmt::format("Unknown marketplace op {:d}", ui_type); + break; + } + + desc += + "\nNote that since Xenia cannot access Xbox Marketplace, any DLC must be " + "installed manually using File -> Install Content."; + + switch (ui_type) { + case 0: + default: + buttons.push_back("OK"); + break; + case 1: + desc += + "\n\nTo start trial games in full mode, set license_mask to 1 in " + "Xenia config file.\n\nDo you wish to change license_mask to 1 for " + "*this session*?"; + buttons.push_back("Yes"); + buttons.push_back("No"); + break; + } + + const Emulator* emulator = kernel_state()->emulator(); + ui::ImGuiDrawer* imgui_drawer = emulator->imgui_drawer(); + return xeXamDispatchDialogAsync( + new MessageBoxDialog(imgui_drawer, title, desc, buttons, 0), close); +} +DECLARE_XAM_EXPORT1(XamShowMarketplaceUI, kUI, kSketchy); + } // namespace xam } // namespace kernel } // namespace xe