game_list_worker.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright 2018 yuzu emulator team
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <memory>
  5. #include <string>
  6. #include <utility>
  7. #include <vector>
  8. #include <QDir>
  9. #include <QFileInfo>
  10. #include "common/common_paths.h"
  11. #include "common/file_util.h"
  12. #include "core/core.h"
  13. #include "core/file_sys/card_image.h"
  14. #include "core/file_sys/content_archive.h"
  15. #include "core/file_sys/control_metadata.h"
  16. #include "core/file_sys/mode.h"
  17. #include "core/file_sys/nca_metadata.h"
  18. #include "core/file_sys/patch_manager.h"
  19. #include "core/file_sys/registered_cache.h"
  20. #include "core/file_sys/submission_package.h"
  21. #include "core/hle/service/filesystem/filesystem.h"
  22. #include "core/loader/loader.h"
  23. #include "yuzu/compatibility_list.h"
  24. #include "yuzu/game_list.h"
  25. #include "yuzu/game_list_p.h"
  26. #include "yuzu/game_list_worker.h"
  27. #include "yuzu/ui_settings.h"
  28. namespace {
  29. void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
  30. std::vector<u8>& icon, std::string& name) {
  31. auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
  32. if (icon_file != nullptr)
  33. icon = icon_file->ReadAllBytes();
  34. if (nacp != nullptr)
  35. name = nacp->GetApplicationName();
  36. }
  37. bool HasSupportedFileExtension(const std::string& file_name) {
  38. const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
  39. return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
  40. }
  41. bool IsExtractedNCAMain(const std::string& file_name) {
  42. return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main");
  43. }
  44. QString FormatGameName(const std::string& physical_name) {
  45. const QString physical_name_as_qstring = QString::fromStdString(physical_name);
  46. const QFileInfo file_info(physical_name_as_qstring);
  47. if (IsExtractedNCAMain(physical_name)) {
  48. return file_info.dir().path();
  49. }
  50. return physical_name_as_qstring;
  51. }
  52. QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
  53. Loader::AppLoader& loader, bool updatable = true) {
  54. QString out;
  55. FileSys::VirtualFile update_raw;
  56. loader.ReadUpdateRaw(update_raw);
  57. for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
  58. const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
  59. if (!updatable && is_update) {
  60. continue;
  61. }
  62. const QString type = QString::fromStdString(kv.first);
  63. if (kv.second.empty()) {
  64. out.append(QStringLiteral("%1\n").arg(type));
  65. } else {
  66. auto ver = kv.second;
  67. // Display container name for packed updates
  68. if (is_update && ver == "PACKED") {
  69. ver = Loader::GetFileTypeString(loader.GetFileType());
  70. }
  71. out.append(QStringLiteral("%1 (%2)\n").arg(type, QString::fromStdString(ver)));
  72. }
  73. }
  74. out.chop(1);
  75. return out;
  76. }
  77. QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
  78. const std::vector<u8>& icon, Loader::AppLoader& loader,
  79. u64 program_id, const CompatibilityList& compatibility_list,
  80. const FileSys::PatchManager& patch) {
  81. const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
  82. // The game list uses this as compatibility number for untested games
  83. QString compatibility{QStringLiteral("99")};
  84. if (it != compatibility_list.end()) {
  85. compatibility = it->second.first;
  86. }
  87. const auto file_type = loader.GetFileType();
  88. const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
  89. QList<QStandardItem*> list{
  90. new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
  91. file_type_string, program_id),
  92. new GameListItemCompat(compatibility),
  93. new GameListItem(file_type_string),
  94. new GameListItemSize(FileUtil::GetSize(path)),
  95. };
  96. if (UISettings::values.show_add_ons) {
  97. list.insert(
  98. 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
  99. }
  100. return list;
  101. }
  102. } // Anonymous namespace
  103. GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
  104. FileSys::ManualContentProvider* provider, QString dir_path,
  105. bool deep_scan, const CompatibilityList& compatibility_list)
  106. : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
  107. compatibility_list(compatibility_list) {}
  108. GameListWorker::~GameListWorker() = default;
  109. void GameListWorker::AddTitlesToGameList() {
  110. const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
  111. Core::System::GetInstance().GetContentProvider());
  112. const auto installed_games = cache.ListEntriesFilterOrigin(
  113. std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
  114. for (const auto& [slot, game] : installed_games) {
  115. if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
  116. continue;
  117. const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
  118. std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
  119. if (!loader)
  120. continue;
  121. std::vector<u8> icon;
  122. std::string name;
  123. u64 program_id = 0;
  124. loader->ReadProgramId(program_id);
  125. const FileSys::PatchManager patch{program_id};
  126. const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control);
  127. if (control != nullptr)
  128. GetMetadataFromControlNCA(patch, *control, icon, name);
  129. emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
  130. compatibility_list, patch));
  131. }
  132. }
  133. void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
  134. unsigned int recursion) {
  135. const auto callback = [this, target, recursion](u64* num_entries_out,
  136. const std::string& directory,
  137. const std::string& virtual_name) -> bool {
  138. if (stop_processing) {
  139. // Breaks the callback loop.
  140. return false;
  141. }
  142. const std::string physical_name = directory + DIR_SEP + virtual_name;
  143. const bool is_dir = FileUtil::IsDirectory(physical_name);
  144. if (!is_dir &&
  145. (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
  146. const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
  147. auto loader = Loader::GetLoader(file);
  148. if (!loader) {
  149. return true;
  150. }
  151. const auto file_type = loader->GetFileType();
  152. if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) &&
  153. !UISettings::values.show_unknown) {
  154. return true;
  155. }
  156. u64 program_id = 0;
  157. const auto res2 = loader->ReadProgramId(program_id);
  158. if (target == ScanTarget::FillManualContentProvider) {
  159. if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
  160. provider->AddEntry(FileSys::TitleType::Application,
  161. FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
  162. program_id, file);
  163. } else if (res2 == Loader::ResultStatus::Success &&
  164. (file_type == Loader::FileType::XCI ||
  165. file_type == Loader::FileType::NSP)) {
  166. const auto nsp = file_type == Loader::FileType::NSP
  167. ? std::make_shared<FileSys::NSP>(file)
  168. : FileSys::XCI{file}.GetSecurePartitionNSP();
  169. for (const auto& title : nsp->GetNCAs()) {
  170. for (const auto& entry : title.second) {
  171. provider->AddEntry(entry.first.first, entry.first.second, title.first,
  172. entry.second->GetBaseFile());
  173. }
  174. }
  175. }
  176. } else {
  177. std::vector<u8> icon;
  178. const auto res1 = loader->ReadIcon(icon);
  179. std::string name = " ";
  180. const auto res3 = loader->ReadTitle(name);
  181. const FileSys::PatchManager patch{program_id};
  182. emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
  183. compatibility_list, patch));
  184. }
  185. } else if (is_dir && recursion > 0) {
  186. watch_list.append(QString::fromStdString(physical_name));
  187. ScanFileSystem(target, physical_name, recursion - 1);
  188. }
  189. return true;
  190. };
  191. FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
  192. }
  193. void GameListWorker::run() {
  194. stop_processing = false;
  195. watch_list.append(dir_path);
  196. provider->ClearAllEntries();
  197. ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
  198. deep_scan ? 256 : 0);
  199. AddTitlesToGameList();
  200. ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
  201. emit Finished(watch_list);
  202. }
  203. void GameListWorker::Cancel() {
  204. this->disconnect();
  205. stop_processing = true;
  206. }