diff --git a/build/Xenia.Cpp.x64.Common.props b/build/Xenia.Cpp.x64.Common.props
index 922f3cfc5..dc716d5a0 100644
--- a/build/Xenia.Cpp.x64.Common.props
+++ b/build/Xenia.Cpp.x64.Common.props
@@ -18,7 +18,7 @@
$(SolutionDir)build\bin\$(Configuration)\
- ntdll.lib;wsock32.lib;ws2_32.lib;xinput.lib;xaudio2.lib;glu32.lib;opengl32.lib;%(AdditionalDependencies)
+ ntdll.lib;wsock32.lib;ws2_32.lib;xinput.lib;xaudio2.lib;glu32.lib;opengl32.lib;comctl32.lib;%(AdditionalDependencies)
$(SolutionDir)build\bin\$(Configuration)\
diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc
index 453eb2dcb..a49cd0f5e 100644
--- a/src/xenia/kernel/kernel_state.cc
+++ b/src/xenia/kernel/kernel_state.cc
@@ -27,6 +27,8 @@
#include "xenia/kernel/objects/xthread.h"
#include "xenia/kernel/objects/xuser_module.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.");
diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h
index 18bcdc3e5..f718b182f 100644
--- a/src/xenia/kernel/kernel_state.h
+++ b/src/xenia/kernel/kernel_state.h
@@ -10,6 +10,8 @@
#ifndef XENIA_KERNEL_KERNEL_STATE_H_
#define XENIA_KERNEL_KERNEL_STATE_H_
+#include
+
#include
#include
@@ -29,6 +31,8 @@ class Processor;
} // namespace cpu
} // namespace xe
+DECLARE_bool(headless);
+
namespace xe {
namespace kernel {
diff --git a/src/xenia/kernel/xam_ui.cc b/src/xenia/kernel/xam_ui.cc
index ebd24b513..bab247f4b 100644
--- a/src/xenia/kernel/xam_ui.cc
+++ b/src/xenia/kernel/xam_ui.cc
@@ -8,11 +8,14 @@
*/
#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/xbox.h"
+#include
+
namespace xe {
namespace kernel {
@@ -51,8 +54,55 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_state, KernelState* state) {
button_count, button_ptrs, all_buttons.c_str(), active_button, flags,
result_ptr, overlapped_ptr);
- // Auto-pick the focused button.
- SHIM_SET_MEM_32(result_ptr, active_button);
+ uint32_t chosen_button;
+ if (FLAGS_headless) {
+ // Auto-pick the focused button.
+ chosen_button = active_button;
+ } else {
+ TASKDIALOGCONFIG config = {0};
+ config.cbSize = sizeof(config);
+ config.hInstance = GetModuleHandle(nullptr);
+ config.hwndParent = state->emulator()->main_window()->hwnd();
+ config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | // esc to exit
+ TDF_POSITION_RELATIVE_TO_WINDOW; // center in window
+ config.dwCommonButtons = 0;
+ config.pszWindowTitle = title.c_str();
+ 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;
+ }
+ config.pszMainInstruction = text.c_str();
+ config.pszContent = nullptr;
+ std::vector taskdialog_buttons;
+ for (uint32_t i = 0; i < button_count; ++i) {
+ taskdialog_buttons.push_back({1000 + int(i), buttons[i].c_str()});
+ }
+ config.pButtons = taskdialog_buttons.data();
+ config.cButtons = button_count;
+ config.nDefaultButton = active_button;
+ int button_pressed = 0;
+ TaskDialogIndirect(&config, &button_pressed, nullptr, nullptr);
+ switch (button_pressed) {
+ default:
+ chosen_button = button_pressed - 1000;
+ break;
+ case IDCANCEL:
+ // User cancelled, just pick default.
+ chosen_button = active_button;
+ break;
+ }
+ }
+ SHIM_SET_MEM_32(result_ptr, chosen_button);
state->CompleteOverlappedImmediate(overlapped_ptr, X_ERROR_SUCCESS);
SHIM_SET_RETURN_32(X_ERROR_IO_PENDING);