From bba12520c432a5b5975a76f014ed3c80e3411bb6 Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Mon, 31 Aug 2015 18:28:37 -0700
Subject: [PATCH 1/7] Expose loader helper functions for identifying files.

---
 src/core/loader/loader.cpp | 26 +++++++++++++-------------
 src/core/loader/loader.h   | 28 ++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 74eb6e871b..b1902dccac 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -26,12 +26,7 @@ const std::initializer_list<Kernel::AddressMapping> default_address_mappings = {
     { 0x1F000000, 0x600000, false }, // entire VRAM
 };
 
-/**
- * Identifies the type of a bootable file
- * @param file open file
- * @return FileType of file
- */
-static FileType IdentifyFile(FileUtil::IOFile& file) {
+FileType IdentifyFile(FileUtil::IOFile& file) {
     FileType type;
 
 #define CHECK_TYPE(loader) \
@@ -48,12 +43,17 @@ static FileType IdentifyFile(FileUtil::IOFile& file) {
     return FileType::Unknown;
 }
 
-/**
- * Guess the type of a bootable file from its extension
- * @param extension_ String extension of bootable file
- * @return FileType of file
- */
-static FileType GuessFromExtension(const std::string& extension_) {
+FileType IdentifyFile(const std::string& file_name) {
+    FileUtil::IOFile file(file_name, "rb");
+    if (!file.IsOpen()) {
+        LOG_ERROR(Loader, "Failed to load file %s", file_name.c_str());
+        return FileType::Unknown;
+    }
+
+    return IdentifyFile(file);
+}
+
+FileType GuessFromExtension(const std::string& extension_) {
     std::string extension = Common::ToLower(extension_);
 
     if (extension == ".elf" || extension == ".axf")
@@ -71,7 +71,7 @@ static FileType GuessFromExtension(const std::string& extension_) {
     return FileType::Unknown;
 }
 
-static const char* GetFileTypeString(FileType type) {
+const char* GetFileTypeString(FileType type) {
     switch (type) {
     case FileType::CCI:
         return "NCSD";
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index a37d3348ce..8de95dacf7 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -33,6 +33,34 @@ enum class FileType {
     THREEDSX, //3DSX
 };
 
+/**
+ * Identifies the type of a bootable file based on the magic value in its header.
+ * @param file open file
+ * @return FileType of file
+ */
+FileType IdentifyFile(FileUtil::IOFile& file);
+
+/**
+ * Identifies the type of a bootable file based on the magic value in its header.
+ * @param file_name path to file
+ * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
+ * a filetype, and will never return FileType::Error.
+ */
+FileType IdentifyFile(const std::string& file_name);
+
+/**
+ * Guess the type of a bootable file from its extension
+ * @param extension String extension of bootable file
+ * @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
+ * a filetype, and will never return FileType::Error.
+ */
+FileType GuessFromExtension(const std::string& extension_);
+
+/**
+ * Convert a FileType into a string which can be displayed to the user.
+ */
+const char* GetFileTypeString(FileType type);
+
 /// Return type for functions in Loader namespace
 enum class ResultStatus {
     Success,

From 7134a17fc6cb7ab8ae46dae04f005bb72e0af88e Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Mon, 31 Aug 2015 18:29:23 -0700
Subject: [PATCH 2/7] Split up FileUtil::ScanDirectoryTree to be able to use
 callbacks for custom behavior

Converted FileUtil::ScanDirectoryTree and FileUtil::DeleteDirRecursively
to use the new ScanDirectoryTreeAndCallback function internally.
---
 src/common/file_util.cpp | 166 +++++++++++++++------------------------
 src/common/file_util.h   |  26 +++++-
 2 files changed, 86 insertions(+), 106 deletions(-)

diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 836b58d527..2fbb0f260b 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -420,28 +420,23 @@ bool CreateEmptyFile(const std::string &filename)
 }
 
 
-// Scans the directory tree gets, starting from _Directory and adds the
-// results into parentEntry. Returns the number of files+directories found
-u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry)
+int ScanDirectoryTreeAndCallback(const std::string &directory, std::function<int(const std::string&, const std::string&)> callback)
 {
     LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
     // How many files + directories we found
-    u32 foundEntries = 0;
+    int found_entries = 0;
 #ifdef _WIN32
     // Find the first file in the directory.
     WIN32_FIND_DATA ffd;
 
-    HANDLE hFind = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd);
-    if (hFind == INVALID_HANDLE_VALUE)
-    {
-        FindClose(hFind);
-        return foundEntries;
+    HANDLE handle_find = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd);
+    if (handle_find == INVALID_HANDLE_VALUE) {
+        FindClose(handle_find);
+        return found_entries;
     }
     // windows loop
-    do
-    {
-        FSTEntry entry;
-        const std::string virtualName(Common::TStrToUTF8(ffd.cFileName));
+    do {
+        const std::string virtual_name(Common::TStrToUTF8(ffd.cFileName));
 #else
     struct dirent dirent, *result = nullptr;
 
@@ -450,115 +445,80 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry)
         return 0;
 
     // non windows loop
-    while (!readdir_r(dirp, &dirent, &result) && result)
-    {
-        FSTEntry entry;
-        const std::string virtualName(result->d_name);
+    while (!readdir_r(dirp, &dirent, &result) && result) {
+        const std::string virtual_name(result->d_name);
 #endif
         // check for "." and ".."
-        if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
-                ((virtualName[0] == '.') && (virtualName[1] == '.') &&
-                 (virtualName[2] == '\0')))
+        if (((virtual_name[0] == '.') && (virtual_name[1] == '\0')) ||
+                ((virtual_name[0] == '.') && (virtual_name[1] == '.') &&
+                 (virtual_name[2] == '\0')))
             continue;
-        entry.virtualName = virtualName;
-        entry.physicalName = directory;
-        entry.physicalName += DIR_SEP + entry.virtualName;
 
-        if (IsDirectory(entry.physicalName.c_str()))
-        {
-            entry.isDirectory = true;
-            // is a directory, lets go inside
-            entry.size = ScanDirectoryTree(entry.physicalName, entry);
-            foundEntries += (u32)entry.size;
+        int ret = callback(directory, virtual_name);
+        if (ret < 0) {
+            if (ret != -1)
+                found_entries = ret;
+            break;
         }
-        else
-        { // is a file
-            entry.isDirectory = false;
-            entry.size = GetSize(entry.physicalName.c_str());
-        }
-        ++foundEntries;
-        // Push into the tree
-        parentEntry.children.push_back(entry);
+        found_entries += ret;
+
 #ifdef _WIN32
-    } while (FindNextFile(hFind, &ffd) != 0);
-    FindClose(hFind);
+    } while (FindNextFile(handle_find, &ffd) != 0);
+    FindClose(handle_find);
 #else
     }
     closedir(dirp);
 #endif
     // Return number of entries found.
-    return foundEntries;
+    return found_entries;
+}
+
+int ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
+{
+    const auto callback = [&parent_entry](const std::string& directory,
+                                          const std::string& virtual_name) -> int {
+        FSTEntry entry;
+        int found_entries = 0;
+        entry.virtualName = virtual_name;
+        entry.physicalName = directory + DIR_SEP + virtual_name;
+
+        if (IsDirectory(entry.physicalName)) {
+            entry.isDirectory = true;
+            // is a directory, lets go inside
+            entry.size = ScanDirectoryTree(entry.physicalName, entry);
+            found_entries += (int)entry.size;
+        } else { // is a file
+            entry.isDirectory = false;
+            entry.size = GetSize(entry.physicalName);
+        }
+        ++found_entries;
+        // Push into the tree
+        parent_entry.children.push_back(entry);
+        return found_entries;
+    };
+
+    return ScanDirectoryTreeAndCallback(directory, callback);
 }
 
 
-// Deletes the given directory and anything under it. Returns true on success.
 bool DeleteDirRecursively(const std::string &directory)
 {
-    LOG_TRACE(Common_Filesystem, "%s", directory.c_str());
-#ifdef _WIN32
-    // Find the first file in the directory.
-    WIN32_FIND_DATA ffd;
-    HANDLE hFind = FindFirstFile(Common::UTF8ToTStr(directory + "\\*").c_str(), &ffd);
+    const static auto callback = [](const std::string& directory,
+                                    const std::string& virtual_name) -> int {
+        std::string new_path = directory + DIR_SEP_CHR + virtual_name;
+        if (IsDirectory(new_path)) {
+            if (!DeleteDirRecursively(new_path)) {
+                return -2;
+            }
+        } else if (!Delete(new_path)) {
+            return -2;
+        }
+        return 0;
+    };
 
-    if (hFind == INVALID_HANDLE_VALUE)
-    {
-        FindClose(hFind);
+    if (ScanDirectoryTreeAndCallback(directory, callback) == -2) {
         return false;
     }
-
-    // windows loop
-    do
-    {
-        const std::string virtualName(Common::TStrToUTF8(ffd.cFileName));
-#else
-    struct dirent dirent, *result = nullptr;
-    DIR *dirp = opendir(directory.c_str());
-    if (!dirp)
-        return false;
-
-    // non windows loop
-    while (!readdir_r(dirp, &dirent, &result) && result)
-    {
-        const std::string virtualName = result->d_name;
-#endif
-
-        // check for "." and ".."
-        if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
-            ((virtualName[0] == '.') && (virtualName[1] == '.') &&
-             (virtualName[2] == '\0')))
-            continue;
-
-        std::string newPath = directory + DIR_SEP_CHR + virtualName;
-        if (IsDirectory(newPath))
-        {
-            if (!DeleteDirRecursively(newPath))
-            {
-                #ifndef _WIN32
-                closedir(dirp);
-                #endif
-
-                return false;
-            }
-        }
-        else
-        {
-            if (!FileUtil::Delete(newPath))
-            {
-                #ifndef _WIN32
-                closedir(dirp);
-                #endif
-
-                return false;
-            }
-        }
-
-#ifdef _WIN32
-    } while (FindNextFile(hFind, &ffd) != 0);
-    FindClose(hFind);
-#else
-    }
-    closedir(dirp);
-#endif
     FileUtil::DeleteDir(directory);
 
     return true;
diff --git a/src/common/file_util.h b/src/common/file_util.h
index e71a9b2fa2..3d617f5730 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -6,6 +6,7 @@
 
 #include <array>
 #include <fstream>
+#include <functional>
 #include <cstddef>
 #include <cstdio>
 #include <string>
@@ -96,9 +97,28 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename);
 // creates an empty file filename, returns true on success
 bool CreateEmptyFile(const std::string &filename);
 
-// Scans the directory tree gets, starting from _Directory and adds the
-// results into parentEntry. Returns the number of files+directories found
-u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry);
+/**
+ * Scans the directory tree, calling the callback for each file/directory found.
+ * The callback must return the number of files and directories which the provided path contains.
+ * If the callback's return value is -1, the callback loop is broken immediately.
+ * If the callback's return value is otherwise negative, the callback loop is broken immediately
+ * and the callback's return value is returned from this function (to allow for error handling).
+ * @param directory the parent directory to start scanning from
+ * @param callback The callback which will be called for each file/directory. It is called
+ *     with the arguments (const std::string& directory, const std::string& virtual_name).
+ *     The `directory `parameter is the path to the directory which contains the file/directory.
+ *     The `virtual_name` parameter is the incomplete file path, without any directory info.
+ * @return the total number of files/directories found
+ */
+int ScanDirectoryTreeAndCallback(const std::string &directory, std::function<int(const std::string&, const std::string&)> callback);
+
+/**
+ * Scans the directory tree, storing the results.
+ * @param directory the parent directory to start scanning from
+ * @param parent_entry FSTEntry where the filesystem tree results will be stored.
+ * @return the total number of files/directories found
+ */
+int ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry);
 
 // deletes the given directory and anything under it. Returns true on success.
 bool DeleteDirRecursively(const std::string &directory);

From afd06675fa2b93c81f0f868443c03cc3ad8bee07 Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Mon, 31 Aug 2015 18:30:06 -0700
Subject: [PATCH 3/7] Don't show render window until a game is started

---
 src/citra_qt/main.cpp | 15 +++++++++++----
 src/citra_qt/main.h   |  2 ++
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 01841b33c2..58de28c1dc 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -266,6 +266,7 @@ void GMainWindow::BootGame(const std::string& filename) {
     callstackWidget->OnDebugModeEntered();
     render_window->show();
 
+    emulation_running = true;
     OnStartGame();
 }
 
@@ -294,6 +295,8 @@ void GMainWindow::ShutdownGame() {
     ui.action_Pause->setEnabled(false);
     ui.action_Stop->setEnabled(false);
     render_window->hide();
+
+    emulation_running = false;
 }
 
 void GMainWindow::StoreRecentFile(const QString& filename)
@@ -423,17 +426,21 @@ void GMainWindow::ToggleWindowMode() {
         // Render in the main window...
         render_window->BackupGeometry();
         ui.horizontalLayout->addWidget(render_window);
-        render_window->setVisible(true);
         render_window->setFocusPolicy(Qt::ClickFocus);
-        render_window->setFocus();
+        if (emulation_running) {
+            render_window->setVisible(true);
+            render_window->setFocus();
+        }
 
     } else {
         // Render in a separate window...
         ui.horizontalLayout->removeWidget(render_window);
         render_window->setParent(nullptr);
-        render_window->setVisible(true);
-        render_window->RestoreGeometry();
         render_window->setFocusPolicy(Qt::NoFocus);
+        if (emulation_running) {
+            render_window->setVisible(true);
+            render_window->RestoreGeometry();
+        }
     }
 }
 
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 32523fded6..e99501cabe 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -102,6 +102,8 @@ private:
 
     GRenderWindow* render_window;
 
+    // Whether emulation is currently running in Citra.
+    bool emulation_running = false;
     std::unique_ptr<EmuThread> emu_thread;
 
     ProfilerWidget* profilerWidget;

From f297a59985a56423da1654cf5bb3448484963ab4 Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Mon, 31 Aug 2015 21:35:33 -0700
Subject: [PATCH 4/7] Add helper function for creating a readable byte size
 string.

---
 src/citra_qt/util/util.cpp | 12 ++++++++++++
 src/citra_qt/util/util.h   |  4 ++++
 2 files changed, 16 insertions(+)

diff --git a/src/citra_qt/util/util.cpp b/src/citra_qt/util/util.cpp
index f292046b75..8734a8efdc 100644
--- a/src/citra_qt/util/util.cpp
+++ b/src/citra_qt/util/util.cpp
@@ -2,6 +2,9 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <array>
+#include <cmath>
+
 #include "citra_qt/util/util.h"
 
 QFont GetMonospaceFont() {
@@ -11,3 +14,12 @@ QFont GetMonospaceFont() {
     font.setFixedPitch(true);
     return font;
 }
+
+QString ReadableByteSize(qulonglong size) {
+    static const std::array<const char*, 6> units = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" };
+    if (size == 0)
+        return "0";
+    int digit_groups = std::min<int>((int)(std::log10(size) / std::log10(1024)), units.size());
+    return QString("%L1 %2").arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
+                            .arg(units[digit_groups]);
+}
diff --git a/src/citra_qt/util/util.h b/src/citra_qt/util/util.h
index 98a9440477..ab443ef9b5 100644
--- a/src/citra_qt/util/util.h
+++ b/src/citra_qt/util/util.h
@@ -5,6 +5,10 @@
 #pragma once
 
 #include <QFont>
+#include <QString>
 
 /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
 QFont GetMonospaceFont();
+
+/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
+QString ReadableByteSize(qulonglong size);

From 6e1bb58ee8ace739615e42cff02f07ee396edb6e Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Mon, 31 Aug 2015 21:35:33 -0700
Subject: [PATCH 5/7] Initial implementation of a game list

---
 src/citra_qt/CMakeLists.txt |   2 +
 src/citra_qt/game_list.cpp  | 154 ++++++++++++++++++++++++++++++++++++
 src/citra_qt/game_list.h    |  48 +++++++++++
 src/citra_qt/game_list_p.h  | 130 ++++++++++++++++++++++++++++++
 src/citra_qt/main.cpp       |  20 ++++-
 src/citra_qt/main.h         |   4 +
 6 files changed, 356 insertions(+), 2 deletions(-)
 create mode 100644 src/citra_qt/game_list.cpp
 create mode 100644 src/citra_qt/game_list.h
 create mode 100644 src/citra_qt/game_list_p.h

diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index a82e8a85bb..51a574629b 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SRCS
             debugger/profiler.cpp
             debugger/ramview.cpp
             debugger/registers.cpp
+            game_list.cpp
             util/spinbox.cpp
             util/util.cpp
             bootmanager.cpp
@@ -42,6 +43,7 @@ set(HEADERS
             debugger/profiler.h
             debugger/ramview.h
             debugger/registers.h
+            game_list.h
             util/spinbox.h
             util/util.h
             bootmanager.h
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
new file mode 100644
index 0000000000..f90e053747
--- /dev/null
+++ b/src/citra_qt/game_list.cpp
@@ -0,0 +1,154 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QHeaderView>
+#include <QThreadPool>
+#include <QVBoxLayout>
+
+#include "game_list.h"
+#include "game_list_p.h"
+
+#include "core/loader/loader.h"
+
+#include "common/common_paths.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+
+GameList::GameList(QWidget* parent)
+{
+    QVBoxLayout* layout = new QVBoxLayout;
+
+    tree_view = new QTreeView;
+    item_model = new QStandardItemModel(tree_view);
+    tree_view->setModel(item_model);
+
+    tree_view->setAlternatingRowColors(true);
+    tree_view->setSelectionMode(QHeaderView::SingleSelection);
+    tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+    tree_view->setSortingEnabled(true);
+    tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+    tree_view->setUniformRowHeights(true);
+
+    item_model->insertColumns(0, COLUMN_COUNT);
+    item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
+    item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
+    item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
+
+    connect(tree_view, SIGNAL(activated(const QModelIndex&)), this, SLOT(ValidateEntry(const QModelIndex&)));
+
+    // We must register all custom types with the Qt Automoc system so that we are able to use it with
+    // signals/slots. In this case, QList falls under the umbrells of custom types.
+    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+    layout->addWidget(tree_view);
+    setLayout(layout);
+}
+
+GameList::~GameList()
+{
+    emit ShouldCancelWorker();
+}
+
+void GameList::AddEntry(QList<QStandardItem*> entry_items)
+{
+    item_model->invisibleRootItem()->appendRow(entry_items);
+}
+
+void GameList::ValidateEntry(const QModelIndex& item)
+{
+    // We don't care about the individual QStandardItem that was selected, but its row.
+    int row = item_model->itemFromIndex(item)->row();
+    QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
+    QString file_path = child_file->data(GameListItemPath::FullPathRole).toString();
+
+    if (file_path.isEmpty())
+        return;
+    std::string std_file_path = file_path.toStdString();
+    if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path))
+        return;
+    emit GameChosen(file_path);
+}
+
+void GameList::DonePopulating()
+{
+    tree_view->setEnabled(true);
+}
+
+void GameList::PopulateAsync(const QString& dir_path, bool deep_scan)
+{
+    if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) {
+        LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLatin1().data());
+        return;
+    }
+
+    tree_view->setEnabled(false);
+    // Delete any rows that might already exist if we're repopulating
+    item_model->removeRows(0, item_model->rowCount());
+
+    emit ShouldCancelWorker();
+    GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
+
+    connect(worker, SIGNAL(EntryReady(QList<QStandardItem*>)), this, SLOT(AddEntry(QList<QStandardItem*>)), Qt::QueuedConnection);
+    connect(worker, SIGNAL(Finished()), this, SLOT(DonePopulating()), Qt::QueuedConnection);
+    // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel without delay.
+    connect(this, SIGNAL(ShouldCancelWorker()), worker, SLOT(Cancel()), Qt::DirectConnection);
+
+    QThreadPool::globalInstance()->start(worker);
+    current_worker = std::move(worker);
+}
+
+void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan)
+{
+    const auto callback = [&](const std::string& directory,
+                                     const std::string& virtual_name) -> int {
+
+        std::string physical_name = directory + DIR_SEP + virtual_name;
+
+        if (stop_processing)
+            return -1; // A negative return value breaks the callback loop.
+
+        if (deep_scan && FileUtil::IsDirectory(physical_name)) {
+            AddFstEntriesToGameList(physical_name, true);
+        } else {
+            std::string filename_filename, filename_extension;
+            Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension);
+
+            Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension);
+            if (guessed_filetype == Loader::FileType::Unknown)
+                return 0;
+            Loader::FileType filetype = Loader::IdentifyFile(physical_name);
+            if (filetype == Loader::FileType::Unknown) {
+                LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str());
+                return 0;
+            }
+            if (guessed_filetype != filetype) {
+                LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str());
+            }
+
+            emit EntryReady({
+                new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))),
+                new GameListItemPath(QString::fromStdString(physical_name)),
+                new GameListItemSize(FileUtil::GetSize(physical_name)),
+            });
+        }
+
+        return 0; // We don't care about the found entries
+    };
+    FileUtil::ScanDirectoryTreeAndCallback(dir_path, callback);
+}
+
+void GameListWorker::run()
+{
+    stop_processing = false;
+    AddFstEntriesToGameList(dir_path.toStdString(), deep_scan);
+    emit Finished();
+}
+
+void GameListWorker::Cancel()
+{
+    disconnect(this, 0, 0, 0);
+    stop_processing = true;
+}
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
new file mode 100644
index 0000000000..ab09edce30
--- /dev/null
+++ b/src/citra_qt/game_list.h
@@ -0,0 +1,48 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QModelIndex>
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include <QString>
+#include <QTreeView>
+#include <QWidget>
+
+class GameListWorker;
+
+
+class GameList : public QWidget {
+    Q_OBJECT
+
+public:
+    enum {
+        COLUMN_FILE_TYPE,
+        COLUMN_NAME,
+        COLUMN_SIZE,
+        COLUMN_COUNT, // Number of columns
+    };
+
+    GameList(QWidget* parent = nullptr);
+    ~GameList() override;
+
+    void PopulateAsync(const QString& dir_path, bool deep_scan);
+
+public slots:
+    void AddEntry(QList<QStandardItem*> entry_items);
+
+private slots:
+    void ValidateEntry(const QModelIndex& item);
+    void DonePopulating();
+
+signals:
+    void GameChosen(QString game_path);
+    void ShouldCancelWorker();
+
+private:
+    QTreeView* tree_view = nullptr;
+    QStandardItemModel* item_model = nullptr;
+    GameListWorker* current_worker = nullptr;
+};
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
new file mode 100644
index 0000000000..820012bce7
--- /dev/null
+++ b/src/citra_qt/game_list_p.h
@@ -0,0 +1,130 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+
+#include <QRunnable>
+#include <QStandardItem>
+#include <QString>
+
+#include "citra_qt/util/util.h"
+#include "common/string_util.h"
+
+
+class GameListItem : public QStandardItem {
+
+public:
+    GameListItem(): QStandardItem() {}
+    GameListItem(const QString& string): QStandardItem(string) {}
+    virtual ~GameListItem() override {}
+};
+
+
+/**
+ * A specialization of GameListItem for path values.
+ * This class ensures that for every full path value it holds, a correct string representation
+ * of just the filename (with no extension) will be displayed to the user.
+ */
+class GameListItemPath : public GameListItem {
+
+public:
+    static const int FullPathRole = Qt::UserRole + 1;
+
+    GameListItemPath(): GameListItem() {}
+    GameListItemPath(const QString& game_path): GameListItem()
+    {
+        setData(game_path, FullPathRole);
+    }
+
+    void setData(const QVariant& value, int role) override
+    {
+        // By specializing setData for FullPathRole, we can ensure that the two string
+        // representations of the data are always accurate and in the correct format.
+        if (role == FullPathRole) {
+            std::string filename;
+            Common::SplitPath(value.toString().toStdString(), nullptr, &filename, nullptr);
+            GameListItem::setData(QString::fromStdString(filename), Qt::DisplayRole);
+            GameListItem::setData(value, FullPathRole);
+        } else {
+            GameListItem::setData(value, role);
+        }
+    }
+};
+
+
+/**
+ * A specialization of GameListItem for size values.
+ * This class ensures that for every numerical size value it holds (in bytes), a correct
+ * human-readable string representation will be displayed to the user.
+ */
+class GameListItemSize : public GameListItem {
+
+public:
+    static const int SizeRole = Qt::UserRole + 1;
+
+    GameListItemSize(): GameListItem() {}
+    GameListItemSize(const qulonglong size_bytes): GameListItem()
+    {
+        setData(size_bytes, SizeRole);
+    }
+
+    void setData(const QVariant& value, int role) override
+    {
+        // By specializing setData for SizeRole, we can ensure that the numerical and string
+        // representations of the data are always accurate and in the correct format.
+        if (role == SizeRole) {
+            qulonglong size_bytes = value.toULongLong();
+            GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole);
+            GameListItem::setData(value, SizeRole);
+        } else {
+            GameListItem::setData(value, role);
+        }
+    }
+
+    /**
+     * This operator is, in practice, only used by the TreeView sorting systems.
+     * Override it so that it will correctly sort by numerical value instead of by string representation.
+     */
+    bool operator<(const QStandardItem& other) const override
+    {
+        return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
+    }
+};
+
+
+/**
+ * Asynchronous worker object for populating the game list.
+ * Communicates with other threads through Qt's signal/slot system.
+ */
+class GameListWorker : public QObject, public QRunnable {
+    Q_OBJECT
+
+public:
+    GameListWorker(QString dir_path, bool deep_scan):
+            QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {}
+
+public slots:
+    /// Starts the processing of directory tree information.
+    void run() override;
+    /// Tells the worker that it should no longer continue processing. Thread-safe.
+    void Cancel();
+
+signals:
+    /**
+     * The `EntryReady` signal is emitted once an entry has been prepared and is ready
+     * to be added to the game list.
+     * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
+     */
+    void EntryReady(QList<QStandardItem*> entry_items);
+    void Finished();
+
+private:
+    QString dir_path;
+    bool deep_scan;
+    std::atomic_bool stop_processing;
+
+    void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan);
+};
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 58de28c1dc..ff2d609962 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -12,6 +12,7 @@
 
 #include "citra_qt/bootmanager.h"
 #include "citra_qt/config.h"
