Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LegacySequence.cpp
Go to the documentation of this file.
1#include "LegacySequence.hpp"
2
3#include <bit>
4#include <utility>
5
6namespace monopoly::data
7{
8 namespace
9 {
10 std::uint32_t readU32(std::span<const std::byte> bytes,
11 std::size_t offset) noexcept
12 {
13 return
14 std::to_integer<std::uint32_t>(bytes[offset]) |
15 (std::to_integer<std::uint32_t>(bytes[offset + 1]) << 8U) |
16 (std::to_integer<std::uint32_t>(bytes[offset + 2]) << 16U) |
17 (std::to_integer<std::uint32_t>(bytes[offset + 3]) << 24U);
18 }
19
20 std::uint16_t readU16(std::span<const std::byte> bytes,
21 std::size_t offset) noexcept
22 {
23 return static_cast<std::uint16_t>(
24 std::to_integer<std::uint8_t>(bytes[offset]) |
25 (std::to_integer<std::uint8_t>(bytes[offset + 1]) << 8U));
26 }
27
28 std::int16_t readI16(std::span<const std::byte> bytes,
29 std::size_t offset) noexcept
30 {
31 const auto value = static_cast<std::uint16_t>(
32 std::to_integer<std::uint8_t>(bytes[offset]) |
33 (std::to_integer<std::uint8_t>(bytes[offset + 1]) << 8U));
34 return static_cast<std::int16_t>(value);
35 }
36
37 std::int32_t signed24(std::uint32_t word) noexcept
38 {
39 const auto value = static_cast<std::int32_t>(word & 0x00FF'FFFFU);
40 return (word & 0x0080'0000U) != 0 ? value - 0x0100'0000 : value;
41 }
42
43 float readF32(std::span<const std::byte> bytes,
44 std::size_t offset) noexcept
45 {
46 return std::bit_cast<float>(readU32(bytes, offset));
47 }
48
49 // L_Seqncr.h:574-677 et L_Chunk.cpp:551-565. Ce sont des tailles
50 // de payload, sans les quatre octets de LE_CHUNK_HeaderRecord.
51 std::size_t fixedRecordSize(std::uint8_t chunkId) noexcept
52 {
53 switch (chunkId)
54 {
55 case 1: return 12; // Grouping
56 case 2: return 16; // Indirect
57 case 3: return 16; // Bitmap
58 case 4: return 24; // Model (geometry, texture table, joints)
59 case 5: return 16; // Sound
60 case 6: return 22; // Video: header + ten one-byte playback fields
61 case 7: return 21; // Camera: header + near/far + packed label
62 case 8: return 16; // Preloader: header + preload DataID
63 case 9: return 16; // Mesh
64 case 10: return 13; // Tweeker (common header + interpolation ID)
65 default: return 0;
66 }
67 }
68 }
69
70 std::string_view sequenceErrorCodeName(SequenceErrorCode code) noexcept
71 {
72 switch (code)
73 {
74 case SequenceErrorCode::None: return "None";
75 case SequenceErrorCode::HeaderTruncated: return "HeaderTruncated";
76 case SequenceErrorCode::ChunkFailure: return "ChunkFailure";
77 case SequenceErrorCode::UnsupportedRecord: return "UnsupportedRecord";
78 case SequenceErrorCode::FixedRecordTruncated: return "FixedRecordTruncated";
79 case SequenceErrorCode::AttributeTruncated: return "AttributeTruncated";
80 case SequenceErrorCode::InvalidDimensionality: return "InvalidDimensionality";
81 case SequenceErrorCode::AttributeLimitExceeded: return "AttributeLimitExceeded";
82 case SequenceErrorCode::RecordLimitExceeded: return "RecordLimitExceeded";
83 case SequenceErrorCode::InvalidClockRange: return "InvalidClockRange";
84 }
85 return "InvalidSequenceErrorCode";
86 }
87
88 std::expected<LegacySequenceAttributes, SequenceError>
90 std::size_t maximumAttributes)
91 {
93 auto candidate = reader;
94 while (candidate.remaining() != 0)
95 {
96 const auto part = candidate.descend();
97 if (!part)
99 part.error().offset, "cannot enter sequence attribute", part.error()});
100 if (part->id >= 1 && part->id < 11)
101 {
102 if (!result.firstChildAttributeIndex)
103 result.firstChildAttributeIndex = result.values.size();
104 // Only siblings describe this sequence. Never read the child's
105 // fixed record or attributes into its parent's initial values.
106 (void)candidate.ascend();
107 continue;
108 }
109 if (result.values.size() >= maximumAttributes)
111 part->headerOffset, "sequence attribute count exceeds configured limit", {}});
112
113 // FILE_NAME_1..5 are variable-length external filenames.
114 // ArtLib accepts all five and keeps the last one encountered.
115 if (part->id >= 20 && part->id <= 24)
116 {
117 if (part->dataSize > 4096)
118 return std::unexpected(SequenceError{
120 part->dataOffset,
121 "sequence external filename exceeds 4096 bytes", {}});
122 const auto bytes = candidate.map(part->dataSize);
123 if (!bytes || bytes->size() != part->dataSize)
124 return std::unexpected(SequenceError{
126 part->dataOffset,
127 "cannot map sequence external filename",
128 bytes ? std::optional<ChunkError>{}
129 : std::optional<ChunkError>{bytes.error()}});
130 std::string fileName;
131 fileName.reserve(bytes->size());
132 for (const auto byte : *bytes)
133 {
134 const auto value = std::to_integer<std::uint8_t>(byte);
135 if (value == 0) break;
136 fileName.push_back(static_cast<char>(value));
137 }
138 result.values.push_back(
140 *part,
141 static_cast<std::uint8_t>(part->id - 19),
142 std::move(fileName)});
143 (void)candidate.ascend();
144 continue;
145 }
146
147 std::size_t required{};
148 switch (part->id)
149 {
150 case 129: required = 1; break;
151 case 130: required = 8; break;
152 case 131: required = 36; break;
153 case 132: required = 28; break;
154 case 133: required = 12; break;
155 case 134: required = 64; break;
156 case 135: required = 48; break;
157 case 136: required = 16; break;
158 case 137: required = 96; break;
159 case 138: required = 4; break;
160 case 139: required = 8; break;
161 case 140: required = 1; break;
162 case 141: required = 2; break;
163 case 142: required = 1; break;
164 case 143: required = 1; break;
165 case 144: required = 4; break;
166 default:
167 result.values.push_back(SequenceUnsupportedAttribute{*part});
168 (void)candidate.ascend();
169 continue;
170 }
171 if (part->dataSize < required)
173 part->dataOffset, "private sequence attribute is shorter than its fixed record", {}});
174 const auto bytes = candidate.map(required);
175 if (!bytes || bytes->size() != required)
176 return std::unexpected(SequenceError{SequenceErrorCode::ChunkFailure,
177 part->dataOffset, "cannot map private sequence attribute", bytes ?
178 std::optional<ChunkError>{} : std::optional<ChunkError>{bytes.error()}});
179
180 switch (part->id)
181 {
182 case 129:
183 {
184 const auto value = std::to_integer<std::uint8_t>((*bytes)[0]);
185 if (value == 1 || value > 3)
187 part->dataOffset, "sequence dimensionality must be 0, 2 or 3", {}});
189 break;
190 }
191 case 130:
192 result.values.push_back(Sequence2DOffsetAttribute{*part,
193 static_cast<std::int32_t>(readU32(*bytes, 0)),
194 static_cast<std::int32_t>(readU32(*bytes, 4))});
195 break;
196 case 131:
197 {
199 for (std::size_t index = 0; index < value.values.size(); ++index)
201 result.values.push_back(value);
202 break;
203 }
204 case 132:
206 static_cast<std::int32_t>(readU32(*bytes, 0)),
207 static_cast<std::int32_t>(readU32(*bytes, 4)),
208 static_cast<std::int32_t>(readU32(*bytes, 8)),
209 static_cast<std::int32_t>(readU32(*bytes, 12)),
210 readF32(*bytes, 16), readF32(*bytes, 20), readF32(*bytes, 24)});
211 break;
212 case 133:
213 result.values.push_back(Sequence3DOffsetAttribute{*part,
214 readF32(*bytes, 0), readF32(*bytes, 4), readF32(*bytes, 8)});
215 break;
216 case 134:
217 {
219 for (std::size_t index = 0; index < value.values.size(); ++index)
221 result.values.push_back(value);
222 break;
223 }
224 case 135:
226 readF32(*bytes, 0), readF32(*bytes, 4), readF32(*bytes, 8),
227 readF32(*bytes, 12), readF32(*bytes, 16), readF32(*bytes, 20),
228 readF32(*bytes, 24), readF32(*bytes, 28), readF32(*bytes, 32),
229 readF32(*bytes, 36), readF32(*bytes, 40), readF32(*bytes, 44)});
230 break;
231 case 136:
233 static_cast<std::int32_t>(readU32(*bytes, 0)),
234 static_cast<std::int32_t>(readU32(*bytes, 4)),
235 static_cast<std::int32_t>(readU32(*bytes, 8)),
236 static_cast<std::int32_t>(readU32(*bytes, 12))});
237 break;
238 case 137:
239 {
241 for (std::size_t point = 0; point < value.points.size(); ++point)
242 for (std::size_t axis = 0; axis < value.points[point].size(); ++axis)
243 value.points[point][axis] =
244 readF32(*bytes, (point * 3U + axis) * 4U);
245 result.values.push_back(value);
246 break;
247 }
248 case 138:
250 *part, readF32(*bytes, 0)});
251 break;
252 case 139:
253 result.values.push_back(Sequence3DMeshChoiceAttribute{*part,
254 readI16(*bytes, 0), readI16(*bytes, 2), readF32(*bytes, 4)});
255 break;
256 case 140:
257 result.values.push_back(SequenceLabelAttribute{
258 *part, std::to_integer<std::uint8_t>((*bytes)[0])});
259 break;
260 case 141:
261 result.values.push_back(SequenceSoundPitchAttribute{
262 *part, readU16(*bytes, 0)});
263 break;
264 case 142:
265 result.values.push_back(SequenceSoundVolumeAttribute{
266 *part, std::to_integer<std::uint8_t>((*bytes)[0])});
267 break;
268 case 143:
269 result.values.push_back(SequenceSoundPanningAttribute{
270 *part, static_cast<std::int8_t>(
271 std::to_integer<std::uint8_t>((*bytes)[0]))});
272 break;
273 case 144:
275 readF32(*bytes, 0)});
276 break;
277 }
278 (void)candidate.ascend();
279 }
280 return result;
281 }
282 std::expected<LegacySequenceHeader, SequenceError>
283 decodeLegacySequenceHeader(std::span<const std::byte> bytes)
284 {
285 if (bytes.size() < LegacySequenceHeaderSize)
286 {
287 return std::unexpected(SequenceError{
289 "sequence common header requires three little-endian 32-bit words",
290 std::nullopt });
291 }
292
293 // Win32 layout explicitly documented as twelve bytes in L_Seqncr.h.
294 const auto first = readU32(bytes, 0);
295 const auto second = readU32(bytes, 4);
296 const auto third = readU32(bytes, 8);
299 static_cast<std::uint8_t>(first >> 24U),
300 signed24(second),
301 static_cast<std::uint8_t>((second >> 24U) & 0x3FU),
302 (second & 0x4000'0000U) != 0,
303 (second & 0x8000'0000U) != 0,
304 static_cast<std::uint8_t>(third & 0x07U),
305 (third & 0x08U) != 0,
306 (third & 0x10U) != 0,
307 third >> 5U
308 };
309 }
310
311 std::expected<LegacySequenceRecord, SequenceError>
313 {
314 // Copier le curseur conserve egalement sa lease SharedDataBytes.
315 // La publication n'a lieu qu'apres lecture de toute la partie fixe.
316 auto candidate = reader;
317 const auto chunk = candidate.descend();
318 if (!chunk)
319 {
320 return std::unexpected(SequenceError{
322 "cannot enter the next sequence chunk", chunk.error() });
323 }
324
325 const auto fixedSize = fixedRecordSize(chunk->id);
326 if (fixedSize == 0)
327 {
328 return std::unexpected(SequenceError{
329 SequenceErrorCode::UnsupportedRecord, chunk->headerOffset,
330 "sequence fixed record decoder does not support chunk ID " +
331 std::to_string(chunk->id), std::nullopt });
332 }
333 if (chunk->dataSize < fixedSize)
334 {
335 return std::unexpected(SequenceError{
337 "chunk payload is shorter than its sequence fixed record",
338 std::nullopt });
339 }
340
341 const auto mapped = candidate.map(fixedSize);
342 if (!mapped)
343 {
344 return std::unexpected(SequenceError{
346 "cannot map the sequence fixed record", mapped.error() });
347 }
349 if (!header)
350 {
351 auto error = header.error();
352 error.offset += chunk->dataOffset;
353 return std::unexpected(std::move(error));
354 }
355
356 LegacySequenceRecord record{ *chunk, *header,
357 SequenceGroupingData{}, candidate.currentOffset() };
358 switch (chunk->id)
359 {
360 case 1: break;
361 case 2:
362 record.data = SequenceIndirectData{ readU32(*mapped, 12) };
363 break;
364 case 3:
365 record.data = SequenceBitmapData{ readU32(*mapped, 12) };
366 break;
367 case 4:
368 record.data = SequenceModelData{ readU32(*mapped, 12),
369 readU32(*mapped, 16), readU32(*mapped, 20) };
370 break;
371 case 5:
372 record.data = SequenceSoundData{ readU32(*mapped, 12) };
373 break;
374 case 6:
375 record.data = SequenceVideoData{
376 std::to_integer<std::uint8_t>((*mapped)[12]) != 0,
377 std::to_integer<std::uint8_t>((*mapped)[13]) != 0,
378 std::to_integer<std::uint8_t>((*mapped)[14]),
379 std::to_integer<std::uint8_t>((*mapped)[15]) != 0,
380 std::to_integer<std::uint8_t>((*mapped)[16]) != 0,
381 std::to_integer<std::uint8_t>((*mapped)[17]) != 0,
382 std::to_integer<std::uint8_t>((*mapped)[18]) != 0,
383 static_cast<std::int8_t>(
384 std::to_integer<std::uint8_t>((*mapped)[19])),
385 static_cast<std::int8_t>(
386 std::to_integer<std::uint8_t>((*mapped)[20])),
387 static_cast<std::int8_t>(
388 std::to_integer<std::uint8_t>((*mapped)[21]))};
389 break;
390 case 7:
391 record.data = SequenceCameraData{readF32(*mapped, 12),
392 readF32(*mapped, 16), std::to_integer<std::uint8_t>((*mapped)[20])};
393 break;
394 case 8:
395 record.data = SequencePreloaderData{readU32(*mapped, 12)};
396 break;
397 case 9:
398 record.data = SequenceMeshData{ readU32(*mapped, 12) };
399 break;
400 case 10:
401 record.data = SequenceTweekerData{
402 std::to_integer<std::uint8_t>((*mapped)[12]) };
403 break;
404 }
405 reader = std::move(candidate);
406 return record;
407 }
408}
std::size_t offset
const char * header
std::vector< std::byte > bytes
unsigned index
data::DataTag first
std::string error
std::uint16_t value
Definition Timers.cpp:16
std::expected< ChunkInfo, ChunkError > descend(std::uint8_t findId=NullStandardId)
std::expected< LegacySequenceAttributes, SequenceError > readLegacySequenceAttributes(const LegacyChunkReader &reader, std::size_t maximumAttributes)
std::expected< LegacySequenceHeader, SequenceError > decodeLegacySequenceHeader(std::span< const std::byte > bytes)
constexpr std::size_t LegacySequenceHeaderSize
std::expected< LegacySequenceRecord, SequenceError > readLegacySequenceRecord(LegacyChunkReader &reader)
std::string_view sequenceErrorCodeName(SequenceErrorCode code) noexcept
std::optional< std::size_t > firstChildAttributeIndex
std::vector< LegacySequenceAttribute > values
std::array< std::array< float, 3 >, 8 > points