mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-04-20 19:44:30 -04:00
fix: NTFS Directory Scanning w/ Linux
Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
@@ -349,7 +349,19 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const
|
|||||||
|
|
||||||
bool HasSupportedFileExtension(const std::string& file_name) {
|
bool HasSupportedFileExtension(const std::string& file_name) {
|
||||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
const QString suffix = file.suffix().toLower();
|
||||||
|
|
||||||
|
// 1. Check if it's a standard game container (.nsp, .xci, etc.)
|
||||||
|
if (GameList::supported_file_extensions.contains(suffix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Only allow .nca if the user explicitly enabled it in the UI Settings
|
||||||
|
if (suffix == QStringLiteral("nca") && UISettings::values.scan_nca.GetValue()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsExtractedNCAMain(const std::string& file_name) {
|
bool IsExtractedNCAMain(const std::string& file_name) {
|
||||||
@@ -553,127 +565,98 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir, const std::map
|
|||||||
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
|
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
|
||||||
GameListDir* parent_dir, const std::map<u64, std::pair<int, int>>& online_stats) {
|
GameListDir* parent_dir, const std::map<u64, std::pair<int, int>>& online_stats) {
|
||||||
const auto callback = [this, target, parent_dir, &online_stats](const std::filesystem::path& path) -> bool {
|
const auto callback = [this, target, parent_dir, &online_stats](const std::filesystem::path& path) -> bool {
|
||||||
if (stop_requested) {
|
if (stop_requested) return false;
|
||||||
// Breaks the callback loop.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto physical_name = Common::FS::PathToUTF8String(path);
|
const auto physical_name = Common::FS::PathToUTF8String(path);
|
||||||
const auto is_dir = Common::FS::IsDir(path);
|
|
||||||
|
|
||||||
if (!is_dir &&
|
// Do not touch the disk if the extension isn't a game
|
||||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
if (!HasSupportedFileExtension(physical_name) && !IsExtractedNCAMain(physical_name)) {
|
||||||
// Try to get cached metadata first
|
return true;
|
||||||
const auto* cached_metadata = GetCachedGameMetadata(physical_name);
|
|
||||||
|
|
||||||
const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
|
|
||||||
if (!file) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto loader = Loader::GetLoader(system, file);
|
|
||||||
if (!loader) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto file_type = loader->GetFileType();
|
|
||||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 program_id = 0;
|
|
||||||
const auto res2 = loader->ReadProgramId(program_id);
|
|
||||||
|
|
||||||
if (target == ScanTarget::FillManualContentProvider) {
|
|
||||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
|
||||||
provider->AddEntry(FileSys::TitleType::Application,
|
|
||||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
|
||||||
program_id, file);
|
|
||||||
} else if (res2 == Loader::ResultStatus::Success &&
|
|
||||||
(file_type == Loader::FileType::XCI ||
|
|
||||||
file_type == Loader::FileType::NSP)) {
|
|
||||||
const auto nsp = file_type == Loader::FileType::NSP
|
|
||||||
? std::make_shared<FileSys::NSP>(file)
|
|
||||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
|
||||||
for (const auto& title : nsp->GetNCAs()) {
|
|
||||||
for (const auto& entry : title.second) {
|
|
||||||
provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
|
||||||
entry.second->GetBaseFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::vector<u64> program_ids;
|
|
||||||
loader->ReadProgramIds(program_ids);
|
|
||||||
|
|
||||||
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 &&
|
|
||||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
|
||||||
for (const auto id : program_ids) {
|
|
||||||
loader = Loader::GetLoader(system, file, id);
|
|
||||||
if (!loader) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<u8> icon;
|
|
||||||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
|
|
||||||
|
|
||||||
std::string name = " ";
|
|
||||||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
|
|
||||||
|
|
||||||
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
|
|
||||||
system.GetContentProvider()};
|
|
||||||
|
|
||||||
auto entry = MakeGameListEntry(
|
|
||||||
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
|
|
||||||
id, compatibility_list, play_time_manager, patch, online_stats);
|
|
||||||
|
|
||||||
RecordEvent(
|
|
||||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use cached metadata if available, otherwise read from file
|
|
||||||
std::vector<u8> icon;
|
|
||||||
std::string name = " ";
|
|
||||||
std::size_t file_size = 0;
|
|
||||||
|
|
||||||
if (cached_metadata && cached_metadata->program_id == program_id) {
|
|
||||||
// Use cached data
|
|
||||||
icon = cached_metadata->icon;
|
|
||||||
name = cached_metadata->title;
|
|
||||||
file_size = cached_metadata->file_size;
|
|
||||||
} else {
|
|
||||||
// Read from file
|
|
||||||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
|
|
||||||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
|
|
||||||
file_size = Common::FS::GetSize(physical_name);
|
|
||||||
|
|
||||||
// Cache it for next time
|
|
||||||
if (res2 == Loader::ResultStatus::Success) {
|
|
||||||
CacheGameMetadata(physical_name, program_id, file_type, file_size, name, icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
|
|
||||||
system.GetContentProvider()};
|
|
||||||
|
|
||||||
auto entry = MakeGameListEntry(
|
|
||||||
physical_name, name, file_size, icon, *loader,
|
|
||||||
program_id, compatibility_list, play_time_manager, patch, online_stats);
|
|
||||||
|
|
||||||
RecordEvent(
|
|
||||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (is_dir) {
|
|
||||||
watch_list.append(QString::fromStdString(physical_name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only now do we do "Heavy" disk I/O
|
||||||
|
const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
|
||||||
|
if (!file) return true;
|
||||||
|
|
||||||
|
auto loader = Loader::GetLoader(system, file);
|
||||||
|
if (!loader) return true;
|
||||||
|
|
||||||
|
const auto file_type = loader->GetFileType();
|
||||||
|
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 program_id = 0;
|
||||||
|
const auto res2 = loader->ReadProgramId(program_id);
|
||||||
|
|
||||||
|
// Handle Content Provider AND Game List in one pass
|
||||||
|
if (target == ScanTarget::FillManualContentProvider || target == ScanTarget::Both) {
|
||||||
|
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||||
|
provider->AddEntry(FileSys::TitleType::Application,
|
||||||
|
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||||
|
program_id, file);
|
||||||
|
} else if (res2 == Loader::ResultStatus::Success &&
|
||||||
|
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||||
|
const auto nsp = file_type == Loader::FileType::NSP
|
||||||
|
? std::make_shared<FileSys::NSP>(file)
|
||||||
|
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||||
|
for (const auto& title : nsp->GetNCAs()) {
|
||||||
|
for (const auto& entry : title.second) {
|
||||||
|
provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||||
|
entry.second->GetBaseFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == ScanTarget::PopulateGameList || target == ScanTarget::Both) {
|
||||||
|
std::vector<u64> program_ids;
|
||||||
|
loader->ReadProgramIds(program_ids);
|
||||||
|
|
||||||
|
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 &&
|
||||||
|
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||||
|
for (const auto id : program_ids) {
|
||||||
|
auto sub_loader = Loader::GetLoader(system, file, id);
|
||||||
|
if (!sub_loader) continue;
|
||||||
|
|
||||||
|
std::vector<u8> icon;
|
||||||
|
sub_loader->ReadIcon(icon);
|
||||||
|
std::string name = " ";
|
||||||
|
sub_loader->ReadTitle(name);
|
||||||
|
|
||||||
|
const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||||
|
auto entry = MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), icon, *sub_loader, id, compatibility_list, play_time_manager, patch, online_stats);
|
||||||
|
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const auto* cached_metadata = GetCachedGameMetadata(physical_name);
|
||||||
|
std::vector<u8> icon;
|
||||||
|
std::string name = " ";
|
||||||
|
std::size_t file_size = 0;
|
||||||
|
|
||||||
|
if (cached_metadata && cached_metadata->program_id == program_id) {
|
||||||
|
icon = cached_metadata->icon;
|
||||||
|
name = cached_metadata->title;
|
||||||
|
file_size = cached_metadata->file_size;
|
||||||
|
} else {
|
||||||
|
loader->ReadIcon(icon);
|
||||||
|
loader->ReadTitle(name);
|
||||||
|
file_size = Common::FS::GetSize(physical_name);
|
||||||
|
if (res2 == Loader::ResultStatus::Success) {
|
||||||
|
CacheGameMetadata(physical_name, program_id, file_type, file_size, name, icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||||
|
auto entry = MakeGameListEntry(physical_name, name, file_size, icon, *loader, program_id, compatibility_list, play_time_manager, patch, online_stats);
|
||||||
|
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deep_scan) {
|
if (deep_scan) {
|
||||||
Common::FS::IterateDirEntriesRecursively(dir_path, callback,
|
Common::FS::IterateDirEntriesRecursively(dir_path, callback, Common::FS::DirEntryFilter::File);
|
||||||
Common::FS::DirEntryFilter::All);
|
|
||||||
} else {
|
} else {
|
||||||
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File);
|
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File);
|
||||||
}
|
}
|
||||||
@@ -703,30 +686,20 @@ void GameListWorker::run() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (UISettings::GameDir& game_dir : game_dirs) {
|
for (UISettings::GameDir& game_dir : game_dirs) {
|
||||||
if (stop_requested) {
|
if (stop_requested) break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game_dir.path == std::string("SDMC")) {
|
if (game_dir.path == std::string("SDMC") || game_dir.path == std::string("UserNAND") || game_dir.path == std::string("SysNAND")) {
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
|
auto type = (game_dir.path == "SDMC") ? GameListItemType::SdmcDir : (game_dir.path == "UserNAND" ? GameListItemType::UserNandDir : GameListItemType::SysNandDir);
|
||||||
DirEntryReady(game_list_dir);
|
auto* const game_list_dir = new GameListDir(game_dir, type);
|
||||||
AddTitlesToGameList(game_list_dir, online_stats);
|
|
||||||
} else if (game_dir.path == std::string("UserNAND")) {
|
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
|
|
||||||
DirEntryReady(game_list_dir);
|
|
||||||
AddTitlesToGameList(game_list_dir, online_stats);
|
|
||||||
} else if (game_dir.path == std::string("SysNAND")) {
|
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
|
|
||||||
DirEntryReady(game_list_dir);
|
DirEntryReady(game_list_dir);
|
||||||
AddTitlesToGameList(game_list_dir, online_stats);
|
AddTitlesToGameList(game_list_dir, online_stats);
|
||||||
} else {
|
} else {
|
||||||
watch_list.append(QString::fromStdString(game_dir.path));
|
watch_list.append(QString::fromStdString(game_dir.path));
|
||||||
auto* const game_list_dir = new GameListDir(game_dir);
|
auto* const game_list_dir = new GameListDir(game_dir);
|
||||||
DirEntryReady(game_list_dir);
|
DirEntryReady(game_list_dir);
|
||||||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
|
|
||||||
game_list_dir, online_stats);
|
// ONE PASS SCAN: Replaces the two separate ScanFileSystem calls
|
||||||
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
|
ScanFileSystem(ScanTarget::Both, game_dir.path, game_dir.deep_scan, game_list_dir, online_stats);
|
||||||
game_list_dir, online_stats);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user