Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LegacyDataArchiveBuilder.cpp
Go to the documentation of this file.
2
3#include <zlib.h>
4
5#include <algorithm>
6#include <fstream>
7#include <limits>
8#include <utility>
9
10namespace monopoly::data
11{
12 namespace
13 {
14 void appendU16Le(DataBytes& bytes, std::uint16_t value)
15 {
16 bytes.push_back(static_cast<std::byte>(value & 0xFFU));
17 bytes.push_back(static_cast<std::byte>((value >> 8U) & 0xFFU));
18 }
19
20
21 void appendU32Le(DataBytes& bytes, std::uint32_t value)
22 {
23 bytes.push_back(static_cast<std::byte>(value & 0xFFU));
24 bytes.push_back(static_cast<std::byte>((value >> 8U) & 0xFFU));
25 bytes.push_back(static_cast<std::byte>((value >> 16U) & 0xFFU));
26 bytes.push_back(static_cast<std::byte>((value >> 24U) & 0xFFU));
27 }
28
29
30 void writeU32Le(
32 std::size_t offset,
33 std::uint32_t value)
34 {
35 bytes[offset] = static_cast<std::byte>(value & 0xFFU);
36 bytes[offset + 1] =
37 static_cast<std::byte>((value >> 8U) & 0xFFU);
38 bytes[offset + 2] =
39 static_cast<std::byte>((value >> 16U) & 0xFFU);
40 bytes[offset + 3] =
41 static_cast<std::byte>((value >> 24U) & 0xFFU);
42 }
43
44
45 [[nodiscard]] DataError buildError(std::string detail)
46 {
47 return
48 {
50 {},
51 std::nullopt,
52 std::move(detail)
53 };
54 }
55 }
56
57
58 std::expected<DataBytes, DataError> buildLegacyDataArchive(
59 std::span<const ArchiveBuildItem> items,
60 ArchiveBuildOptions options)
61 {
62 if (items.empty())
63 {
64 return std::unexpected(buildError(
65 "legacy DAT archives require at least one index item"));
66 }
67
68 if (items.size() > LegacyDataArchive::MaximumItemCount)
69 {
70 return std::unexpected(buildError(
71 "archive item count exceeds the 16-bit DataTag space"));
72 }
73
74 if (options.compressionLevel < Z_NO_COMPRESSION ||
76 {
77 return std::unexpected(buildError(
78 "zlib compression level must be between 0 and 9"));
79 }
80
81 struct EncodedItem
82 {
83 LegacyDataType type{};
84 std::uint32_t offset{};
85 std::uint32_t uncompressedSize{};
87 bool empty{};
88 };
89
90 std::vector<EncodedItem> encoded;
91 encoded.reserve(items.size());
92
93 const std::uint64_t dataStart =
96 std::uint64_t nextOffset = dataStart;
97
98 for (const auto& item : items)
99 {
100 if (item.emptySlot())
101 {
102 encoded.push_back(
103 { LegacyDataType::Unknown, 0, 0, {}, true });
104 continue;
105 }
106
107 if (item.type == LegacyDataType::Unknown || item.payload.empty())
108 {
109 return std::unexpected(buildError(
110 "present items require a non-Unknown type and payload"));
111 }
112
113 if (item.payload.size() > static_cast<std::size_t>(
114 std::numeric_limits<std::int32_t>::max()))
115 {
116 return std::unexpected(buildError(
117 "uncompressed item exceeds the signed Win32 size field"));
118 }
119
121 static_cast<uLong>(item.payload.size()));
123
124 const int result = compress2(
125 reinterpret_cast<Bytef*>(compressed.data()),
127 reinterpret_cast<const Bytef*>(item.payload.data()),
128 static_cast<uLong>(item.payload.size()),
129 options.compressionLevel);
130
131 if (result != Z_OK)
132 {
133 return std::unexpected(buildError(
134 "zlib failed while compressing an archive item"));
135 }
136
138
139 if (compressed.size() > static_cast<std::size_t>(
140 std::numeric_limits<std::int32_t>::max()) ||
141 nextOffset > static_cast<std::uint64_t>(
142 std::numeric_limits<std::int32_t>::max()) ||
143 nextOffset + compressed.size() > static_cast<std::uint64_t>(
144 std::numeric_limits<std::int32_t>::max()))
145 {
146 return std::unexpected(buildError(
147 "archive offsets exceed the signed Win32 DAT fields"));
148 }
149
150 encoded.push_back(
151 {
152 item.type,
153 static_cast<std::uint32_t>(nextOffset),
154 static_cast<std::uint32_t>(item.payload.size()),
155 std::move(compressed),
156 false
157 });
158
159 nextOffset += encoded.back().compressed.size();
160 }
161
162 DataBytes archive;
163 archive.reserve(static_cast<std::size_t>(nextOffset));
164
165 for (const char value : { 'A', 'r', 't', 'e', 'c', 'h', '\0', '\0' })
166 {
167 archive.push_back(static_cast<std::byte>(value));
168 }
169
172 appendU16Le(archive, options.patchMajor);
173 appendU16Le(archive, options.patchMinor);
174 appendU16Le(archive, 0);
175 appendU16Le(archive, 0);
176 appendU32Le(archive, static_cast<std::uint32_t>(items.size()));
177 appendU32Le(archive, 0); // CRC rempli apres index et payloads.
178
179 for (const auto& item : encoded)
180 {
181 if (item.empty)
182 {
183 appendU32Le(archive, 0);
184 appendU32Le(archive, 0);
185 appendU32Le(archive, 0);
186 appendU32Le(archive, 0);
187 continue;
188 }
189
190 appendU32Le(archive, item.offset);
191 appendU32Le(archive, item.uncompressedSize);
193 archive,
194 static_cast<std::uint32_t>(item.type));
196 archive,
197 static_cast<std::uint32_t>(item.compressed.size()));
198 }
199
200 for (const auto& item : encoded)
201 {
202 archive.insert(
203 archive.end(),
204 item.compressed.begin(),
205 item.compressed.end());
206 }
207
208 uLong checksum = crc32(0L, Z_NULL, 0);
209 const auto checksumBytes = std::span<const std::byte>(archive).subspan(
211
212 std::size_t offset = 0;
213
214 while (offset < checksumBytes.size())
215 {
216 const auto amount = std::min<std::size_t>(
217 checksumBytes.size() - offset,
218 static_cast<std::size_t>(
219 std::numeric_limits<uInt>::max()));
220 checksum = crc32(
221 checksum,
222 reinterpret_cast<const Bytef*>(
223 checksumBytes.data() + offset),
224 static_cast<uInt>(amount));
225 offset += amount;
226 }
227
229 archive,
230 24,
231 static_cast<std::uint32_t>(checksum));
232 return archive;
233 }
234
235
236 std::expected<void, DataError> writeLegacyDataArchive(
237 const std::filesystem::path& path,
238 std::span<const ArchiveBuildItem> items,
239 ArchiveBuildOptions options)
240 {
241 auto archive = buildLegacyDataArchive(items, options);
242
243 if (!archive)
244 {
245 return std::unexpected(archive.error());
246 }
247
248 std::ofstream output(path, std::ios::binary | std::ios::trunc);
249
250 if (!output)
251 {
252 return std::unexpected(DataError
253 {
255 path,
256 std::nullopt,
257 "unable to create output DAT archive"
258 });
259 }
260
261 output.write(
262 reinterpret_cast<const char*>(archive->data()),
263 static_cast<std::streamsize>(archive->size()));
264
265 if (!output)
266 {
267 return std::unexpected(DataError
268 {
270 path,
271 std::nullopt,
272 "unable to write complete output DAT archive"
273 });
274 }
275
276 return {};
277 }
278}
std::size_t offset
std::vector< std::byte > bytes
unsigned index
std::string path
std::deque< Bytes > output
std::uint16_t value
Definition Timers.cpp:16
static constexpr std::size_t PhysicalHeaderSize
static constexpr std::size_t PhysicalIndexRecordSize
static constexpr std::uint16_t SupportedVersionMajor
static constexpr std::uint16_t SupportedVersionMinor
static constexpr std::size_t MaximumItemCount
std::vector< std::byte > DataBytes
std::expected< void, DataError > writeLegacyDataArchive(const std::filesystem::path &path, std::span< const ArchiveBuildItem > items, ArchiveBuildOptions options)
std::expected< DataBytes, DataError > buildLegacyDataArchive(std::span< const ArchiveBuildItem > items, ArchiveBuildOptions options)