+#include "citra_qt/game_list.h"
 #include "citra_qt/hotkeys.h"
 #include "citra_qt/main.h"
 
@@ -59,6 +60,9 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)
     render_window = new GRenderWindow(this, emu_thread.get());
     render_window->hide();
 
+    game_list = new GameList();
+    ui.horizontalLayout->addWidget(game_list);
+
     profilerWidget = new ProfilerWidget(this);
     addDockWidget(Qt::BottomDockWidgetArea, profilerWidget);
     profilerWidget->hide();
@@ -160,6 +164,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)
     UpdateRecentFiles();
 
     // Setup connections
+    connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)));
     connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()));
     connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap()));
     connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame()));
@@ -193,6 +198,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)
 
     show();
 
+    game_list->PopulateAsync(settings.value("gameListRootDir").toString(), settings.value("gameListDeepScan").toBool());
+
     QStringList args = QApplication::arguments();
     if (args.length() >= 2) {
         BootGame(args[1].toStdString());
@@ -264,6 +271,9 @@ void GMainWindow::BootGame(const std::string& filename) {
     // Update the GUI
     registersWidget->OnDebugModeEntered();
     callstackWidget->OnDebugModeEntered();
+    if (ui.action_Single_Window_Mode->isChecked()) {
+        game_list->hide();
+    }
     render_window->show();
 
     emulation_running = true;
@@ -295,6 +305,7 @@ void GMainWindow::ShutdownGame() {
     ui.action_Pause->setEnabled(false);
     ui.action_Stop->setEnabled(false);
     render_window->hide();
+    game_list->show();
 
     emulation_running = false;
 }
@@ -340,12 +351,16 @@ void GMainWindow::UpdateRecentFiles() {
     }
 }
 
+void GMainWindow::OnGameListLoadFile(QString game_path) {
+    BootGame(game_path.toLatin1().data());
+}
+
 void GMainWindow::OnMenuLoadFile() {
     QSettings settings;
     QString rom_path = settings.value("romsPath", QString()).toString();
 
     QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), rom_path, tr("3DS executable (*.3ds *.3dsx *.elf *.axf *.cci *.cxi)"));
