1 /* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
2 
3 #ifndef MPT_IO_READ_FILEREADER_HPP
4 #define MPT_IO_READ_FILEREADER_HPP
5 
6 
7 
8 #include "mpt/base/alloc.hpp"
9 #include "mpt/base/bit.hpp"
10 #include "mpt/base/integer.hpp"
11 #include "mpt/base/memory.hpp"
12 #include "mpt/base/namespace.hpp"
13 #include "mpt/base/span.hpp"
14 #include "mpt/base/utility.hpp"
15 #include "mpt/io/base.hpp"
16 #include "mpt/endian/floatingpoint.hpp"
17 #include "mpt/endian/integer.hpp"
18 #include "mpt/string/utility.hpp"
19 
20 #include <algorithm>
21 #include <array>
22 #include <limits>
23 #include <string>
24 #include <vector>
25 
26 #include <cassert>
27 #include <cstddef>
28 #include <cstring>
29 
30 
31 
32 namespace mpt {
33 inline namespace MPT_INLINE_NS {
34 
35 
36 
37 namespace IO {
38 
39 
40 
41 namespace FileReader {
42 
43 // Read a "T" object from the stream.
44 // If not enough bytes can be read, false is returned.
45 // If successful, the file cursor is advanced by the size of "T".
46 template <typename T, typename TFileCursor>
Read(TFileCursor & f,T & target)47 bool Read(TFileCursor & f, T & target) {
48 	// cppcheck false-positive
49 	// cppcheck-suppress uninitvar
50 	mpt::byte_span dst = mpt::as_raw_memory(target);
51 	if (dst.size() != f.GetRaw(dst).size()) {
52 		return false;
53 	}
54 	f.Skip(dst.size());
55 	return true;
56 }
57 
58 // Read an array of binary-safe T values.
59 // If successful, the file cursor is advanced by the size of the array.
60 // Otherwise, the target is zeroed.
61 template <typename T, std::size_t destSize, typename TFileCursor>
ReadArray(TFileCursor & f,T (& destArray)[destSize])62 bool ReadArray(TFileCursor & f, T (&destArray)[destSize]) {
63 	static_assert(mpt::is_binary_safe<T>::value);
64 	if (!f.CanRead(sizeof(destArray))) {
65 		mpt::reset(destArray);
66 		return false;
67 	}
68 	f.ReadRaw(mpt::as_raw_memory(destArray));
69 	return true;
70 }
71 
72 // Read an array of binary-safe T values.
73 // If successful, the file cursor is advanced by the size of the array.
74 // Otherwise, the target is zeroed.
75 template <typename T, std::size_t destSize, typename TFileCursor>
ReadArray(TFileCursor & f,std::array<T,destSize> & destArray)76 bool ReadArray(TFileCursor & f, std::array<T, destSize> & destArray) {
77 	static_assert(mpt::is_binary_safe<T>::value);
78 	if (!f.CanRead(sizeof(destArray))) {
79 		destArray.fill(T{});
80 		return false;
81 	}
82 	f.ReadRaw(mpt::as_raw_memory(destArray));
83 	return true;
84 }
85 
86 // Read destSize elements of binary-safe type T into a vector.
87 // If successful, the file cursor is advanced by the size of the vector.
88 // Otherwise, the vector is resized to destSize, but possibly existing contents are not cleared.
89 template <typename T, typename TFileCursor>
ReadVector(TFileCursor & f,std::vector<T> & destVector,size_t destSize)90 bool ReadVector(TFileCursor & f, std::vector<T> & destVector, size_t destSize) {
91 	static_assert(mpt::is_binary_safe<T>::value);
92 	destVector.resize(destSize);
93 	if (!f.CanRead(sizeof(T) * destSize)) {
94 		return false;
95 	}
96 	f.ReadRaw(mpt::as_raw_memory(destVector));
97 	return true;
98 }
99 
100 template <typename T, std::size_t destSize, typename TFileCursor>
ReadArray(TFileCursor & f)101 std::array<T, destSize> ReadArray(TFileCursor & f) {
102 	std::array<T, destSize> destArray;
103 	ReadArray(f, destArray);
104 	return destArray;
105 }
106 
107 // Read some kind of integer in little-endian format.
108 // If successful, the file cursor is advanced by the size of the integer.
109 template <typename T, typename TFileCursor>
110 T ReadIntLE(TFileCursor & f) {
111 	static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
112 	typename mpt::make_le<T>::type target;
113 	if (!FileReader::Read(f, target)) {
114 		return 0;
115 	}
116 	return target;
117 }
118 
119 // Read some kind of integer in big-endian format.
120 // If successful, the file cursor is advanced by the size of the integer.
121 template <typename T, typename TFileCursor>
122 T ReadIntBE(TFileCursor & f) {
123 	static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
124 	typename mpt::make_be<T>::type target;
125 	if (!FileReader::Read(f, target)) {
126 		return 0;
127 	}
128 	return target;
129 }
130 
131 // Read a integer in little-endian format which has some of its higher bytes not stored in file.
132 // If successful, the file cursor is advanced by the given size.
133 template <typename T, typename TFileCursor>
134 T ReadTruncatedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) {
135 	static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
136 	assert(sizeof(T) >= size);
137 	if (size == 0) {
138 		return 0;
139 	}
140 	if (!f.CanRead(size)) {
141 		return 0;
142 	}
143 	uint8 buf[sizeof(T)];
144 	bool negative = false;
145 	for (std::size_t i = 0; i < sizeof(T); ++i) {
146 		uint8 byte = 0;
147 		if (i < size) {
148 			FileReader::Read(f, byte);
149 			negative = std::numeric_limits<T>::is_signed && ((byte & 0x80) != 0x00);
150 		} else {
151 			// sign or zero extend
152 			byte = negative ? 0xff : 0x00;
153 		}
154 		buf[i] = byte;
155 	}
156 	return mpt::bit_cast<typename mpt::make_le<T>::type>(buf);
157 }
158 
159 // Read a supplied-size little endian integer to a fixed size variable.
160 // The data is properly sign-extended when fewer bytes are stored.
161 // If more bytes are stored, higher order bytes are silently ignored.
162 // If successful, the file cursor is advanced by the given size.
163 template <typename T, typename TFileCursor>
164 T ReadSizedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) {
165 	static_assert(std::numeric_limits<T>::is_integer == true, "Target type is a not an integer");
166 	if (size == 0) {
167 		return 0;
168 	}
169 	if (!f.CanRead(size)) {
170 		return 0;
171 	}
172 	if (size < sizeof(T)) {
173 		return FileReader::ReadTruncatedIntLE<T>(f, size);
174 	}
175 	T retval = FileReader::ReadIntLE<T>(f);
176 	f.Skip(size - sizeof(T));
177 	return retval;
178 }
179 
180 // Read unsigned 32-Bit integer in little-endian format.
181 // If successful, the file cursor is advanced by the size of the integer.
182 template <typename TFileCursor>
ReadUint32LE(TFileCursor & f)183 uint32 ReadUint32LE(TFileCursor & f) {
184 	return FileReader::ReadIntLE<uint32>(f);
185 }
186 
187 // Read unsigned 32-Bit integer in big-endian format.
188 // If successful, the file cursor is advanced by the size of the integer.
189 template <typename TFileCursor>
ReadUint32BE(TFileCursor & f)190 uint32 ReadUint32BE(TFileCursor & f) {
191 	return FileReader::ReadIntBE<uint32>(f);
192 }
193 
194 // Read signed 32-Bit integer in little-endian format.
195 // If successful, the file cursor is advanced by the size of the integer.
196 template <typename TFileCursor>
ReadInt32LE(TFileCursor & f)197 int32 ReadInt32LE(TFileCursor & f) {
198 	return FileReader::ReadIntLE<int32>(f);
199 }
200 
201 // Read signed 32-Bit integer in big-endian format.
202 // If successful, the file cursor is advanced by the size of the integer.
203 template <typename TFileCursor>
ReadInt32BE(TFileCursor & f)204 int32 ReadInt32BE(TFileCursor & f) {
205 	return FileReader::ReadIntBE<int32>(f);
206 }
207 
208 // Read unsigned 24-Bit integer in little-endian format.
209 // If successful, the file cursor is advanced by the size of the integer.
210 template <typename TFileCursor>
ReadUint24LE(TFileCursor & f)211 uint32 ReadUint24LE(TFileCursor & f) {
212 	const auto arr = FileReader::ReadArray<uint8, 3>(f);
213 	return arr[0] | (arr[1] << 8) | (arr[2] << 16);
214 }
215 
216 // Read unsigned 24-Bit integer in big-endian format.
217 // If successful, the file cursor is advanced by the size of the integer.
218 template <typename TFileCursor>
ReadUint24BE(TFileCursor & f)219 uint32 ReadUint24BE(TFileCursor & f) {
220 	const auto arr = FileReader::ReadArray<uint8, 3>(f);
221 	return (arr[0] << 16) | (arr[1] << 8) | arr[2];
222 }
223 
224 // Read unsigned 16-Bit integer in little-endian format.
225 // If successful, the file cursor is advanced by the size of the integer.
226 template <typename TFileCursor>
ReadUint16LE(TFileCursor & f)227 uint16 ReadUint16LE(TFileCursor & f) {
228 	return FileReader::ReadIntLE<uint16>(f);
229 }
230 
231 // Read unsigned 16-Bit integer in big-endian format.
232 // If successful, the file cursor is advanced by the size of the integer.
233 template <typename TFileCursor>
ReadUint16BE(TFileCursor & f)234 uint16 ReadUint16BE(TFileCursor & f) {
235 	return FileReader::ReadIntBE<uint16>(f);
236 }
237 
238 // Read signed 16-Bit integer in little-endian format.
239 // If successful, the file cursor is advanced by the size of the integer.
240 template <typename TFileCursor>
ReadInt16LE(TFileCursor & f)241 int16 ReadInt16LE(TFileCursor & f) {
242 	return FileReader::ReadIntLE<int16>(f);
243 }
244 
245 // Read signed 16-Bit integer in big-endian format.
246 // If successful, the file cursor is advanced by the size of the integer.
247 template <typename TFileCursor>
ReadInt16BE(TFileCursor & f)248 int16 ReadInt16BE(TFileCursor & f) {
249 	return FileReader::ReadIntBE<int16>(f);
250 }
251 
252 // Read a single 8bit character.
253 // If successful, the file cursor is advanced by the size of the integer.
254 template <typename TFileCursor>
ReadChar(TFileCursor & f)255 char ReadChar(TFileCursor & f) {
256 	char target;
257 	if (!FileReader::Read(f, target)) {
258 		return 0;
259 	}
260 	return target;
261 }
262 
263 // Read unsigned 8-Bit integer.
264 // If successful, the file cursor is advanced by the size of the integer.
265 template <typename TFileCursor>
ReadUint8(TFileCursor & f)266 uint8 ReadUint8(TFileCursor & f) {
267 	uint8 target;
268 	if (!FileReader::Read(f, target)) {
269 		return 0;
270 	}
271 	return target;
272 }
273 
274 // Read signed 8-Bit integer. If successful, the file cursor is advanced by the size of the integer.
275 template <typename TFileCursor>
ReadInt8(TFileCursor & f)276 int8 ReadInt8(TFileCursor & f) {
277 	int8 target;
278 	if (!FileReader::Read(f, target)) {
279 		return 0;
280 	}
281 	return target;
282 }
283 
284 // Read 32-Bit float in little-endian format.
285 // If successful, the file cursor is advanced by the size of the float.
286 template <typename TFileCursor>
ReadFloatLE(TFileCursor & f)287 float ReadFloatLE(TFileCursor & f) {
288 	IEEE754binary32LE target;
289 	if (!FileReader::Read(f, target)) {
290 		return 0.0f;
291 	}
292 	return target;
293 }
294 
295 // Read 32-Bit float in big-endian format.
296 // If successful, the file cursor is advanced by the size of the float.
297 template <typename TFileCursor>
ReadFloatBE(TFileCursor & f)298 float ReadFloatBE(TFileCursor & f) {
299 	IEEE754binary32BE target;
300 	if (!FileReader::Read(f, target)) {
301 		return 0.0f;
302 	}
303 	return target;
304 }
305 
306 // Read 64-Bit float in little-endian format.
307 // If successful, the file cursor is advanced by the size of the float.
308 template <typename TFileCursor>
ReadDoubleLE(TFileCursor & f)309 double ReadDoubleLE(TFileCursor & f) {
310 	IEEE754binary64LE target;
311 	if (!FileReader::Read(f, target)) {
312 		return 0.0;
313 	}
314 	return target;
315 }
316 
317 // Read 64-Bit float in big-endian format.
318 // If successful, the file cursor is advanced by the size of the float.
319 template <typename TFileCursor>
ReadDoubleBE(TFileCursor & f)320 double ReadDoubleBE(TFileCursor & f) {
321 	IEEE754binary64BE target;
322 	if (!FileReader::Read(f, target)) {
323 		return 0.0;
324 	}
325 	return target;
326 }
327 
328 // Read a struct.
329 // If successful, the file cursor is advanced by the size of the struct. Otherwise, the target is zeroed.
330 template <typename T, typename TFileCursor>
ReadStruct(TFileCursor & f,T & target)331 bool ReadStruct(TFileCursor & f, T & target) {
332 	static_assert(mpt::is_binary_safe<T>::value);
333 	if (!FileReader::Read(f, target)) {
334 		mpt::reset(target);
335 		return false;
336 	}
337 	return true;
338 }
339 
340 // Allow to read a struct partially (if there's less memory available than the struct's size, fill it up with zeros).
341 // The file cursor is advanced by "partialSize" bytes.
342 template <typename T, typename TFileCursor>
ReadStructPartial(TFileCursor & f,T & target,typename TFileCursor::pos_type partialSize=sizeof (T))343 typename TFileCursor::pos_type ReadStructPartial(TFileCursor & f, T & target, typename TFileCursor::pos_type partialSize = sizeof(T)) {
344 	static_assert(mpt::is_binary_safe<T>::value);
345 	typename TFileCursor::pos_type copyBytes = std::min(partialSize, sizeof(T));
346 	if (!f.CanRead(copyBytes)) {
347 		copyBytes = f.BytesLeft();
348 	}
349 	f.GetRaw(mpt::span(mpt::as_raw_memory(target).data(), copyBytes));
350 	std::memset(mpt::as_raw_memory(target).data() + copyBytes, 0, sizeof(target) - copyBytes);
351 	f.Skip(partialSize);
352 	return copyBytes;
353 }
354 
355 // Read a null-terminated string into a std::string
356 template <typename TFileCursor>
ReadNullString(TFileCursor & f,std::string & dest,const typename TFileCursor::pos_type maxLength=std::numeric_limits<typename TFileCursor::pos_type>::max ())357 bool ReadNullString(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits<typename TFileCursor::pos_type>::max()) {
358 	dest.clear();
359 	if (!f.CanRead(1)) {
360 		return false;
361 	}
362 	char buffer[mpt::IO::BUFFERSIZE_MINUSCULE];
363 	typename TFileCursor::pos_type avail = 0;
364 	while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) {
365 		auto end = std::find(buffer, buffer + avail, '\0');
366 		dest.insert(dest.end(), buffer, end);
367 		f.Skip(end - buffer);
368 		if (end < buffer + avail) {
369 			// Found null char
370 			f.Skip(1);
371 			break;
372 		}
373 	}
374 	return dest.length() != 0;
375 }
376 
377 // Read a string up to the next line terminator into a std::string
378 template <typename TFileCursor>
ReadLine(TFileCursor & f,std::string & dest,const typename TFileCursor::pos_type maxLength=std::numeric_limits<typename TFileCursor::pos_type>::max ())379 bool ReadLine(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits<typename TFileCursor::pos_type>::max()) {
380 	dest.clear();
381 	if (!f.CanRead(1)) {
382 		return false;
383 	}
384 	char buffer[mpt::IO::BUFFERSIZE_MINUSCULE];
385 	char c = '\0';
386 	typename TFileCursor::pos_type avail = 0;
387 	while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) {
388 		auto end = std::find_if(buffer, buffer + avail, mpt::is_any_line_ending<char>);
389 		dest.insert(dest.end(), buffer, end);
390 		f.Skip(end - buffer);
391 		if (end < buffer + avail) {
392 			// Found line ending
393 			f.Skip(1);
394 			// Handle CRLF line ending
395 			if (*end == '\r') {
396 				if (FileReader::Read(f, c) && c != '\n') {
397 					f.SkipBack(1);
398 				}
399 			}
400 			break;
401 		}
402 	}
403 	return true;
404 }
405 
406 // Compare a magic string with the current stream position.
407 // Returns true if they are identical and advances the file cursor by the the length of the "magic" string.
408 // Returns false if the string could not be found. The file cursor is not advanced in this case.
409 template <size_t N, typename TFileCursor>
ReadMagic(TFileCursor & f,const char (& magic)[N])410 bool ReadMagic(TFileCursor & f, const char (&magic)[N]) {
411 	assert(magic[N - 1] == '\0');
412 	for (std::size_t i = 0; i < N - 1; ++i) {
413 		assert(magic[i] != '\0');
414 	}
415 	constexpr typename TFileCursor::pos_type magicLength = N - 1;
416 	std::byte buffer[magicLength] = {};
417 	if (f.GetRaw(mpt::span(buffer, magicLength)).size() != magicLength) {
418 		return false;
419 	}
420 	if (std::memcmp(buffer, magic, magicLength)) {
421 		return false;
422 	}
423 	f.Skip(magicLength);
424 	return true;
425 }
426 
427 // Read variable-length unsigned integer (as found in MIDI files).
428 // If successful, the file cursor is advanced by the size of the integer and true is returned.
429 // False is returned if not enough bytes were left to finish reading of the integer or if an overflow happened (source doesn't fit into target integer).
430 // In case of an overflow, the target is also set to the maximum value supported by its data type.
431 template <typename T, typename TFileCursor>
ReadVarInt(TFileCursor & f,T & target)432 bool ReadVarInt(TFileCursor & f, T & target) {
433 	static_assert(std::numeric_limits<T>::is_integer == true && std::numeric_limits<T>::is_signed == false, "Target type is not an unsigned integer");
434 
435 	if (f.NoBytesLeft()) {
436 		target = 0;
437 		return false;
438 	}
439 
440 	std::byte bytes[16]; // More than enough for any valid VarInt
441 	typename TFileCursor::pos_type avail = f.GetRaw(mpt::as_span(bytes)).size();
442 	typename TFileCursor::pos_type readPos = 1;
443 
444 	uint8 b = mpt::byte_cast<uint8>(bytes[0]);
445 	target = (b & 0x7F);
446 	std::size_t writtenBits = static_cast<std::size_t>(mpt::bit_width(target)); // Bits used in the most significant byte
447 
448 	while (readPos < avail && (b & 0x80) != 0) {
449 		b = mpt::byte_cast<uint8>(bytes[readPos++]);
450 		target <<= 7;
451 		target |= (b & 0x7F);
452 		writtenBits += 7;
453 		if (readPos == avail) {
454 			f.Skip(readPos);
455 			avail = f.GetRaw(mpt::as_span(bytes)).size();
456 			readPos = 0;
457 		}
458 	}
459 	f.Skip(readPos);
460 
461 	if (writtenBits > sizeof(target) * 8u) {
462 		// Overflow
463 		target = std::numeric_limits<T>::max();
464 		return false;
465 	} else if ((b & 0x80) != 0) {
466 		// Reached EOF
467 		return false;
468 	}
469 	return true;
470 }
471 
472 template <typename Tid, typename Tsize>
473 struct ChunkHeader {
474 	using id_type = Tid;
475 	using size_type = Tsize;
declare_binary_safe(const ChunkHeader &)476 	friend constexpr bool declare_binary_safe(const ChunkHeader &) noexcept {
477 		return true;
478 	}
479 	id_type id{};
480 	size_type size{};
GetIDmpt::MPT_INLINE_NS::IO::FileReader::ChunkHeader481 	id_type GetID() const {
482 		return id;
483 	}
GetLengthmpt::MPT_INLINE_NS::IO::FileReader::ChunkHeader484 	size_type GetLength() const {
485 		return size;
486 	}
487 };
488 
489 template <typename TChunkHeader, typename TFileCursor>
490 struct Chunk {
491 	TChunkHeader header;
492 	TFileCursor data;
GetHeadermpt::MPT_INLINE_NS::IO::FileReader::Chunk493 	TChunkHeader GetHeader() const {
494 		return header;
495 	}
GetDatampt::MPT_INLINE_NS::IO::FileReader::Chunk496 	TFileCursor GetData() const {
497 		return data;
498 	}
499 };
500 
501 template <typename TChunkHeader, typename TFileCursor>
502 struct ChunkList {
503 
504 	using id_type = decltype(TChunkHeader().GetID());
505 	using size_type = decltype(TChunkHeader().GetLength());
506 
507 	std::vector<Chunk<TChunkHeader, TFileCursor>> chunks;
508 
509 	// Check if the list contains a given chunk.
ChunkExistsmpt::MPT_INLINE_NS::IO::FileReader::ChunkList510 	bool ChunkExists(id_type id) const {
511 		return std::find_if(chunks.begin(), chunks.end(), [id](const Chunk<TChunkHeader, TFileCursor> & chunk) { return chunk.GetHeader().GetID() == id; }) != chunks.end();
512 	}
513 
514 	// Retrieve the first chunk with a given ID.
GetChunkmpt::MPT_INLINE_NS::IO::FileReader::ChunkList515 	TFileCursor GetChunk(id_type id) const {
516 		auto chunk = std::find_if(chunks.begin(), chunks.end(), [id](const Chunk<TChunkHeader, TFileCursor> & chunk) { return chunk.GetHeader().GetID() == id; });
517 		if (chunk == chunks.end()) {
518 			return TFileCursor();
519 		}
520 		return chunk->GetData();
521 	}
522 
523 	// Retrieve all chunks with a given ID.
GetAllChunksmpt::MPT_INLINE_NS::IO::FileReader::ChunkList524 	std::vector<TFileCursor> GetAllChunks(id_type id) const {
525 		std::vector<TFileCursor> result;
526 		for (const auto & chunk : chunks) {
527 			if (chunk.GetHeader().GetID() == id) {
528 				result.push_back(chunk.GetData());
529 			}
530 		}
531 		return result;
532 	}
533 };
534 
535 // Read a single "TChunkHeader" chunk.
536 // T is required to have the methods GetID() and GetLength().
537 // GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
538 template <typename TChunkHeader, typename TFileCursor>
ReadNextChunk(TFileCursor & f,typename TFileCursor::pos_type alignment)539 Chunk<TChunkHeader, TFileCursor> ReadNextChunk(TFileCursor & f, typename TFileCursor::pos_type alignment) {
540 	Chunk<TChunkHeader, TFileCursor> result;
541 	if (!FileReader::Read(f, result.header)) {
542 		return Chunk<TChunkHeader, TFileCursor>();
543 	}
544 	typename TFileCursor::pos_type dataSize = result.header.GetLength();
545 	result.data = f.ReadChunk(dataSize);
546 	if (alignment > 1) {
547 		if ((dataSize % alignment) != 0) {
548 			f.Skip(alignment - (dataSize % alignment));
549 		}
550 	}
551 	return result;
552 }
553 
554 // Read a series of "TChunkHeader" chunks until the end of file is reached.
555 // T is required to have the methods GetID() and GetLength().
556 // GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
557 template <typename TChunkHeader, typename TFileCursor>
ReadChunks(TFileCursor & f,typename TFileCursor::pos_type alignment)558 ChunkList<TChunkHeader, TFileCursor> ReadChunks(TFileCursor & f, typename TFileCursor::pos_type alignment) {
559 	ChunkList<TChunkHeader, TFileCursor> result;
560 	while (f.CanRead(sizeof(TChunkHeader))) {
561 		result.chunks.push_back(FileReader::ReadNextChunk<TChunkHeader, TFileCursor>(f, alignment));
562 	}
563 	return result;
564 }
565 
566 // Read a series of "TChunkHeader" chunks until a given chunk ID is found.
567 // T is required to have the methods GetID() and GetLength().
568 // GetLength() must return the chunk size in bytes, and GetID() the chunk ID.
569 template <typename TChunkHeader, typename TFileCursor>
570 ChunkList<TChunkHeader, TFileCursor> ReadChunksUntil(TFileCursor & f, typename TFileCursor::pos_type alignment, decltype(TChunkHeader().GetID()) lastID) {
571 	ChunkList<TChunkHeader, TFileCursor> result;
572 	while (f.CanRead(sizeof(TChunkHeader))) {
573 		result.chunks.push_back(FileReader::ReadNextChunk<TChunkHeader, TFileCursor>(f, alignment));
574 		if (result.chunks.back().GetHeader().GetID() == lastID) {
575 			break;
576 		}
577 	}
578 	return result;
579 }
580 
581 } // namespace FileReader
582 
583 
584 
585 } // namespace IO
586 
587 
588 
589 } // namespace MPT_INLINE_NS
590 } // namespace mpt
591 
592 
593 
594 #endif // MPT_IO_READ_FILEREADER_HPP
595