summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/game_list.cpp221
-rw-r--r--src/yuzu/game_list_p.h76
-rw-r--r--src/yuzu/game_list_worker.cpp239
-rw-r--r--src/yuzu/game_list_worker.h72
5 files changed, 324 insertions, 286 deletions
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
43 game_list.cpp 43 game_list.cpp
44 game_list.h 44 game_list.h
45 game_list_p.h 45 game_list_p.h
46 game_list_worker.cpp
47 game_list_worker.h
46 hotkeys.cpp 48 hotkeys.cpp
47 hotkeys.h 49 hotkeys.h
48 main.cpp 50 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 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/file_util.h" 19#include "common/file_util.h"
20#include "common/logging/log.h" 20#include "common/logging/log.h"
21#include "core/file_sys/content_archive.h"
22#include "core/file_sys/control_metadata.h"
23#include "core/file_sys/nca_metadata.h"
24#include "core/file_sys/patch_manager.h" 21#include "core/file_sys/patch_manager.h"
25#include "core/file_sys/registered_cache.h"
26#include "core/file_sys/romfs.h"
27#include "core/file_sys/vfs_real.h"
28#include "core/hle/service/filesystem/filesystem.h"
29#include "core/loader/loader.h"
30#include "yuzu/game_list.h" 22#include "yuzu/game_list.h"
31#include "yuzu/game_list_p.h" 23#include "yuzu/game_list_p.h"
24#include "yuzu/game_list_worker.h"
32#include "yuzu/main.h" 25#include "yuzu/main.h"
33#include "yuzu/ui_settings.h" 26#include "yuzu/ui_settings.h"
34 27
@@ -436,45 +429,6 @@ void GameList::LoadInterfaceLayout() {
436 429
437const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; 430const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
438 431
439static bool HasSupportedFileExtension(const std::string& file_name) {
440 const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
441 return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
442}
443
444static bool IsExtractedNCAMain(const std::string& file_name) {
445 return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
446}
447
448static QString FormatGameName(const std::string& physical_name) {
449 const QString physical_name_as_qstring = QString::fromStdString(physical_name);
450 const QFileInfo file_info(physical_name_as_qstring);
451
452 if (IsExtractedNCAMain(physical_name)) {
453 return file_info.dir().path();
454 }
455
456 return physical_name_as_qstring;
457}
458
459static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
460 bool updatable = true) {
461 QString out;
462 for (const auto& kv : patch_manager.GetPatchVersionNames()) {
463 if (!updatable && kv.first == FileSys::PatchType::Update)
464 continue;
465
466 if (kv.second.empty()) {
467 out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
468 } else {
469 out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
470 .c_str());
471 }
472 }
473
474 out.chop(1);
475 return out;
476}
477
478void GameList::RefreshGameDirectory() { 432void GameList::RefreshGameDirectory() {
479 if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { 433 if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
480 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); 434 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
@@ -482,176 +436,3 @@ void GameList::RefreshGameDirectory() {
482 PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 436 PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
483 } 437 }
484} 438}
485
486static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
487 const std::shared_ptr<FileSys::NCA>& nca,
488 std::vector<u8>& icon, std::string& name) {
489 auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
490 if (icon_file != nullptr)
491 icon = icon_file->ReadAllBytes();
492 if (nacp != nullptr)
493 name = nacp->GetApplicationName();
494}
495
496GameListWorker::GameListWorker(
497 FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
498 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
499 : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
500 compatibility_list(compatibility_list) {}
501
502GameListWorker::~GameListWorker() = default;
503
504void GameListWorker::AddInstalledTitlesToGameList() {
505 const auto cache = Service::FileSystem::GetUnionContents();
506 const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
507 FileSys::ContentRecordType::Program);
508
509 for (const auto& game : installed_games) {
510 const auto& file = cache->GetEntryUnparsed(game);
511 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
512 if (!loader)
513 continue;
514
515 std::vector<u8> icon;
516 std::string name;
517 u64 program_id = 0;
518 loader->ReadProgramId(program_id);
519
520 const FileSys::PatchManager patch{program_id};
521 const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
522 if (control != nullptr)
523 GetMetadataFromControlNCA(patch, control, icon, name);
524
525 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
526
527 // The game list uses this as compatibility number for untested games
528 QString compatibility("99");
529 if (it != compatibility_list.end())
530 compatibility = it->second.first;
531
532 emit EntryReady({
533 new GameListItemPath(
534 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
535 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
536 program_id),
537 new GameListItemCompat(compatibility),
538 new GameListItem(FormatPatchNameVersions(patch)),
539 new GameListItem(
540 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
541 new GameListItemSize(file->GetSize()),
542 });
543 }
544
545 const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
546 FileSys::ContentRecordType::Control);
547
548 for (const auto& entry : control_data) {
549 const auto nca = cache->GetEntry(entry);
550 if (nca != nullptr)
551 nca_control_map.insert_or_assign(entry.title_id, nca);
552 }
553}
554
555void GameListWorker::FillControlMap(const std::string& dir_path) {
556 const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
557 const std::string& virtual_name) -> bool {
558 std::string physical_name = directory + DIR_SEP + virtual_name;
559
560 if (stop_processing)
561 return false; // Breaks the callback loop.
562
563 bool is_dir = FileUtil::IsDirectory(physical_name);
564 QFileInfo file_info(physical_name.c_str());
565 if (!is_dir && file_info.suffix().toStdString() == "nca") {
566 auto nca =
567 std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
568 if (nca->GetType() == FileSys::NCAContentType::Control)
569 nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
570 }
571 return true;
572 };
573
574 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
575}
576
577void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
578 const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
579 const std::string& virtual_name) -> bool {
580 std::string physical_name = directory + DIR_SEP + virtual_name;
581
582 if (stop_processing)
583 return false; // Breaks the callback loop.
584
585 bool is_dir = FileUtil::IsDirectory(physical_name);
586 if (!is_dir &&
587 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
588 std::unique_ptr<Loader::AppLoader> loader =
589 Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
590 if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
591 loader->GetFileType() == Loader::FileType::Error) &&
592 !UISettings::values.show_unknown))
593 return true;
594
595 std::vector<u8> icon;
596 const auto res1 = loader->ReadIcon(icon);
597
598 u64 program_id = 0;
599 const auto res2 = loader->ReadProgramId(program_id);
600
601 std::string name = " ";
602 const auto res3 = loader->ReadTitle(name);
603
604 const FileSys::PatchManager patch{program_id};
605
606 if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
607 res2 == Loader::ResultStatus::Success) {
608 // Use from metadata pool.
609 if (nca_control_map.find(program_id) != nca_control_map.end()) {
610 const auto nca = nca_control_map[program_id];
611 GetMetadataFromControlNCA(patch, nca, icon, name);
612 }
613 }
614
615 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
616
617 // The game list uses this as compatibility number for untested games
618 QString compatibility("99");
619 if (it != compatibility_list.end())
620 compatibility = it->second.first;
621
622 emit EntryReady({
623 new GameListItemPath(
624 FormatGameName(physical_name), icon, QString::fromStdString(name),
625 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
626 program_id),
627 new GameListItemCompat(compatibility),
628 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
629 new GameListItem(
630 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
631 new GameListItemSize(FileUtil::GetSize(physical_name)),
632 });
633 } else if (is_dir && recursion > 0) {
634 watch_list.append(QString::fromStdString(physical_name));
635 AddFstEntriesToGameList(physical_name, recursion - 1);
636 }
637
638 return true;
639 };
640
641 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
642}
643
644void GameListWorker::run() {
645 stop_processing = false;
646 watch_list.append(dir_path);
647 FillControlMap(dir_path.toStdString());
648 AddInstalledTitlesToGameList();
649 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
650 nca_control_map.clear();
651 emit Finished(watch_list);
652}
653
654void GameListWorker::Cancel() {
655 this->disconnect();
656 stop_processing = true;
657}
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 @@
6 6
7#include <algorithm> 7#include <algorithm>
8#include <array> 8#include <array>
9#include <atomic>
10#include <map> 9#include <map>
11#include <memory>
12#include <string> 10#include <string>
13#include <unordered_map> 11#include <unordered_map>
14#include <utility> 12#include <utility>
@@ -16,7 +14,6 @@
16#include <QCoreApplication> 14#include <QCoreApplication>
17#include <QImage> 15#include <QImage>
18#include <QObject> 16#include <QObject>
19#include <QRunnable>
20#include <QStandardItem> 17#include <QStandardItem>
21#include <QString> 18#include <QString>
22 19
@@ -26,12 +23,6 @@
26#include "yuzu/ui_settings.h" 23#include "yuzu/ui_settings.h"
27#include "yuzu/util/util.h" 24#include "yuzu/util/util.h"
28 25
29namespace FileSys {
30class NCA;
31class RegisteredCache;
32class VfsFilesystem;
33} // namespace FileSys
34
35/** 26/**
36 * Gets the default icon (for games without valid SMDH) 27 * Gets the default icon (for games without valid SMDH)
37 * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) 28 * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
@@ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) {
43 return icon; 34 return icon;
44} 35}
45 36
46static auto FindMatchingCompatibilityEntry(
47 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
48 u64 program_id) {
49 return std::find_if(
50 compatibility_list.begin(), compatibility_list.end(),
51 [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
52 std::string pid = fmt::format("{:016X}", program_id);
53 return element.first == pid;
54 });
55}
56
57class GameListItem : public QStandardItem { 37class GameListItem : public QStandardItem {
58 38
59public: 39public:
@@ -197,49 +177,13 @@ public:
197 } 177 }
198}; 178};
199 179
200/** 180inline auto FindMatchingCompatibilityEntry(
201 * Asynchronous worker object for populating the game list. 181 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
202 * Communicates with other threads through Qt's signal/slot system. 182 u64 program_id) {
203 */ 183 return std::find_if(
204class GameListWorker : public QObject, public QRunnable { 184 compatibility_list.begin(), compatibility_list.end(),
205 Q_OBJECT 185 [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
206 186 std::string pid = fmt::format("{:016X}", program_id);
207public: 187 return element.first == pid;
208 GameListWorker( 188 });
209 std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, 189}
210 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
211 ~GameListWorker() override;
212
213public slots:
214 /// Starts the processing of directory tree information.
215 void run() override;
216 /// Tells the worker that it should no longer continue processing. Thread-safe.
217 void Cancel();
218
219signals:
220 /**
221 * The `EntryReady` signal is emitted once an entry has been prepared and is ready
222 * to be added to the game list.
223 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
224 */
225 void EntryReady(QList<QStandardItem*> entry_items);
226
227 /**
228 * After the worker has traversed the game directory looking for entries, this signal is emmited
229 * with a list of folders that should be watched for changes as well.
230 */
231 void Finished(QStringList watch_list);
232
233private:
234 std::shared_ptr<FileSys::VfsFilesystem> vfs;
235 std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
236 QStringList watch_list;
237 QString dir_path;
238 bool deep_scan;
239 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
240 std::atomic_bool stop_processing;
241
242 void AddInstalledTitlesToGameList();
243 void FillControlMap(const std::string& dir_path);
244 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
245};
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 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include <string>
7#include <utility>
8#include <vector>
9
10#include <QDir>
11#include <QFileInfo>
12
13#include "common/common_paths.h"
14#include "common/file_util.h"
15#include "core/file_sys/content_archive.h"
16#include "core/file_sys/control_metadata.h"
17#include "core/file_sys/mode.h"
18#include "core/file_sys/nca_metadata.h"
19#include "core/file_sys/patch_manager.h"
20#include "core/file_sys/registered_cache.h"
21#include "core/hle/service/filesystem/filesystem.h"
22#include "core/loader/loader.h"
23#include "yuzu/game_list.h"
24#include "yuzu/game_list_p.h"
25#include "yuzu/game_list_worker.h"
26#include "yuzu/ui_settings.h"
27
28namespace {
29void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
30 const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
31 std::string& name) {
32 auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
33 if (icon_file != nullptr)
34 icon = icon_file->ReadAllBytes();
35 if (nacp != nullptr)
36 name = nacp->GetApplicationName();
37}
38
39bool HasSupportedFileExtension(const std::string& file_name) {
40 const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
41 return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
42}
43
44bool IsExtractedNCAMain(const std::string& file_name) {
45 return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
46}
47
48QString FormatGameName(const std::string& physical_name) {
49 const QString physical_name_as_qstring = QString::fromStdString(physical_name);
50 const QFileInfo file_info(physical_name_as_qstring);
51
52 if (IsExtractedNCAMain(physical_name)) {
53 return file_info.dir().path();
54 }
55
56 return physical_name_as_qstring;
57}
58
59QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
60 QString out;
61 for (const auto& kv : patch_manager.GetPatchVersionNames()) {
62 if (!updatable && kv.first == FileSys::PatchType::Update)
63 continue;
64
65 if (kv.second.empty()) {
66 out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
67 } else {
68 out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
69 .c_str());
70 }
71 }
72
73 out.chop(1);
74 return out;
75}
76} // Anonymous namespace
77
78GameListWorker::GameListWorker(
79 FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
80 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
81 : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
82 compatibility_list(compatibility_list) {}
83
84GameListWorker::~GameListWorker() = default;
85
86void GameListWorker::AddInstalledTitlesToGameList() {
87 const auto cache = Service::FileSystem::GetUnionContents();
88 const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
89 FileSys::ContentRecordType::Program);
90
91 for (const auto& game : installed_games) {
92 const auto& file = cache->GetEntryUnparsed(game);
93 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
94 if (!loader)
95 continue;
96
97 std::vector<u8> icon;
98 std::string name;
99 u64 program_id = 0;
100 loader->ReadProgramId(program_id);
101
102 const FileSys::PatchManager patch{program_id};
103 const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
104 if (control != nullptr)
105 GetMetadataFromControlNCA(patch, control, icon, name);
106
107 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
108
109 // The game list uses this as compatibility number for untested games
110 QString compatibility("99");
111 if (it != compatibility_list.end())
112 compatibility = it->second.first;
113
114 emit EntryReady({
115 new GameListItemPath(
116 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
117 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
118 program_id),
119 new GameListItemCompat(compatibility),
120 new GameListItem(FormatPatchNameVersions(patch)),
121 new GameListItem(
122 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
123 new GameListItemSize(file->GetSize()),
124 });
125 }
126
127 const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
128 FileSys::ContentRecordType::Control);
129
130 for (const auto& entry : control_data) {
131 const auto nca = cache->GetEntry(entry);
132 if (nca != nullptr)
133 nca_control_map.insert_or_assign(entry.title_id, nca);
134 }
135}
136
137void GameListWorker::FillControlMap(const std::string& dir_path) {
138 const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
139 const std::string& virtual_name) -> bool {
140 std::string physical_name = directory + DIR_SEP + virtual_name;
141
142 if (stop_processing)
143 return false; // Breaks the callback loop.
144
145 bool is_dir = FileUtil::IsDirectory(physical_name);
146 QFileInfo file_info(physical_name.c_str());
147 if (!is_dir && file_info.suffix().toStdString() == "nca") {
148 auto nca =
149 std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
150 if (nca->GetType() == FileSys::NCAContentType::Control)
151 nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
152 }
153 return true;
154 };
155
156 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
157}
158
159void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
160 const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
161 const std::string& virtual_name) -> bool {
162 std::string physical_name = directory + DIR_SEP + virtual_name;
163
164 if (stop_processing)
165 return false; // Breaks the callback loop.
166
167 bool is_dir = FileUtil::IsDirectory(physical_name);
168 if (!is_dir &&
169 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
170 std::unique_ptr<Loader::AppLoader> loader =
171 Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
172 if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
173 loader->GetFileType() == Loader::FileType::Error) &&
174 !UISettings::values.show_unknown))
175 return true;
176
177 std::vector<u8> icon;
178 const auto res1 = loader->ReadIcon(icon);
179
180 u64 program_id = 0;
181 const auto res2 = loader->ReadProgramId(program_id);
182
183 std::string name = " ";
184 const auto res3 = loader->ReadTitle(name);
185
186 const FileSys::PatchManager patch{program_id};
187
188 if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
189 res2 == Loader::ResultStatus::Success) {
190 // Use from metadata pool.
191 if (nca_control_map.find(program_id) != nca_control_map.end()) {
192 const auto nca = nca_control_map[program_id];
193 GetMetadataFromControlNCA(patch, nca, icon, name);
194 }
195 }
196
197 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
198
199 // The game list uses this as compatibility number for untested games
200 QString compatibility("99");
201 if (it != compatibility_list.end())
202 compatibility = it->second.first;
203
204 emit EntryReady({
205 new GameListItemPath(
206 FormatGameName(physical_name), icon, QString::fromStdString(name),
207 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
208 program_id),
209 new GameListItemCompat(compatibility),
210 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
211 new GameListItem(
212 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
213 new GameListItemSize(FileUtil::GetSize(physical_name)),
214 });
215 } else if (is_dir && recursion > 0) {
216 watch_list.append(QString::fromStdString(physical_name));
217 AddFstEntriesToGameList(physical_name, recursion - 1);
218 }
219
220 return true;
221 };
222
223 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
224}
225
226void GameListWorker::run() {
227 stop_processing = false;
228 watch_list.append(dir_path);
229 FillControlMap(dir_path.toStdString());
230 AddInstalledTitlesToGameList();
231 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
232 nca_control_map.clear();
233 emit Finished(watch_list);
234}
235
236void GameListWorker::Cancel() {
237 this->disconnect();
238 stop_processing = true;
239}
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 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <map>
9#include <memory>
10#include <string>
11#include <unordered_map>
12
13#include <QList>
14#include <QObject>
15#include <QRunnable>
16#include <QString>
17
18#include "common/common_types.h"
19
20class QStandardItem;
21
22namespace FileSys {
23class NCA;
24class VfsFilesystem;
25} // namespace FileSys
26
27/**
28 * Asynchronous worker object for populating the game list.
29 * Communicates with other threads through Qt's signal/slot system.
30 */
31class GameListWorker : public QObject, public QRunnable {
32 Q_OBJECT
33
34public:
35 GameListWorker(
36 std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
37 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
38 ~GameListWorker() override;
39
40 /// Starts the processing of directory tree information.
41 void run() override;
42
43 /// Tells the worker that it should no longer continue processing. Thread-safe.
44 void Cancel();
45
46signals:
47 /**
48 * The `EntryReady` signal is emitted once an entry has been prepared and is ready
49 * to be added to the game list.
50 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
51 */
52 void EntryReady(QList<QStandardItem*> entry_items);
53
54 /**
55 * After the worker has traversed the game directory looking for entries, this signal is emitted
56 * with a list of folders that should be watched for changes as well.
57 */
58 void Finished(QStringList watch_list);
59
60private:
61 void AddInstalledTitlesToGameList();
62 void FillControlMap(const std::string& dir_path);
63 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
64
65 std::shared_ptr<FileSys::VfsFilesystem> vfs;
66 std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
67 QStringList watch_list;
68 QString dir_path;
69 bool deep_scan;
70 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
71 std::atomic_bool stop_processing;
72};