diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index ea9ea69e4..a2b6e984e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -43,6 +43,8 @@ add_executable(yuzu
     game_list.cpp
     game_list.h
     game_list_p.h
+    game_list_worker.cpp
+    game_list_worker.h
     hotkeys.cpp
     hotkeys.h
     main.cpp
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index a3b841684..86532e4a9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -18,17 +18,10 @@
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
-#include "core/file_sys/content_archive.h"
-#include "core/file_sys/control_metadata.h"
-#include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/patch_manager.h"
-#include "core/file_sys/registered_cache.h"
-#include "core/file_sys/romfs.h"
-#include "core/file_sys/vfs_real.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/loader/loader.h"
 #include "yuzu/game_list.h"
 #include "yuzu/game_list_p.h"
+#include "yuzu/game_list_worker.h"
 #include "yuzu/main.h"
 #include "yuzu/ui_settings.h"
 
@@ -436,45 +429,6 @@ void GameList::LoadInterfaceLayout() {
 
 const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
 
-static bool HasSupportedFileExtension(const std::string& file_name) {
-    const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
-    return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
-}
-
-static bool IsExtractedNCAMain(const std::string& file_name) {
-    return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
-}
-
-static QString FormatGameName(const std::string& physical_name) {
-    const QString physical_name_as_qstring = QString::fromStdString(physical_name);
-    const QFileInfo file_info(physical_name_as_qstring);
-
-    if (IsExtractedNCAMain(physical_name)) {
-        return file_info.dir().path();
-    }
-
-    return physical_name_as_qstring;
-}
-
-static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
-                                       bool updatable = true) {
-    QString out;
-    for (const auto& kv : patch_manager.GetPatchVersionNames()) {
-        if (!updatable && kv.first == FileSys::PatchType::Update)
-            continue;
-
-        if (kv.second.empty()) {
-            out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
-        } else {
-            out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
-                           .c_str());
-        }
-    }
-
-    out.chop(1);
-    return out;
-}
-
 void GameList::RefreshGameDirectory() {
     if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
         LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
@@ -482,176 +436,3 @@ void GameList::RefreshGameDirectory() {
         PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
     }
 }
