game_list_worker.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 <QSettings>
  11. #include "common/common_paths.h"
  12. #include "common/file_util.h"
  13. #include "core/core.h"
  14. #include "core/file_sys/card_image.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/file_sys/submission_package.h"
  22. #include "core/hle/service/filesystem/filesystem.h"
  23. #include "core/loader/loader.h"
  24. #include "yuzu/compatibility_list.h"
  25. #include "yuzu/game_list.h"
  26. #include "yuzu/game_list_p.h"
  27. #include "yuzu/game_list_worker.h"
  28. #include "yuzu/ui_settings.h"
  29. namespace {
  30. template <typename T>
  31. T GetGameListCachedObject(const std::string& filename, const std::string& ext,
  32. const std::function<T()>& generator);
  33. template <>
  34. QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
  35. const std::function<QString()>& generator) {
  36. if (!UISettings::values.cache_game_list || filename == "0000000000000000")
  37. return generator();
  38. const auto& path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
  39. DIR_SEP + filename + "." + ext;
  40. FileUtil::CreateFullPath(path);
  41. if (!FileUtil::Exists(path)) {
  42. const auto str = generator();
  43. std::ofstream stream(path);
  44. if (stream)
  45. stream << str.toStdString();
  46. stream.close();
  47. return str;
  48. }
  49. std::ifstream stream(path);
  50. if (stream) {
  51. const std::string out(std::istreambuf_iterator<char>{stream},
  52. std::istreambuf_iterator<char>{});
  53. stream.close();
  54. return QString::fromStdString(out);
  55. }
  56. return generator();
  57. }
  58. template <>
  59. std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
  60. const std::string& filename, const std::string& ext,
  61. const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
  62. if (!UISettings::values.cache_game_list || filename == "0000000000000000")
  63. return generator();
  64. const auto& path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
  65. "game_list" + DIR_SEP + filename + ".jpeg";
  66. const auto& path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
  67. "game_list" + DIR_SEP + filename + ".appname.txt";
  68. FileUtil::CreateFullPath(path1);
  69. if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
  70. const auto [icon, nacp] = generator();
  71. FileUtil::IOFile file1(path1, "wb");
  72. file1.Resize(icon.size());
  73. file1.WriteBytes(icon.data(), icon.size());
  74. std::ofstream stream2(path2, std::ios::out);
  75. if (stream2)
  76. stream2 << nacp;
  77. file1.Close();
  78. stream2.close();
  79. return std::make_pair(icon, nacp);
  80. }
  81. FileUtil::IOFile file1(path1, "rb");
  82. std::ifstream stream2(path2);
  83. std::vector<u8> vec(file1.GetSize());
  84. file1.ReadBytes(vec.data(), vec.size());
  85. if (stream2 && !vec.empty()) {
  86. const std::string out(std::istreambuf_iterator<char>{stream2},
  87. std::istreambuf_iterator<char>{});
  88. stream2.close();
  89. return std::make_pair(vec, out);
  90. }
  91. return generator();
  92. }
  93. void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
  94. std::vector<u8>& icon, std::string& name) {
  95. auto res = GetGameListCachedObject<std::pair<std::vector<u8>, std::string>>(
  96. fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
  97. const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
  98. return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
  99. });
  100. icon = std::move(res.first);
  101. name = std::move(res.second);
  102. }
  103. bool HasSupportedFileExtension(const std::string& file_name) {
  104. const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
  105. return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
  106. }
  107. bool IsExtractedNCAMain(const std::string& file_name) {
  108. return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main");
  109. }
  110. QString FormatGameName(const std::string& physical_name) {
  111. const QString physical_name_as_qstring = QString::fromStdString(physical_name);
  112. const QFileInfo file_info(physical_name_as_qstring);
  113. if (IsExtractedNCAMain(physical_name)) {
  114. return file_info.dir().path();
  115. }
  116. return physical_name_as_qstring;
  117. }
  118. QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
  119. Loader::AppLoader& loader, bool updatable = true) {
  120. QString out;
  121. FileSys::VirtualFile update_raw;
  122. loader.ReadUpdateRaw(update_raw);
  123. for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
  124. const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
  125. if (!updatable && is_update) {
  126. continue;
  127. }
  128. const QString type = QString::fromStdString(kv.first);
  129. if (kv.second.empty()) {
  130. out.append(QStringLiteral("%1\n").arg(type));
  131. } else {
  132. auto ver = kv.second;
  133. // Display container name for packed updates
  134. if (is_update && ver == "PACKED") {
  135. ver = Loader::GetFileTypeString(loader.GetFileType());
  136. }
  137. out.append(QStringLiteral("%1 (%2)\n").arg(type, QString::fromStdString(ver)));
  138. }
  139. }
  140. out.chop(1);
  141. return out;
  142. }
  143. QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
  144. const std::vector<u8>& icon, Loader::AppLoader& loader,
  145. u64 program_id, const CompatibilityList& compatibility_list,
  146. const FileSys::PatchManager& patch) {
  147. const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
  148. // The game list uses this as compatibility number for untested games
  149. QString compatibility{QStringLiteral("99")};
  150. if (it != compatibility_list.end()) {
  151. compatibility = it->second.first;
  152. }
  153. const auto file_type = loader.GetFileType();
  154. const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
  155. QList<QStandardItem*> list{
  156. new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
  157. file_type_string, program_id),
  158. new GameListItemCompat(compatibility),
  159. new GameListItem(file_type_string),
  160. new GameListItemSize(FileUtil::GetSize(path)),
  161. };
  162. if (UISettings::values.show_add_ons) {
  163. const auto patch_versions = GetGameListCachedObject<QString>(
  164. fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
  165. return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
  166. });
  167. list.insert(2, new GameListItem(patch_versions));
  168. }
  169. return list;
  170. }
  171. } // Anonymous namespace
  172. GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
  173. FileSys::ManualContentProvider* provider, QString dir_path,
  174. bool deep_scan, const CompatibilityList& compatibility_list)
  175. : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
  176. compatibility_list(compatibility_list) {}
  177. GameListWorker::~GameListWorker() = default;
  178. void GameListWorker::AddTitlesToGameList() {
  179. const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
  180. Core::System::GetInstance().GetContentProvider());
  181. const auto installed_games = cache.ListEntriesFilterOrigin(
  182. std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
  183. for (const auto& [slot, game] : installed_games) {
  184. if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
  185. continue;
  186. const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
  187. std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
  188. if (!loader)
  189. continue;
  190. std::vector<u8> icon;
  191. std::string name;
  192. u64 program_id = 0;
  193. loader->ReadProgramId(program_id);
  194. const FileSys::PatchManager patch{program_id};
  195. const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control);
  196. if (control != nullptr)
  197. GetMetadataFromControlNCA(patch, *control, icon, name);
  198. emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
  199. compatibility_list, patch));
  200. }
  201. }
  202. void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
  203. unsigned int recursion) {
  204. const auto callback = [this, target, recursion](u64* num_entries_out,
  205. const std::string& directory,
  206. const std::string& virtual_name) -> bool {
  207. if (stop_processing) {
  208. // Breaks the callback loop.
  209. return false;
  210. }
  211. const std::string physical_name = directory + DIR_SEP + virtual_name;
  212. const bool is_dir = FileUtil::IsDirectory(physical_name);
  213. if (!is_dir &&
  214. (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
  215. const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
  216. auto loader = Loader::GetLoader(file);
  217. if (!loader) {
  218. return true;
  219. }
  220. const auto file_type = loader->GetFileType();
  221. if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) &&
  222. !UISettings::values.show_unknown) {
  223. return true;
  224. }
  225. u64 program_id = 0;
  226. const auto res2 = loader->ReadProgramId(program_id);
  227. if (target == ScanTarget::FillManualContentProvider) {
  228. if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
  229. provider->AddEntry(FileSys::TitleType::Application,
  230. FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
  231. program_id, file);
  232. } else if (res2 == Loader::ResultStatus::Success &&
  233. (file_type == Loader::FileType::XCI ||
  234. file_type == Loader::FileType::NSP)) {
  235. const auto nsp = file_type == Loader::FileType::NSP
  236. ? std::make_shared<FileSys::NSP>(file)
  237. : FileSys::XCI{file}.GetSecurePartitionNSP();
  238. for (const auto& title : nsp->GetNCAs()) {
  239. for (const auto& entry : title.second) {
  240. provider->AddEntry(entry.first.first, entry.first.second, title.first,
  241. entry.second->GetBaseFile());
  242. }
  243. }
  244. }
  245. } else {
  246. std::vector<u8> icon;
  247. const auto res1 = loader->ReadIcon(icon);
  248. std::string name = " ";
  249. const auto res3 = loader->ReadTitle(name);
  250. const FileSys::PatchManager patch{program_id};
  251. emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
  252. compatibility_list, patch));
  253. }
  254. } else if (is_dir && recursion > 0) {
  255. watch_list.append(QString::fromStdString(physical_name));
  256. ScanFileSystem(target, physical_name, recursion - 1);
  257. }
  258. return true;
  259. };
  260. FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
  261. }
  262. void GameListWorker::run() {
  263. stop_processing = false;
  264. watch_list.append(dir_path);
  265. provider->ClearAllEntries();
  266. ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
  267. deep_scan ? 256 : 0);
  268. AddTitlesToGameList();
  269. ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
  270. emit Finished(watch_list);
  271. }
  272. void GameListWorker::Cancel() {
  273. this->disconnect();
  274. stop_processing = true;
  275. }