-    if (filename.size()) {
+    if (!filename.isEmpty()) {
         settings.setValue("romsPath", QFileInfo(filename).path());
         StoreRecentFile(filename);
 
@@ -358,7 +373,7 @@ void GMainWindow::OnMenuLoadSymbolMap() {
     QString symbol_path = settings.value("symbolsPath", QString()).toString();
 
     QString filename = QFileDialog::getOpenFileName(this, tr("Load Symbol Map"), symbol_path, tr("Symbol map (*)"));
-    if (filename.size()) {
+    if (!filename.isEmpty()) {
         settings.setValue("symbolsPath", QFileInfo(filename).path());
 
         LoadSymbolMap(filename.toLatin1().data());
@@ -440,6 +455,7 @@ void GMainWindow::ToggleWindowMode() {
         if (emulation_running) {
             render_window->setVisible(true);
             render_window->RestoreGeometry();
+            game_list->show();
         }
     }
 }
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index e99501cabe..48a1032bd1 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -10,6 +10,7 @@
 
 #include "ui_main.h"
 
+class GameList;
 class GImageInfo;
 class GRenderWindow;
 class EmuThread;
@@ -87,6 +88,8 @@ private slots:
     void OnStartGame();
     void OnPauseGame();
     void OnStopGame();
+    /// Called whenever a user selects a game in the game list widget.
+    void OnGameListLoadFile(QString game_path);
     void OnMenuLoadFile();
     void OnMenuLoadSymbolMap();
     void OnMenuRecentFile();
@@ -101,6 +104,7 @@ private:
     Ui::MainWindow ui;
 
     GRenderWindow* render_window;
+    GameList* game_list;
 
     // Whether emulation is currently running in Citra.
     bool emulation_running = false;

From 797b91a449c549995ab4afe786275b02b3e1ab87 Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Sun, 6 Sep 2015 15:33:57 -0700
Subject: [PATCH 6/7] Add menu item for selecting the game list folder

---
 src/citra_qt/main.cpp | 11 +++++++++++
 src/citra_qt/main.h   |  2 ++
 src/citra_qt/main.ui  | 11 ++++++++++-
 3 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index ff2d609962..c5e338c91f 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -167,6 +167,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)
     connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)));
     connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()));
     connect(ui.action_Load_Symbol_Map, SIGNAL(triggered()), this, SLOT(OnMenuLoadSymbolMap()));
