diff --git a/build/Xenia.Cpp.x64.Common.props b/build/Xenia.Cpp.x64.Common.props
index ac53c1d86..4c7ccc788 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;comctl32.lib;%(AdditionalDependencies)
+ ntdll.lib;wsock32.lib;ws2_32.lib;xinput.lib;xaudio2.lib;glu32.lib;opengl32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies)
/ignore:4006 /ignore:4221 %(AdditionalOptions)
MSVCRTD
diff --git a/libxenia.vcxproj b/libxenia.vcxproj
index adf121ff8..b3dce1255 100644
--- a/libxenia.vcxproj
+++ b/libxenia.vcxproj
@@ -203,6 +203,7 @@
+
@@ -459,11 +460,13 @@
+
+
diff --git a/libxenia.vcxproj.filters b/libxenia.vcxproj.filters
index 165bf7e35..9ff5950a3 100644
--- a/libxenia.vcxproj.filters
+++ b/libxenia.vcxproj.filters
@@ -778,6 +778,9 @@
src\xenia\base
+
+ src\xenia\ui\win32
+
@@ -1506,6 +1509,12 @@
src\xenia\apu
+
+ src\xenia\ui
+
+
+ src\xenia\ui\win32
+
diff --git a/src/xenia/ui/file_picker.h b/src/xenia/ui/file_picker.h
new file mode 100644
index 000000000..1cd7553bb
--- /dev/null
+++ b/src/xenia/ui/file_picker.h
@@ -0,0 +1,79 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2015 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_UI_FILE_PICKER_H_
+#define XENIA_UI_FILE_PICKER_H_
+
+#include
+#include
+
+namespace xe {
+namespace ui {
+
+class FilePicker {
+ public:
+ enum class Mode {
+ kOpen = 0,
+ kSave = 1,
+ };
+ enum class Type {
+ kFile = 0,
+ kDirectory = 1,
+ };
+
+ FilePicker()
+ : mode_(Mode::kOpen),
+ type_(Type::kFile),
+ title_(L"Select Files"),
+ multi_selection_(false) {}
+ virtual ~FilePicker() = default;
+
+ Mode mode() const { return mode_; }
+ void set_mode(Mode mode) { mode_ = mode; }
+
+ Type type() const { return type_; }
+ void set_type(Type type) { type_ = type; }
+
+ std::wstring title() const { return title_; }
+ void set_title(std::wstring title) { title_ = std::move(title); }
+
+ std::vector> extensions() const {
+ return extensions_;
+ }
+ void set_extensions(
+ std::vector> extensions) {
+ extensions_ = std::move(extensions);
+ }
+
+ bool multi_selection() const { return multi_selection_; }
+ void set_multi_selection(bool multi_selection) {
+ multi_selection_ = multi_selection;
+ }
+
+ std::vector selected_files() const { return selected_files_; }
+ void set_selected_files(std::vector selected_files) {
+ selected_files_ = std::move(selected_files);
+ }
+
+ virtual bool Show(void* parent_window_handle = nullptr) = 0;
+
+ private:
+ Mode mode_;
+ Type type_;
+ std::wstring title_;
+ std::vector> extensions_;
+ bool multi_selection_;
+
+ std::vector selected_files_;
+};
+
+} // namespace ui
+} // namespace xe
+
+#endif // XENIA_UI_FILE_PICKER_H_
diff --git a/src/xenia/ui/main_window.h b/src/xenia/ui/main_window.h
index c4a4c0537..2c75e4953 100644
--- a/src/xenia/ui/main_window.h
+++ b/src/xenia/ui/main_window.h
@@ -14,6 +14,7 @@
#include "xenia/xbox.h"
// TODO(benvanik): only on windows.
+#include "xenia/ui/win32/win32_file_picker.h"
#include "xenia/ui/win32/win32_loop.h"
#include "xenia/ui/win32/win32_menu_item.h"
#include "xenia/ui/win32/win32_window.h"
@@ -28,6 +29,7 @@ namespace ui {
using PlatformLoop = xe::ui::win32::Win32Loop;
using PlatformWindow = xe::ui::win32::Win32Window;
using PlatformMenu = xe::ui::win32::Win32MenuItem;
+using PlatformFilePicker = xe::ui::win32::Win32FilePicker;
class MainWindow : public PlatformWindow {
public:
diff --git a/src/xenia/ui/win32/win32_file_picker.cc b/src/xenia/ui/win32/win32_file_picker.cc
new file mode 100644
index 000000000..3f84977f0
--- /dev/null
+++ b/src/xenia/ui/win32/win32_file_picker.cc
@@ -0,0 +1,210 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2015 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#include "xenia/ui/win32/win32_file_picker.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "xenia/base/assert.h"
+
+namespace xe {
+namespace ui {
+namespace win32 {
+
+class CDialogEventHandler : public IFileDialogEvents,
+ public IFileDialogControlEvents {
+ public:
+ // IUnknown methods
+ IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ static const QITAB qit[] = {
+ {&__uuidof(IFileDialogEvents),
+ (int)OFFSETOFCLASS(IFileDialogEvents, CDialogEventHandler)},
+ {&__uuidof(IFileDialogControlEvents),
+ (int)OFFSETOFCLASS(IFileDialogControlEvents, CDialogEventHandler)},
+ {0},
+ };
+ return QISearch(this, qit, riid, ppv);
+ }
+
+ IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); }
+
+ IFACEMETHODIMP_(ULONG) Release() {
+ long cRef = InterlockedDecrement(&_cRef);
+ if (!cRef) delete this;
+ return cRef;
+ }
+
+ // IFileDialogEvents methods
+ IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; };
+ IFACEMETHODIMP OnFolderChange(IFileDialog*) { return S_OK; };
+ IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; };
+ IFACEMETHODIMP OnHelp(IFileDialog*) { return S_OK; };
+ IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; };
+ IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*,
+ FDE_SHAREVIOLATION_RESPONSE*) {
+ return S_OK;
+ };
+ IFACEMETHODIMP OnTypeChange(IFileDialog* pfd) { return S_OK; };
+ IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*,
+ FDE_OVERWRITE_RESPONSE*) {
+ return S_OK;
+ };
+
+ // IFileDialogControlEvents methods
+ IFACEMETHODIMP OnItemSelected(IFileDialogCustomize* pfdc, DWORD dwIDCtl,
+ DWORD dwIDItem) {
+ return S_OK;
+ };
+ IFACEMETHODIMP OnButtonClicked(IFileDialogCustomize*, DWORD) { return S_OK; };
+ IFACEMETHODIMP OnCheckButtonToggled(IFileDialogCustomize*, DWORD, BOOL) {
+ return S_OK;
+ };
+ IFACEMETHODIMP OnControlActivating(IFileDialogCustomize*, DWORD) {
+ return S_OK;
+ };
+
+ CDialogEventHandler() : _cRef(1){};
+
+ private:
+ ~CDialogEventHandler(){};
+ long _cRef;
+};
+
+HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void** ppv) {
+ *ppv = NULL;
+ auto dialog_event_handler = new (std::nothrow) CDialogEventHandler();
+ HRESULT hr = dialog_event_handler ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr)) {
+ hr = dialog_event_handler->QueryInterface(riid, ppv);
+ dialog_event_handler->Release();
+ }
+ return hr;
+}
+
+Win32FilePicker::Win32FilePicker() = default;
+
+Win32FilePicker::~Win32FilePicker() = default;
+
+template
+struct com_ptr {
+ com_ptr() : value(nullptr) {}
+ ~com_ptr() { reset(); }
+ void reset() {
+ if (value) {
+ value->Release();
+ value = nullptr;
+ }
+ }
+ operator T*() { return value; }
+ T* operator->() const { return value; }
+ T** addressof() { return &value; }
+ T* value;
+};
+
+bool Win32FilePicker::Show(void* parent_window_handle) {
+ // TODO(benvanik): FileSaveDialog.
+ assert_true(mode() == Mode::kOpen);
+ // TODO(benvanik): folder dialogs.
+ assert_true(type() == Type::kFile);
+
+ com_ptr file_dialog;
+ HRESULT hr =
+ CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(file_dialog.addressof()));
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ hr = file_dialog->SetTitle(title().c_str());
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ DWORD flags;
+ hr = file_dialog->GetOptions(&flags);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+ // FOS_PICKFOLDERS
+ // FOS_FILEMUSTEXIST
+ // FOS_PATHMUSTEXIST
+ flags |= FOS_FORCEFILESYSTEM;
+ if (multi_selection()) {
+ flags |= FOS_ALLOWMULTISELECT;
+ }
+ hr = file_dialog->SetOptions(flags);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ // Set the file types to display only. Notice that this is a 1-based array.
+ std::vector save_types;
+ for (auto& extension : extensions()) {
+ save_types.push_back({extension.first.c_str(), extension.second.c_str()});
+ }
+ hr = file_dialog->SetFileTypes(static_cast(save_types.size()),
+ save_types.data());
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ hr = file_dialog->SetFileTypeIndex(1);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ // Create an event handling object, and hook it up to the dialog.
+ com_ptr file_dialog_events;
+ hr = CDialogEventHandler_CreateInstance(
+ IID_PPV_ARGS(file_dialog_events.addressof()));
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+ DWORD cookie;
+ hr = file_dialog->Advise(file_dialog_events, &cookie);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ // Show the dialog modally.
+ hr = file_dialog->Show(static_cast(parent_window_handle));
+ file_dialog->Unadvise(cookie);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ // Obtain the result once the user clicks the 'Open' button.
+ // The result is an IShellItem object.
+ com_ptr shell_item;
+ hr = file_dialog->GetResult(shell_item.addressof());
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+
+ // We are just going to print out the name of the file for sample sake.
+ PWSTR file_path = nullptr;
+ hr = shell_item->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
+ if (!SUCCEEDED(hr)) {
+ return false;
+ }
+ std::vector selected_files;
+ selected_files.push_back(std::wstring(file_path));
+ set_selected_files(selected_files);
+ CoTaskMemFree(file_path);
+
+ return true;
+}
+
+} // namespace win32
+} // namespace ui
+} // namespace xe
diff --git a/src/xenia/ui/win32/win32_file_picker.h b/src/xenia/ui/win32/win32_file_picker.h
new file mode 100644
index 000000000..ade3c2ba2
--- /dev/null
+++ b/src/xenia/ui/win32/win32_file_picker.h
@@ -0,0 +1,33 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2015 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
+#define XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
+
+#include "xenia/ui/file_picker.h"
+
+namespace xe {
+namespace ui {
+namespace win32 {
+
+class Win32FilePicker : public FilePicker {
+ public:
+ Win32FilePicker();
+ ~Win32FilePicker() override;
+
+ bool Show(void* parent_window_handle) override;
+
+ private:
+};
+
+} // namespace win32
+} // namespace ui
+} // namespace xe
+
+#endif // XENIA_UI_WIN32_WIN32_FILE_PICKER_H_
diff --git a/src/xenia/xenia_main.cc b/src/xenia/xenia_main.cc
index a3f09392a..621b94e3a 100644
--- a/src/xenia/xenia_main.cc
+++ b/src/xenia/xenia_main.cc
@@ -14,6 +14,7 @@
#include "xenia/emulator.h"
#include "xenia/kernel/kernel.h"
#include "xenia/profiling.h"
+#include "xenia/ui/file_picker.h"
#include "xenia/ui/main_window.h"
DEFINE_string(target, "", "Specifies the target .xex or .iso to execute.");
@@ -33,8 +34,8 @@ int xenia_main(std::vector& args) {
}
// Grab path from the flag or unnamed argument.
+ std::wstring path;
if (!FLAGS_target.empty() || args.size() >= 2) {
- std::wstring path;
if (!FLAGS_target.empty()) {
// Passed as a named argument.
// TODO(benvanik): find something better than gflags that supports
@@ -44,6 +45,31 @@ int xenia_main(std::vector& args) {
// Passed as an unnamed argument.
path = args[1];
}
+ }
+
+ // If no path passed, ask the user.
+ if (path.empty()) {
+ ui::PlatformFilePicker file_picker;
+ file_picker.set_mode(ui::FilePicker::Mode::kOpen);
+ file_picker.set_type(ui::FilePicker::Type::kFile);
+ file_picker.set_multi_selection(false);
+ file_picker.set_title(L"Select Content Package");
+ file_picker.set_extensions({
+ {L"Supported Files", L"*.iso;*.xex;*.xcp;*.*"},
+ {L"Disc Image (*.iso)", L"*.iso"},
+ {L"Xbox Executable (*.xex)", L"*.xex"},
+ //{ L"Content Package (*.xcp)", L"*.xcp" },
+ {L"All Files (*.*)", L"*.*"},
+ });
+ if (file_picker.Show(emulator->main_window()->hwnd())) {
+ auto selected_files = file_picker.selected_files();
+ if (!selected_files.empty()) {
+ path = selected_files[0];
+ }
+ }
+ }
+
+ if (!path.empty()) {
// Normalize the path and make absolute.
std::wstring abs_path = xe::to_absolute_path(path);