-
-static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
-                                      const std::shared_ptr<FileSys::NCA>& nca,
-                                      std::vector<u8>& icon, std::string& name) {
-    auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
-    if (icon_file != nullptr)
-        icon = icon_file->ReadAllBytes();
-    if (nacp != nullptr)
-        name = nacp->GetApplicationName();
-}
-
-GameListWorker::GameListWorker(
-    FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
-    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
-    : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
-      compatibility_list(compatibility_list) {}
-
-GameListWorker::~GameListWorker() = default;
-
-void GameListWorker::AddInstalledTitlesToGameList() {
-    const auto cache = Service::FileSystem::GetUnionContents();
-    const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
-                                                          FileSys::ContentRecordType::Program);
-
-    for (const auto& game : installed_games) {
-        const auto& file = cache->GetEntryUnparsed(game);
-        std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
-        if (!loader)
-            continue;
-
-        std::vector<u8> icon;
-        std::string name;
-        u64 program_id = 0;
-        loader->ReadProgramId(program_id);
-
-        const FileSys::PatchManager patch{program_id};
-        const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
-        if (control != nullptr)
-            GetMetadataFromControlNCA(patch, control, icon, name);
-
-        auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
-
-        // The game list uses this as compatibility number for untested games
-        QString compatibility("99");
-        if (it != compatibility_list.end())
-            compatibility = it->second.first;
-
-        emit EntryReady({
-            new GameListItemPath(
-                FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
-                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
-                program_id),
-            new GameListItemCompat(compatibility),
-            new GameListItem(FormatPatchNameVersions(patch)),
-            new GameListItem(
-                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
-            new GameListItemSize(file->GetSize()),
-        });
-    }
-
-    const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
-                                                       FileSys::ContentRecordType::Control);
-
-    for (const auto& entry : control_data) {
-        const auto nca = cache->GetEntry(entry);
-        if (nca != nullptr)
-            nca_control_map.insert_or_assign(entry.title_id, nca);
-    }
-}
-
-void GameListWorker::FillControlMap(const std::string& dir_path) {
-    const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
-                                             const std::string& virtual_name) -> bool {
-        std::string physical_name = directory + DIR_SEP + virtual_name;
-
-        if (stop_processing)
-            return false; // Breaks the callback loop.
-
-        bool is_dir = FileUtil::IsDirectory(physical_name);
-        QFileInfo file_info(physical_name.c_str());
-        if (!is_dir && file_info.suffix().toStdString() == "nca") {
-            auto nca =
-                std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
-            if (nca->GetType() == FileSys::NCAContentType::Control)
-                nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
-        }
-        return true;
-    };
-
-    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
-}
-
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
-    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
-                                            const std::string& virtual_name) -> bool {
-        std::string physical_name = directory + DIR_SEP + virtual_name;
-
-        if (stop_processing)
-            return false; // Breaks the callback loop.
-
-        bool is_dir = FileUtil::IsDirectory(physical_name);
-        if (!is_dir &&
-            (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
-            std::unique_ptr<Loader::AppLoader> loader =
-                Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
-            if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
-                             loader->GetFileType() == Loader::FileType::Error) &&
-                            !UISettings::values.show_unknown))
-                return true;
-
-            std::vector<u8> icon;
-            const auto res1 = loader->ReadIcon(icon);
-
-            u64 program_id = 0;
-            const auto res2 = loader->ReadProgramId(program_id);
-
-            std::string name = " ";
-            const auto res3 = loader->ReadTitle(name);
-
-            const FileSys::PatchManager patch{program_id};
-
-            if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
-                res2 == Loader::ResultStatus::Success) {
-                // Use from metadata pool.
-                if (nca_control_map.find(program_id) != nca_control_map.end()) {
-                    const auto nca = nca_control_map[program_id];
-                    GetMetadataFromControlNCA(patch, nca, icon, name);
-                }
-            }
-
-            auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
-
-            // The game list uses this as compatibility number for untested games
-            QString compatibility("99");
-            if (it != compatibility_list.end())
-                compatibility = it->second.first;
-
-            emit EntryReady({
-                new GameListItemPath(
-                    FormatGameName(physical_name), icon, QString::fromStdString(name),
-                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
-                    program_id),
-                new GameListItemCompat(compatibility),
-                new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
-                new GameListItem(
-                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
-                new GameListItemSize(FileUtil::GetSize(physical_name)),
-            });
-        } else if (is_dir && recursion > 0) {
-            watch_list.append(QString::fromStdString(physical_name));
-            AddFstEntriesToGameList(physical_name, recursion - 1);
-        }
-
-        return true;
-    };
-
-    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
-}
-
-void GameListWorker::run() {
-    stop_processing = false;
-    watch_list.append(dir_path);
-    FillControlMap(dir_path.toStdString());
-    AddInstalledTitlesToGameList();
-    AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
-    nca_control_map.clear();
-    emit Finished(watch_list);
-}
-
-void GameListWorker::Cancel() {
-    this->disconnect();
-    stop_processing = true;
-}
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index a70a151c5..2720bf143 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -6,9 +6,7 @@
 
 #include <algorithm>
 #include <array>
-#include <atomic>
 #include <map>
-#include <memory>
 #include <string>
 #include <unordered_map>
 #include <utility>
@@ -16,7 +14,6 @@
 #include <QCoreApplication>
 #include <QImage>
 #include <QObject>
-#include <QRunnable>
 #include <QStandardItem>
 #include <QString>
 
@@ -26,12 +23,6 @@
 #include "yuzu/ui_settings.h"
 #include "yuzu/util/util.h"
 
