1 /* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
2 
3 #ifndef MPT_IO_IO_HPP
4 #define MPT_IO_IO_HPP
5 
6 
7 
8 #include "mpt/base/array.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/endian/integer.hpp"
15 #include "mpt/io/base.hpp"
16 
17 #include <algorithm>
18 #include <limits>
19 #include <string>
20 #include <vector>
21 
22 #include <cassert>
23 #include <cstddef>
24 
25 
26 
27 namespace mpt {
28 inline namespace MPT_INLINE_NS {
29 
30 
31 
32 namespace IO {
33 
34 
35 
36 template <typename Tbyte, typename Tfile>
ReadRaw(Tfile & f,Tbyte * data,std::size_t size)37 inline mpt::byte_span ReadRaw(Tfile & f, Tbyte * data, std::size_t size) {
38 	return mpt::IO::ReadRawImpl(f, mpt::as_span(mpt::byte_cast<std::byte *>(data), size));
39 }
40 
41 template <typename Tbyte, typename Tfile>
ReadRaw(Tfile & f,mpt::span<Tbyte> data)42 inline mpt::byte_span ReadRaw(Tfile & f, mpt::span<Tbyte> data) {
43 	return mpt::IO::ReadRawImpl(f, mpt::byte_cast<mpt::byte_span>(data));
44 }
45 
46 template <typename Tbyte, typename Tfile>
WriteRaw(Tfile & f,const Tbyte * data,std::size_t size)47 inline bool WriteRaw(Tfile & f, const Tbyte * data, std::size_t size) {
48 	return mpt::IO::WriteRawImpl(f, mpt::as_span(mpt::byte_cast<const std::byte *>(data), size));
49 }
50 
51 template <typename Tbyte, typename Tfile>
WriteRaw(Tfile & f,mpt::span<Tbyte> data)52 inline bool WriteRaw(Tfile & f, mpt::span<Tbyte> data) {
53 	return mpt::IO::WriteRawImpl(f, mpt::byte_cast<mpt::const_byte_span>(data));
54 }
55 
56 template <typename Tbinary, typename Tfile>
Read(Tfile & f,Tbinary & v)57 inline bool Read(Tfile & f, Tbinary & v) {
58 	return mpt::IO::ReadRaw(f, mpt::as_raw_memory(v)).size() == mpt::as_raw_memory(v).size();
59 }
60 
61 template <typename Tbinary, typename Tfile>
Write(Tfile & f,const Tbinary & v)62 inline bool Write(Tfile & f, const Tbinary & v) {
63 	return mpt::IO::WriteRaw(f, mpt::as_raw_memory(v));
64 }
65 
66 template <typename Tbinary, typename Tfile>
Write(Tfile & f,const std::vector<Tbinary> & v)67 inline bool Write(Tfile & f, const std::vector<Tbinary> & v) {
68 	static_assert(mpt::is_binary_safe<Tbinary>::value);
69 	return mpt::IO::WriteRaw(f, mpt::as_raw_memory(v));
70 }
71 
72 template <typename T, typename Tfile>
WritePartial(Tfile & f,const T & v,std::size_t size=sizeof (T))73 inline bool WritePartial(Tfile & f, const T & v, std::size_t size = sizeof(T)) {
74 	assert(size <= sizeof(T));
75 	return mpt::IO::WriteRaw(f, mpt::as_span(mpt::as_raw_memory(v).data(), size));
76 }
77 
78 template <typename Tfile>
ReadByte(Tfile & f,std::byte & v)79 inline bool ReadByte(Tfile & f, std::byte & v) {
80 	bool result = false;
81 	std::byte byte = mpt::as_byte(0);
82 	const std::size_t readResult = mpt::IO::ReadRaw(f, &byte, sizeof(std::byte)).size();
83 	result = (readResult == sizeof(std::byte));
84 	v = byte;
85 	return result;
86 }
87 
88 template <typename T, typename Tfile>
ReadBinaryTruncatedLE(Tfile & f,T & v,std::size_t size)89 inline bool ReadBinaryTruncatedLE(Tfile & f, T & v, std::size_t size) {
90 	bool result = false;
91 	static_assert(std::numeric_limits<T>::is_integer);
92 	std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
93 	const std::size_t readResult = mpt::IO::ReadRaw(f, bytes.data(), std::min(size, sizeof(T))).size();
94 	result = (readResult == std::min(size, sizeof(T)));
95 	v = mpt::bit_cast<typename mpt::make_le<T>::type>(bytes);
96 	return result;
97 }
98 
99 template <typename T, typename Tfile>
ReadIntLE(Tfile & f,T & v)100 inline bool ReadIntLE(Tfile & f, T & v) {
101 	bool result = false;
102 	static_assert(std::numeric_limits<T>::is_integer);
103 	std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
104 	const std::size_t readResult = mpt::IO::ReadRaw(f, mpt::as_span(bytes)).size();
105 	result = (readResult == sizeof(T));
106 	v = mpt::bit_cast<typename mpt::make_le<T>::type>(bytes);
107 	return result;
108 }
109 
110 template <typename T, typename Tfile>
ReadIntBE(Tfile & f,T & v)111 inline bool ReadIntBE(Tfile & f, T & v) {
112 	bool result = false;
113 	static_assert(std::numeric_limits<T>::is_integer);
114 	std::array<uint8, sizeof(T)> bytes = mpt::init_array<uint8, sizeof(T)>(uint8{0});
115 	const std::size_t readResult = mpt::IO::ReadRaw(f, mpt::as_span(bytes)).size();
116 	result = (readResult == sizeof(T));
117 	v = mpt::bit_cast<typename mpt::make_be<T>::type>(bytes);
118 	return result;
119 }
120 
121 template <typename Tfile>
ReadAdaptiveInt16LE(Tfile & f,uint16 & v)122 inline bool ReadAdaptiveInt16LE(Tfile & f, uint16 & v) {
123 	bool result = true;
124 	uint8 byte = 0;
125 	std::size_t additionalBytes = 0;
126 	v = 0;
127 	byte = 0;
128 	if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
129 		result = false;
130 	}
131 	additionalBytes = (byte & 0x01);
132 	v = byte >> 1;
133 	for (std::size_t i = 0; i < additionalBytes; ++i) {
134 		byte = 0;
135 		if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
136 			result = false;
137 		}
138 		v |= (static_cast<uint16>(byte) << (((i + 1) * 8) - 1));
139 	}
140 	return result;
141 }
142 
143 template <typename Tfile>
ReadAdaptiveInt32LE(Tfile & f,uint32 & v)144 inline bool ReadAdaptiveInt32LE(Tfile & f, uint32 & v) {
145 	bool result = true;
146 	uint8 byte = 0;
147 	std::size_t additionalBytes = 0;
148 	v = 0;
149 	byte = 0;
150 	if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
151 		result = false;
152 	}
153 	additionalBytes = (byte & 0x03);
154 	v = byte >> 2;
155 	for (std::size_t i = 0; i < additionalBytes; ++i) {
156 		byte = 0;
157 		if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
158 			result = false;
159 		}
160 		v |= (static_cast<uint32>(byte) << (((i + 1) * 8) - 2));
161 	}
162 	return result;
163 }
164 
165 template <typename Tfile>
ReadAdaptiveInt64LE(Tfile & f,uint64 & v)166 inline bool ReadAdaptiveInt64LE(Tfile & f, uint64 & v) {
167 	bool result = true;
168 	uint8 byte = 0;
169 	std::size_t additionalBytes = 0;
170 	v = 0;
171 	byte = 0;
172 	if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
173 		result = false;
174 	}
175 	additionalBytes = (1 << (byte & 0x03)) - 1;
176 	v = byte >> 2;
177 	for (std::size_t i = 0; i < additionalBytes; ++i) {
178 		byte = 0;
179 		if (!mpt::IO::ReadIntLE<uint8>(f, byte)) {
180 			result = false;
181 		}
182 		v |= (static_cast<uint64>(byte) << (((i + 1) * 8) - 2));
183 	}
184 	return result;
185 }
186 
187 template <typename Tsize, typename Tfile>
ReadSizedStringLE(Tfile & f,std::string & str,Tsize maxSize=std::numeric_limits<Tsize>::max ())188 inline bool ReadSizedStringLE(Tfile & f, std::string & str, Tsize maxSize = std::numeric_limits<Tsize>::max()) {
189 	static_assert(std::numeric_limits<Tsize>::is_integer);
190 	str.clear();
191 	Tsize size = 0;
192 	if (!mpt::IO::ReadIntLE(f, size)) {
193 		return false;
194 	}
195 	if (size > maxSize) {
196 		return false;
197 	}
198 	for (Tsize i = 0; i != size; ++i) {
199 		char c = '\0';
200 		if (!mpt::IO::ReadIntLE(f, c)) {
201 			return false;
202 		}
203 		str.push_back(c);
204 	}
205 	return true;
206 }
207 
208 
209 
210 template <typename T, typename Tfile>
WriteIntLE(Tfile & f,const T v)211 inline bool WriteIntLE(Tfile & f, const T v) {
212 	static_assert(std::numeric_limits<T>::is_integer);
213 	return mpt::IO::Write(f, mpt::as_le(v));
214 }
215 
216 template <typename T, typename Tfile>
WriteIntBE(Tfile & f,const T v)217 inline bool WriteIntBE(Tfile & f, const T v) {
218 	static_assert(std::numeric_limits<T>::is_integer);
219 	return mpt::IO::Write(f, mpt::as_be(v));
220 }
221 
222 template <typename Tfile>
WriteAdaptiveInt16LE(Tfile & f,const uint16 v,std::size_t fixedSize=0)223 inline bool WriteAdaptiveInt16LE(Tfile & f, const uint16 v, std::size_t fixedSize = 0) {
224 	std::size_t minSize = fixedSize;
225 	std::size_t maxSize = fixedSize;
226 	assert(minSize == 0 || minSize == 1 || minSize == 2);
227 	assert(maxSize == 0 || maxSize == 1 || maxSize == 2);
228 	assert(maxSize == 0 || maxSize >= minSize);
229 	if (maxSize == 0) {
230 		maxSize = 2;
231 	}
232 	if (v < 0x80 && minSize <= 1 && 1 <= maxSize) {
233 		return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 1) | 0x00);
234 	} else if (v < 0x8000 && minSize <= 2 && 2 <= maxSize) {
235 		return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 1) | 0x01);
236 	} else {
237 		assert(false);
238 		return false;
239 	}
240 }
241 
242 template <typename Tfile>
WriteAdaptiveInt32LE(Tfile & f,const uint32 v,std::size_t fixedSize=0)243 inline bool WriteAdaptiveInt32LE(Tfile & f, const uint32 v, std::size_t fixedSize = 0) {
244 	std::size_t minSize = fixedSize;
245 	std::size_t maxSize = fixedSize;
246 	assert(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 3 || minSize == 4);
247 	assert(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 3 || maxSize == 4);
248 	assert(maxSize == 0 || maxSize >= minSize);
249 	if (maxSize == 0) {
250 		maxSize = 4;
251 	}
252 	if (v < 0x40 && minSize <= 1 && 1 <= maxSize) {
253 		return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
254 	} else if (v < 0x4000 && minSize <= 2 && 2 <= maxSize) {
255 		return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
256 	} else if (v < 0x400000 && minSize <= 3 && 3 <= maxSize) {
257 		uint32 value = static_cast<uint32>(v << 2) | 0x02;
258 		std::byte bytes[3];
259 		bytes[0] = static_cast<std::byte>(value >> 0);
260 		bytes[1] = static_cast<std::byte>(value >> 8);
261 		bytes[2] = static_cast<std::byte>(value >> 16);
262 		return mpt::IO::WriteRaw(f, bytes, 3);
263 	} else if (v < 0x40000000 && minSize <= 4 && 4 <= maxSize) {
264 		return mpt::IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x03);
265 	} else {
266 		assert(false);
267 		return false;
268 	}
269 }
270 
271 template <typename Tfile>
WriteAdaptiveInt64LE(Tfile & f,const uint64 v,std::size_t fixedSize=0)272 inline bool WriteAdaptiveInt64LE(Tfile & f, const uint64 v, std::size_t fixedSize = 0) {
273 	std::size_t minSize = fixedSize;
274 	std::size_t maxSize = fixedSize;
275 	assert(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 4 || minSize == 8);
276 	assert(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 4 || maxSize == 8);
277 	assert(maxSize == 0 || maxSize >= minSize);
278 	if (maxSize == 0) {
279 		maxSize = 8;
280 	}
281 	if (v < 0x40 && minSize <= 1 && 1 <= maxSize) {
282 		return mpt::IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
283 	} else if (v < 0x4000 && minSize <= 2 && 2 <= maxSize) {
284 		return mpt::IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
285 	} else if (v < 0x40000000 && minSize <= 4 && 4 <= maxSize) {
286 		return mpt::IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x02);
287 	} else if (v < 0x4000000000000000ull && minSize <= 8 && 8 <= maxSize) {
288 		return mpt::IO::WriteIntLE<uint64>(f, static_cast<uint64>(v << 2) | 0x03);
289 	} else {
290 		assert(false);
291 		return false;
292 	}
293 }
294 
295 // Write a variable-length integer, as found in MIDI files. The number of written bytes is placed in the bytesWritten parameter.
296 template <typename Tfile, typename T>
WriteVarInt(Tfile & f,const T v,std::size_t * bytesWritten=nullptr)297 bool WriteVarInt(Tfile & f, const T v, std::size_t * bytesWritten = nullptr) {
298 	static_assert(std::numeric_limits<T>::is_integer);
299 	static_assert(!std::numeric_limits<T>::is_signed);
300 	std::byte out[(sizeof(T) * 8 + 6) / 7];
301 	std::size_t numBytes = 0;
302 	for (uint32 n = (sizeof(T) * 8) / 7; n > 0; n--) {
303 		if (v >= (static_cast<T>(1) << (n * 7u))) {
304 			out[numBytes++] = static_cast<std::byte>(((v >> (n * 7u)) & 0x7F) | 0x80);
305 		}
306 	}
307 	out[numBytes++] = static_cast<std::byte>(v & 0x7F);
308 	assert(numBytes <= std::size(out));
309 	if (bytesWritten != nullptr) {
310 		*bytesWritten = numBytes;
311 	}
312 	return mpt::IO::WriteRaw(f, out, numBytes);
313 }
314 
315 template <typename Tsize, typename Tfile>
WriteSizedStringLE(Tfile & f,const std::string & str)316 inline bool WriteSizedStringLE(Tfile & f, const std::string & str) {
317 	static_assert(std::numeric_limits<Tsize>::is_integer);
318 	if (str.size() > std::numeric_limits<Tsize>::max()) {
319 		return false;
320 	}
321 	Tsize size = static_cast<Tsize>(str.size());
322 	if (!mpt::IO::WriteIntLE(f, size)) {
323 		return false;
324 	}
325 	if (!mpt::IO::WriteRaw(f, str.data(), str.size())) {
326 		return false;
327 	}
328 	return true;
329 }
330 
331 template <typename Tfile>
WriteText(Tfile & f,const std::string & s)332 inline bool WriteText(Tfile & f, const std::string & s) {
333 	return mpt::IO::WriteRaw(f, s.data(), s.size());
334 }
335 
336 template <typename Tfile>
WriteTextCRLF(Tfile & f)337 inline bool WriteTextCRLF(Tfile & f) {
338 	return mpt::IO::WriteText(f, "\r\n");
339 }
340 
341 template <typename Tfile>
WriteTextLF(Tfile & f)342 inline bool WriteTextLF(Tfile & f) {
343 	return mpt::IO::WriteText(f, "\n");
344 }
345 
346 template <typename Tfile>
WriteTextCRLF(Tfile & f,const std::string & s)347 inline bool WriteTextCRLF(Tfile & f, const std::string & s) {
348 	return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextCRLF(f);
349 }
350 
351 template <typename Tfile>
WriteTextLF(Tfile & f,const std::string & s)352 inline bool WriteTextLF(Tfile & f, const std::string & s) {
353 	return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextLF(f);
354 }
355 
356 
357 
358 } // namespace IO
359 
360 
361 
362 } // namespace MPT_INLINE_NS
363 } // namespace mpt
364 
365 
366 
367 #endif // MPT_IO_IO_HPP
368