Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LooseDataOverrides.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <charconv>
5#include <fstream>
6#include <limits>
7#include <string>
8#include <string_view>
9#include <unordered_set>
10#include <utility>
11
12namespace monopoly::data
13{
14 namespace
15 {
16 using namespace std::string_view_literals;
17
18 constexpr std::uint64_t MaximumPayloadBytes =
19 512ULL * 1024ULL * 1024ULL;
20
21 [[nodiscard]] DataError manifestError(
22 DataErrorCode code,
23 const std::filesystem::path& path,
24 std::size_t line,
25 std::string detail,
26 std::optional<DataTag> tag = std::nullopt)
27 {
28 if (line != 0)
29 detail = "line " + std::to_string(line) + ": " + detail;
30 return {code, path, tag, std::move(detail)};
31 }
32
33 [[nodiscard]] std::optional<std::array<std::string_view, 4>>
34 splitFields(std::string_view line)
35 {
36 std::array<std::string_view, 4> result{};
37 for (std::size_t index = 0; index < result.size(); ++index)
38 {
39 const auto separator = line.find('\t');
40 if (index + 1 == result.size())
41 {
42 if (separator != std::string_view::npos) return std::nullopt;
43 result[index] = line;
44 return result;
45 }
46 if (separator == std::string_view::npos) return std::nullopt;
47 result[index] = line.substr(0, separator);
48 line.remove_prefix(separator + 1);
49 }
50 return result;
51 }
52
53 [[nodiscard]] std::optional<std::uint16_t>
54 parseU16(std::string_view value)
55 {
56 unsigned parsed{};
57 int base = 10;
58 if (value.starts_with("0x") || value.starts_with("0X"))
59 {
60 value.remove_prefix(2);
61 base = 16;
62 }
63 if (value.empty()) return std::nullopt;
64 const auto converted = std::from_chars(
65 value.data(), value.data() + value.size(), parsed, base);
66 if (converted.ec != std::errc{} ||
67 converted.ptr != value.data() + value.size() ||
68 parsed > std::numeric_limits<std::uint16_t>::max())
69 return std::nullopt;
70 return static_cast<std::uint16_t>(parsed);
71 }
72
73 [[nodiscard]] std::optional<LegacyDataType>
74 parseType(std::string_view name)
75 {
76 constexpr std::array types{
77 std::pair{"Bitmap"sv, LegacyDataType::Bitmap},
78 std::pair{"DopeTable"sv, LegacyDataType::DopeTable},
79 std::pair{"Uap"sv, LegacyDataType::Uap},
80 std::pair{"Native"sv, LegacyDataType::Native},
81 std::pair{"GbmTexture"sv, LegacyDataType::GbmTexture},
82 std::pair{"GbmPicture"sv, LegacyDataType::GbmPicture},
83 std::pair{"GenericBitmap"sv, LegacyDataType::GenericBitmap},
84 std::pair{"Wave"sv, LegacyDataType::Wave},
85 std::pair{"String"sv, LegacyDataType::String},
86 std::pair{"UserCreated1"sv, LegacyDataType::UserCreated1},
87 std::pair{"IndexTable"sv, LegacyDataType::IndexTable},
88 std::pair{"Chunky"sv, LegacyDataType::Chunky},
89 std::pair{"TextureArray"sv, LegacyDataType::TextureArray},
90 std::pair{"Model3D"sv, LegacyDataType::Model3D},
91 std::pair{"Pose3D"sv, LegacyDataType::Pose3D},
92 std::pair{"Hmd"sv, LegacyDataType::Hmd},
93 std::pair{"MeshX"sv, LegacyDataType::MeshX}
94 };
95 for (const auto& [text, type] : types)
96 if (name == text) return type;
97 return std::nullopt;
98 }
99
101 const std::filesystem::path& path)
102 {
103 if (path.empty() || path.is_absolute() || path.has_root_path())
104 return false;
105 for (const auto& component : path)
106 if (component == "..") return false;
107 return true;
108 }
109
110 [[nodiscard]] std::filesystem::path utf8Path(
111 std::string_view text)
112 {
113 std::u8string value;
114 value.reserve(text.size());
115 for (const unsigned char character : text)
117 return std::filesystem::path(std::move(value));
118 }
119
120 [[nodiscard]] std::expected<SharedDataBytes, DataError>
122 const std::filesystem::path& path,
123 std::optional<DataTag> tag)
124 {
125 std::ifstream input(path, std::ios::binary | std::ios::ate);
126 if (!input)
127 return std::unexpected(DataError{
129 "loose DATA payload cannot be opened"});
130
131 const auto end = input.tellg();
132 if (end <= 0 ||
133 static_cast<std::uint64_t>(end) > MaximumPayloadBytes)
134 return std::unexpected(DataError{
136 "loose DATA payload must be 1..512 MiB"});
137
138 auto bytes = std::make_shared<DataBytes>(
139 static_cast<std::size_t>(end));
140 input.seekg(0, std::ios::beg);
141 input.read(
142 reinterpret_cast<char*>(bytes->data()),
143 static_cast<std::streamsize>(bytes->size()));
144 if (!input)
145 return std::unexpected(DataError{
147 "loose DATA payload could not be read completely"});
148 return std::const_pointer_cast<const DataBytes>(bytes);
149 }
150 }
151
152
153 std::expected<std::vector<DataSourceOverride>, DataError>
154 loadLooseDataOverrides(const std::filesystem::path& manifestPath)
155 {
156 std::ifstream manifest(manifestPath);
157 if (!manifest)
158 return std::unexpected(manifestError(
161 0,
162 "loose DATA manifest cannot be opened"));
163
164 std::vector<DataSourceOverride> result;
165 std::unordered_set<DataId> seen;
166 std::string line;
167 std::size_t lineNumber{};
168 while (std::getline(manifest, line))
169 {
170 ++lineNumber;
171 if (!line.empty() && line.back() == '\r') line.pop_back();
172 if (line.empty() || line.starts_with('#')) continue;
173 if (line == "group\ttag\ttype\tpath") continue;
174
175 const auto fields = splitFields(line);
176 if (!fields)
177 return std::unexpected(manifestError(
181 "expected four tab-separated columns: group, tag, type, path"));
182
183 const auto group = parseU16((*fields)[0]);
184 const auto tag = parseU16((*fields)[1]);
185 if (!group || *group == 0 || !tag)
186 return std::unexpected(manifestError(
190 "group and tag must be unsigned 16-bit integers; group cannot be zero"));
191
192 const auto type = parseType((*fields)[2]);
193 if (!type)
194 return std::unexpected(manifestError(
198 "unknown LegacyDataType name",
199 static_cast<DataTag>(*tag)));
200
201 const auto relative = utf8Path((*fields)[3]);
203 return std::unexpected(manifestError(
207 "payload path must stay relative to the manifest directory",
208 static_cast<DataTag>(*tag)));
209
210 const auto id = packDataId(
211 *group,
212 static_cast<DataTag>(*tag));
213 if (!seen.emplace(id).second)
214 return std::unexpected(manifestError(
218 "logical DATA id is declared more than once",
219 static_cast<DataTag>(*tag)));
220
221 auto payload = readPayload(
222 manifestPath.parent_path() / relative,
223 static_cast<DataTag>(*tag));
224 if (!payload)
225 return std::unexpected(payload.error());
226
227 result.push_back(DataSourceOverride{
228 id,
229 *type,
230 std::move(*payload)
231 });
232 }
233
234 if (!manifest.eof())
235 return std::unexpected(manifestError(
239 "loose DATA manifest read failed"));
240
241 return result;
242 }
243}
const char * name
fs::path relative
std::uint16_t group
std::string_view text
std::vector< std::byte > bytes
data::DataId id
unsigned index
std::string path
Bytes input
std::uint16_t value
Definition Timers.cpp:16
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< std::vector< DataSourceOverride >, DataError > loadLooseDataOverrides(const std::filesystem::path &manifestPath)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39