1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <unordered_set>
#include <utility>
#include <vector>
namespace GameListDiscovery {
struct Result {
std::vector<std::filesystem::path> files;
std::vector<std::filesystem::path> directories;
std::size_t skipped_romfs{};
std::size_t errors{};
};
inline bool IsExeFS(const std::filesystem::path& path) {
std::error_code ec;
return std::filesystem::is_regular_file(path / "main", ec) &&
std::filesystem::is_regular_file(path / "main.npdm", ec);
}
template <typename Supported, typename Stopped>
Result Discover(const std::filesystem::path& root, bool deep, Supported supported,
Stopped stopped) {
namespace fs = std::filesystem;
Result result;
std::unordered_set<fs::path> files;
std::unordered_set<fs::path> directories;
const auto add_file = [&](const fs::path& path) {
if (files.insert(path).second) {
result.files.push_back(path);
}
};
const auto watch = [&](const fs::path& path) {
if (directories.insert(path).second) {
result.directories.push_back(path);
}
};
const auto probe = [&](const fs::path& path) {
if (IsExeFS(path)) {
watch(path);
add_file(path / "main");
}
};
const auto probe_layouts = [&](const fs::path& path) {
for (const auto& content : {path, path / "base"}) {
probe(content / "exefs");
const auto updates = content / "updates";
std::error_code ec;
if (!fs::is_directory(updates, ec)) {
continue;
}
watch(updates);
for (fs::directory_iterator it{updates, fs::directory_options::skip_permission_denied,
ec};
!ec && it != fs::directory_iterator{} && !stopped(); it.increment(ec)) {
probe(it->path() / "exefs");
}
}
};
std::vector<std::pair<fs::path, unsigned>> pending{{root, 0}};
while (!pending.empty() && !stopped()) {
auto [directory, depth] = std::move(pending.back());
pending.pop_back();
watch(directory);
if (IsExeFS(directory)) {
add_file(directory / "main");
continue;
}
if (!deep) {
probe_layouts(directory);
}
std::error_code ec;
for (fs::directory_iterator it{directory, fs::directory_options::skip_permission_denied,
ec};
!ec && it != fs::directory_iterator{} && !stopped(); it.increment(ec)) {
const auto& entry = *it;
std::error_code entry_ec;
if (entry.is_directory(entry_ec)) {
if (entry.is_symlink(entry_ec)) {
continue;
}
auto name = entry.path().filename().u8string();
std::transform(name.begin(), name.end(), name.begin(), [](char8_t c) {
return static_cast<char8_t>(std::tolower(static_cast<unsigned char>(c)));
});
if (name == u8"romfs" && IsExeFS(directory / "exefs")) {
++result.skipped_romfs;
continue;
}
if (deep || depth == 0) {
pending.emplace_back(entry.path(), depth + 1);
}
} else if (entry.is_regular_file(entry_ec) && depth == 0 && !deep) {
if (supported(entry.path())) {
add_file(entry.path());
}
} else if (entry.is_regular_file(entry_ec) && deep && supported(entry.path())) {
add_file(entry.path());
}
}
result.errors += ec ? 1 : 0;
}
return result;
}
} // namespace GameListDiscovery
|