+    connect(ui.action_Select_Game_List_Root, SIGNAL(triggered()), this, SLOT(OnMenuSelectGameListRoot()));
     connect(ui.action_Start, SIGNAL(triggered()), this, SLOT(OnStartGame()));
     connect(ui.action_Pause, SIGNAL(triggered()), this, SLOT(OnPauseGame()));
     connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame()));
@@ -380,6 +381,16 @@ void GMainWindow::OnMenuLoadSymbolMap() {
     }
 }
 
+void GMainWindow::OnMenuSelectGameListRoot() {
+    QSettings settings;
+
+    QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
+    if (!dir_path.isEmpty()) {
+        settings.setValue("gameListRootDir", dir_path);
+        game_list->PopulateAsync(dir_path, settings.value("gameListDeepScan").toBool());
+    }
+}
+
 void GMainWindow::OnMenuRecentFile() {
     QAction* action = qobject_cast<QAction*>(sender());
     assert(action);
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 48a1032bd1..6d27ce6a97 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -92,6 +92,8 @@ private slots:
     void OnGameListLoadFile(QString game_path);
     void OnMenuLoadFile();
     void OnMenuLoadSymbolMap();
+    /// Called whenever a user selects the "File->Select Game List Root" menu item
+    void OnMenuSelectGameListRoot();
     void OnMenuRecentFile();
     void OnOpenHotkeysDialog();
     void OnConfigure();
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 1ba700a3a6..997597642b 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -45,7 +45,7 @@
      <x>0</x>
      <y>0</y>
      <width>1081</width>
-     <height>21</height>
+     <height>22</height>
     </rect>
    </property>
    <widget class="QMenu" name="menu_File">
@@ -60,6 +60,7 @@
     <addaction name="action_Load_File"/>
     <addaction name="action_Load_Symbol_Map"/>
     <addaction name="separator"/>
+    <addaction name="action_Select_Game_List_Root"/>
     <addaction name="menu_recent_files"/>
     <addaction name="separator"/>
     <addaction name="action_Exit"/>
@@ -182,6 +183,14 @@
     <string>Display Dock Widget Headers</string>
    </property>
   </action>
+  <action name="action_Select_Game_List_Root">
+   <property name="text">
+    <string>Select Game Directory...</string>
+   </property>
+   <property name="toolTip">
+    <string>Selects a folder to display in the game list</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections>

From 0fae76c741b84cfe6d31a9079b818866778dfa31 Mon Sep 17 00:00:00 2001
From: archshift <gh@archshift.com>
Date: Sun, 6 Sep 2015 23:51:57 -0700
Subject: [PATCH 7/7] Game list: save and load column sizes, sort order, to
 QSettings

---
 src/citra_qt/game_list.cpp | 17 +++++++++++++++++
 src/citra_qt/game_list.h   |  4 ++++
 src/citra_qt/main.cpp      |  3 +++
 3 files changed, 24 insertions(+)

diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index f90e053747..dade3c2124 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -100,6 +100,23 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan)
     current_worker = std::move(worker);
 }
 