-namespace FileSys {
-class NCA;
-class RegisteredCache;
-class VfsFilesystem;
-} // namespace FileSys
-
 /**
  * Gets the default icon (for games without valid SMDH)
  * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
@@ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) {
     return icon;
 }
 
-static auto FindMatchingCompatibilityEntry(
-    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
-    u64 program_id) {
-    return std::find_if(
-        compatibility_list.begin(), compatibility_list.end(),
-        [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
-            std::string pid = fmt::format("{:016X}", program_id);
-            return element.first == pid;
-        });
-}
-
 class GameListItem : public QStandardItem {
 
 public:
@@ -197,49 +177,13 @@ public:
     }
 };
 
-/**
- * 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(
-        std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
-        const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
-    ~GameListWorker() override;
-
-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);
-
-    /**
-     * After the worker has traversed the game directory looking for entries, this signal is emmited
-     * with a list of folders that should be watched for changes as well.
-     */
-    void Finished(QStringList watch_list);
-
-private:
-    std::shared_ptr<FileSys::VfsFilesystem> vfs;
-    std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
-    QStringList watch_list;
-    QString dir_path;
-    bool deep_scan;
-    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
-    std::atomic_bool stop_processing;
-
-    void AddInstalledTitlesToGameList();
-    void FillControlMap(const std::string& dir_path);
-    void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
-};
+inline auto FindMatchingCompatibilityEntry(
+    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
+    u64 program_id) {
+    return std::find_if(
+        compatibility_list.begin(), compatibility_list.end(),
+        [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
+            std::string pid = fmt::format("{:016X}", program_id);
+            return element.first == pid;
+        });
+}
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
new file mode 100644
index 000000000..9f26935d6
--- /dev/null
+++ b/src/yuzu/game_list_worker.cpp
@@ -0,0 +1,239 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <QDir>
+#include <QFileInfo>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/loader.h"
+#include "yuzu/game_list.h"
+#include "yuzu/game_list_p.h"
+#include "yuzu/game_list_worker.h"
+#include "yuzu/ui_settings.h"
+
+namespace {
+void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
+                               const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
+                               std::string& name) {
+    auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
+    if (icon_file != nullptr)
+        icon = icon_file->ReadAllBytes();
+    if (nacp != nullptr)
+        name = nacp->GetApplicationName();
+}
+
+bool HasSupportedFileExtension(const std::string& file_name) {
+    const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
+    return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
+}
+
+bool IsExtractedNCAMain(const std::string& file_name) {
+    return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
+}
+
+QString FormatGameName(const std::string& physical_name) {
+    const QString physical_name_as_qstring = QString::fromStdString(physical_name);
+    const QFileInfo file_info(physical_name_as_qstring);
+
+    if (IsExtractedNCAMain(physical_name)) {
+        return file_info.dir().path();
+    }
+
+    return physical_name_as_qstring;
+}
+
+QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
+    QString out;
+    for (const auto& kv : patch_manager.GetPatchVersionNames()) {
+        if (!updatable && kv.first == FileSys::PatchType::Update)
+            continue;
+
+        if (kv.second.empty()) {
+            out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
+        } else {
+            out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
+                           .c_str());
+        }
+    }
+
+    out.chop(1);
+    return out;
+}
+} // Anonymous namespace
+
+GameListWorker::GameListWorker(
+    FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
+    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
+    : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
+      compatibility_list(compatibility_list) {}
+
+GameListWorker::~GameListWorker() = default;
+
+void GameListWorker::AddInstalledTitlesToGameList() {
+    const auto cache = Service::FileSystem::GetUnionContents();
+    const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
+                                                          FileSys::ContentRecordType::Program);
+
+    for (const auto& game : installed_games) {
+        const auto& file = cache->GetEntryUnparsed(game);
+        std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
+        if (!loader)
+            continue;
+
+        std::vector<u8> icon;
+        std::string name;
+        u64 program_id = 0;
+        loader->ReadProgramId(program_id);
+
+        const FileSys::PatchManager patch{program_id};
+        const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
+        if (control != nullptr)
+            GetMetadataFromControlNCA(patch, control, icon, name);
+
+        auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
+
+        // The game list uses this as compatibility number for untested games
+        QString compatibility("99");
+        if (it != compatibility_list.end())
+            compatibility = it->second.first;
+
+        emit EntryReady({
+            new GameListItemPath(
+                FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
+                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
+                program_id),
+            new GameListItemCompat(compatibility),
+            new GameListItem(FormatPatchNameVersions(patch)),
+            new GameListItem(
+                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
+            new GameListItemSize(file->GetSize()),
+        });
+    }
+
+    const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
+                                                       FileSys::ContentRecordType::Control);
+
+    for (const auto& entry : control_data) {
+        const auto nca = cache->GetEntry(entry);
+        if (nca != nullptr)
+            nca_control_map.insert_or_assign(entry.title_id, nca);
+    }
+}
+
+void GameListWorker::FillControlMap(const std::string& dir_path) {
+    const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
+                                             const std::string& virtual_name) -> bool {
+        std::string physical_name = directory + DIR_SEP + virtual_name;
+
+        if (stop_processing)
+            return false; // Breaks the callback loop.
+
+        bool is_dir = FileUtil::IsDirectory(physical_name);
+        QFileInfo file_info(physical_name.c_str());
+        if (!is_dir && file_info.suffix().toStdString() == "nca") {
+            auto nca =
+                std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+            if (nca->GetType() == FileSys::NCAContentType::Control)
+                nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
+        }
+        return true;
+    };
+
+    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
+}
+
+void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
+    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
+                                            const std::string& virtual_name) -> bool {
+        std::string physical_name = directory + DIR_SEP + virtual_name;
+
+        if (stop_processing)
+            return false; // Breaks the callback loop.
+
+        bool is_dir = FileUtil::IsDirectory(physical_name);
+        if (!is_dir &&
+            (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
+            std::unique_ptr<Loader::AppLoader> loader =
+                Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+            if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
+                             loader->GetFileType() == Loader::FileType::Error) &&
+                            !UISettings::values.show_unknown))
+                return true;
+
+            std::vector<u8> icon;
+            const auto res1 = loader->ReadIcon(icon);
+
+            u64 program_id = 0;
+            const auto res2 = loader->ReadProgramId(program_id);
+
+            std::string name = " ";
+            const auto res3 = loader->ReadTitle(name);
+
+            const FileSys::PatchManager patch{program_id};
+
+            if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
+                res2 == Loader::ResultStatus::Success) {
+                // Use from metadata pool.
+                if (nca_control_map.find(program_id) != nca_control_map.end()) {
+                    const auto nca = nca_control_map[program_id];
+                    GetMetadataFromControlNCA(patch, nca, icon, name);
+                }
+            }
+
+            auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
+
+            // The game list uses this as compatibility number for untested games
+            QString compatibility("99");
+            if (it != compatibility_list.end())
+                compatibility = it->second.first;
+
+            emit EntryReady({
+                new GameListItemPath(
+                    FormatGameName(physical_name), icon, QString::fromStdString(name),
+                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
+                    program_id),
+                new GameListItemCompat(compatibility),
+                new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
+                new GameListItem(
+                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
+                new GameListItemSize(FileUtil::GetSize(physical_name)),
+            });
+        } else if (is_dir && recursion > 0) {
+            watch_list.append(QString::fromStdString(physical_name));
+            AddFstEntriesToGameList(physical_name, recursion - 1);
+        }
+
+        return true;
+    };
+
+    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
+}
+
+void GameListWorker::run() {
+    stop_processing = false;
+    watch_list.append(dir_path);
+    FillControlMap(dir_path.toStdString());
+    AddInstalledTitlesToGameList();
+    AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
+    nca_control_map.clear();
+    emit Finished(watch_list);
+}
+
+void GameListWorker::Cancel() {
+    this->disconnect();
+    stop_processing = true;
+}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
new file mode 100644
index 000000000..42c93fc31
--- /dev/null
+++ b/src/yuzu/game_list_worker.h
@@ -0,0 +1,72 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <QList>
+#include <QObject>
+#include <QRunnable>
+#include <QString>
+
+#include "common/common_types.h"
+
+class QStandardItem;
+
+namespace FileSys {
+class NCA;
+class VfsFilesystem;
+} // namespace FileSys
+
+/**
+ * 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(
+        std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
+        const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
+    ~GameListWorker() override;
+
+    /// 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);
+
+    /**
+     * After the worker has traversed the game directory looking for entries, this signal is emitted
+     * with a list of folders that should be watched for changes as well.
+     */
+    void Finished(QStringList watch_list);
+
+private:
+    void AddInstalledTitlesToGameList();
+    void FillControlMap(const std::string& dir_path);
+    void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
+
+    std::shared_ptr<FileSys::VfsFilesystem> vfs;
+    std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
+    QStringList watch_list;
+    QString dir_path;
+    bool deep_scan;
+    const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
+    std::atomic_bool stop_processing;
+};