+void GameList::SaveInterfaceLayout(QSettings& settings)
+{
+    settings.beginGroup("UILayout");
+    settings.setValue("gameListHeaderState", tree_view->header()->saveState());
+    settings.endGroup();
+}
+
+void GameList::LoadInterfaceLayout(QSettings& settings)
+{
+    auto header = tree_view->header();
+    settings.beginGroup("UILayout");
+    header->restoreState(settings.value("gameListHeaderState").toByteArray());
+    settings.endGroup();
+
+    item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
+}
+
 void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan)
 {
     const auto callback = [&](const std::string& directory,
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index ab09edce30..0950d96220 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QModelIndex>
+#include <QSettings>
 #include <QStandardItem>
 #include <QStandardItemModel>
 #include <QString>
@@ -30,6 +31,9 @@ public:
 
     void PopulateAsync(const QString& dir_path, bool deep_scan);
 
+    void SaveInterfaceLayout(QSettings& settings);
+    void LoadInterfaceLayout(QSettings& settings);
+
 public slots:
     void AddEntry(QList<QStandardItem*> entry_items);
 
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index c5e338c91f..298649aaf6 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -141,6 +141,8 @@ GMainWindow::GMainWindow() : emu_thread(nullptr)
     microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool());
     settings.endGroup();
 
+    game_list->LoadInterfaceLayout(settings);
+
     ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer);
     SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked());
 
@@ -490,6 +492,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
     settings.setValue("singleWindowMode", ui.action_Single_Window_Mode->isChecked());
     settings.setValue("displayTitleBars", ui.actionDisplay_widget_title_bars->isChecked());
     settings.setValue("firstStart", false);
+    game_list->SaveInterfaceLayout(settings);
     SaveHotkeys(settings);
 
     // Shutdown session if the emu thread is active...