1 /** 2 Stream Readers and Writers. 3 4 Utility classes for reading from and writing to streams. 5 6 Copyright: 7 Copyright © 2023-2025, Kitsunebi Games 8 Copyright © 2023-2025, Inochi2D Project 9 10 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 11 Authors: Luna Nielsen 12 */ 13 module nulib.io.stream.rw; 14 import nulib.io.stream; 15 import nulib.memory.endian; 16 import nulib.text.unicode; 17 import nulib.string; 18 import numem; 19 20 /** 21 A stream reader is a helper class which makes it easier to 22 read data from streams. 23 */ 24 final 25 class StreamReader : NuObject { 26 @nogc: 27 private: 28 Stream stream_; 29 30 // Reads a sequence of bytes and ensures their endianness is correct. 31 // Additionally does a type-cast 32 pragma(inline, true) 33 T readEndian(T, Endianess endian)() @trusted if (__traits(isScalar, T)) { 34 ubyte[T.sizeof] data; 35 ptrdiff_t l = stream_.read(data); 36 37 if (l <= 0) 38 return T.init; 39 40 return nu_etoh!(T, endian)(*(cast(T*)data.ptr)); 41 } 42 43 // Reads a sequence of bytes and ensures their endianness is correct. 44 pragma(inline, true) 45 T[] readEndianSeq(T, Endianess endian)(size_t count) @trusted if (__traits(isScalar, T)) { 46 T[] buffer; 47 buffer = nu_resize(buffer, count, 1); 48 ubyte[] bufferView = (cast(ubyte*)buffer.ptr)[0..count*T.sizeof]; 49 50 ptrdiff_t read = stream_.read(bufferView)/T.sizeof; 51 return nu_etoh!(T, endian)(nu_resize(buffer, read <= 0 ? 0 : read, 1)[]); 52 } 53 54 public: 55 @safe: 56 57 /** 58 The stream this reader is reading from 59 */ 60 @property Stream stream() => stream_; 61 62 /** 63 Constructs a new stream reader 64 */ 65 this(Stream stream) @trusted { 66 enforce(stream.canRead(), nogc_new!StreamUnsupportedException(stream)); 67 this.stream_ = stream; 68 } 69 70 /** 71 Reads a float from the stream. 72 73 Returns: 74 The read value. 75 */ 76 float readF32LE() { return readEndian!(float, Endianess.littleEndian)(); } 77 float readF32BE() { return readEndian!(float, Endianess.bigEndian)(); } /// ditto 78 79 /** 80 Reads a double from the stream. 81 82 Returns: 83 The read value. 84 */ 85 double readF64LE() { return readEndian!(double, Endianess.littleEndian)(); } 86 double readF64BE() { return readEndian!(double, Endianess.bigEndian)(); } /// ditto 87 88 /** 89 Reads a byte from the stream. 90 91 Returns: 92 The read value. 93 */ 94 byte readI8() { return cast(byte)readU8(); } 95 96 /** 97 Reads a short from the stream. 98 99 Returns: 100 The read value. 101 */ 102 short readI16LE() { return readEndian!(short, Endianess.littleEndian)(); } 103 short readI16BE() { return readEndian!(short, Endianess.bigEndian)(); } /// ditto 104 105 /** 106 Reads a int from the stream. 107 108 Returns: 109 The read value. 110 */ 111 int readI32LE() { return readEndian!(int, Endianess.littleEndian)(); } 112 int readI32BE() { return readEndian!(int, Endianess.bigEndian)(); } /// ditto 113 114 /** 115 Reads a long from the stream. 116 117 Returns: 118 The read value. 119 */ 120 long readI64LE() { return readEndian!(long, Endianess.littleEndian)(); } 121 long readI64BE() { return readEndian!(long, Endianess.bigEndian)(); } /// ditto 122 123 /** 124 Reads a ubyte from the stream. 125 126 Returns: 127 The read value. 128 */ 129 ubyte readU8() { 130 ubyte[1] buffer; 131 stream.read(buffer); 132 return buffer[0]; 133 } 134 135 /** 136 Reads a ushort from the stream. 137 138 Returns: 139 The read value. 140 */ 141 ushort readU16LE() { return readEndian!(ushort, Endianess.littleEndian)(); } 142 ushort readU16BE() { return readEndian!(ushort, Endianess.bigEndian)(); } /// ditto 143 144 /** 145 Reads a uint from the stream. 146 147 Returns: 148 The read value. 149 */ 150 uint readU32LE() { return readEndian!(uint, Endianess.littleEndian)(); } 151 uint readU32BE() { return readEndian!(uint, Endianess.bigEndian)(); } /// ditto 152 153 /** 154 Reads a ulong from the stream. 155 156 Returns: 157 The read value. 158 */ 159 ulong readU64LE() { return readEndian!(ulong, Endianess.littleEndian)(); } 160 ulong readU64BE() { return readEndian!(ulong, Endianess.bigEndian)(); } /// ditto 161 162 /** 163 Reads a value from the stream. 164 165 Returns: 166 The read value. 167 */ 168 T readLE(T)() { return readEndian!(T, Endianess.littleEndian)(); } 169 T readBE(T)() { return readEndian!(T, Endianess.bigEndian)(); } 170 171 /** 172 Reads a UTF8 string from the stream. 173 174 Returns: 175 The UTF8 string read from the stream. 176 */ 177 nstring readUTF8(uint length) @trusted { 178 nstring result = nstring(length); 179 ptrdiff_t read = stream.read(cast(ubyte[])result.value); 180 return read <= 0 ? nstring.init : result; 181 } 182 183 /** 184 Reads a UTF16 string from the stream. 185 186 This function determines endianness from the byte-order-mark 187 in the stream; if none is found an exception is thrown. 188 189 Params: 190 length = the length of the string to read, including BOM. 191 192 Returns: 193 The UTF16 string read from the stream. 194 */ 195 nwstring readUTF16(uint length) @trusted { 196 wchar[] buffer = this.readEndianSeq!(wchar, NATIVE_ENDIAN)(length); 197 codepoint bom = getBOM(buffer); 198 199 if (!bom) { 200 201 // Clear buffer. 202 buffer = nu_resize(buffer, 0, 1); 203 throw nogc_new!StreamReadException(stream, "UTF16 string did not have a BOM!"); 204 } 205 206 // Copy UTF-16 string out. 207 nwstring out_ = nwstring(nu_etoh!wchar(buffer, getEndianFromBOM(bom))); 208 nu_freea(buffer); 209 return out_; 210 } 211 212 /** 213 Reads a UTF16 string from the stream. 214 215 Params: 216 length = the length of the string to read, including BOM, if any. 217 218 Returns: 219 The UTF16 string read from the stream. 220 */ 221 nwstring readUTF16LE(uint length) @trusted { 222 wchar[] buffer = this.readEndianSeq!(wchar, Endianess.littleEndian)(length); 223 nwstring out_ = nwstring(buffer); 224 nu_freea(buffer); 225 return out_; 226 } 227 228 /** 229 Reads a UTF16 string from the stream. 230 231 Params: 232 length = the length of the string to read, including BOM, if any. 233 234 Returns: 235 The UTF16 string read from the stream. 236 */ 237 nwstring readUTF16BE(uint length) @trusted { 238 wchar[] buffer = this.readEndianSeq!(wchar, Endianess.bigEndian)(length); 239 nwstring out_ = nwstring(buffer); 240 nu_freea(buffer); 241 return out_; 242 } 243 244 /** 245 Reads a UTF32 string from the stream. 246 247 This function determines endianness from the byte-order-mark 248 in the stream; if none is found an exception is thrown. 249 250 Params: 251 length = the length of the string to read, including BOM. 252 253 Returns: 254 The UTF32 string read from the stream. 255 */ 256 ndstring readUTF32(uint length) @trusted { 257 dchar[] buffer = this.readEndianSeq!(dchar, NATIVE_ENDIAN)(length); 258 codepoint bom = getBOM(buffer); 259 260 if (!bom) { 261 262 // Clear buffer. 263 buffer = nu_resize(buffer, 0, 1); 264 throw nogc_new!StreamReadException(stream, "UTF32 string did not have a BOM!"); 265 } 266 267 // Copy UTF-16 string out. 268 ndstring out_ = ndstring(nu_etoh!dchar(buffer, getEndianFromBOM(bom))); 269 nu_freea(buffer); 270 return out_; 271 } 272 273 /** 274 Reads a UTF32 string from the stream. 275 276 Params: 277 length = the length of the string to read, including BOM, if any. 278 279 Returns: 280 The UTF32 string read from the stream. 281 */ 282 ndstring readUTF32LE(uint length) @trusted { 283 dchar[] buffer = this.readEndianSeq!(dchar, Endianess.littleEndian)(length); 284 ndstring out_ = ndstring(buffer); 285 nu_freea(buffer); 286 return out_; 287 } 288 289 /** 290 Reads a UTF32 string from the stream. 291 292 Params: 293 length = the length of the string to read, including BOM, if any. 294 295 Returns: 296 The UTF32 string read from the stream. 297 */ 298 ndstring readUTF32BE(uint length) @trusted { 299 dchar[] buffer = this.readEndianSeq!(dchar, Endianess.bigEndian)(length); 300 ndstring out_ = ndstring(buffer); 301 buffer = nu_resize(buffer, 0, 1); 302 return out_; 303 } 304 } 305 306 /** 307 A stream writer is a helper class which makes it easier to 308 write data to streams. 309 */ 310 final 311 class StreamWriter : NuObject { 312 @nogc: 313 private: 314 Stream stream_; 315 316 // Reads a sequence of bytes and ensures their endianness is correct. 317 // Additionally does a type-cast 318 pragma(inline, true) 319 void writeEndian(Endianess endian, T)(T value) @trusted if (__traits(isScalar, T)) { 320 import numem.core.traits : Unqual; 321 alias UT = Unqual!T; 322 323 value = cast(T)nu_etoh!(T, endian)(cast(UT)value); 324 ubyte[] bufferView = (cast(ubyte*)&value)[0..T.sizeof]; 325 stream.write(bufferView); 326 } 327 328 // Reads a sequence of bytes and ensures their endianness is correct. 329 pragma(inline, true) 330 void writeEndianSeq(Endianess endian, T)(T[] values) @trusted if (__traits(isScalar, T)) { 331 import numem.core.traits : Unqual; 332 alias UT = Unqual!(T[]); 333 334 auto buffer = cast(ubyte[])cast(void[])nu_etoh!(UT, endian)(cast(UT)values.nu_dup()); 335 stream.write(buffer); 336 nu_freea(buffer); 337 } 338 339 public: 340 @safe: 341 342 /** 343 The stream this writer is writing to 344 */ 345 @property Stream stream() => stream_; 346 347 /** 348 Constructs a new stream writer 349 */ 350 this(Stream stream) @trusted{ 351 enforce(stream.canWrite(), nogc_new!StreamUnsupportedException(stream)); 352 this.stream_ = stream; 353 } 354 355 /** 356 Reads a value from the stream. 357 358 Returns: 359 The read value. 360 */ 361 void writeLE(T)(T value) { writeEndian!(Endianess.littleEndian)(value); } 362 void writeBE(T)(T value) { writeEndian!(Endianess.littleEndian)(value); } 363 364 /** 365 Writes a UTF8 string to the stream. 366 367 Params: 368 value = The string to write to the stream. 369 */ 370 void writeUTF8(string value) @trusted { 371 stream.write(cast(ubyte[])value); 372 } 373 374 /** 375 Writes a UTF16 string to the stream. 376 377 Params: 378 value = The string to write to the stream. 379 */ 380 void writeUTF16(wstring value) @trusted { 381 stream.write(cast(ubyte[])cast(void[])value); 382 } 383 384 /** 385 Writes a UTF16 string to the stream. 386 387 Params: 388 value = The string to write to the stream. 389 */ 390 void writeUTF16LE(wstring value) @trusted { 391 this.writeEndianSeq!(Endianess.littleEndian)(value); 392 } 393 394 /** 395 Writes a UTF16 string to the stream. 396 397 Params: 398 value = The string to write to the stream. 399 */ 400 void writeUTF16BE(wstring value) @trusted { 401 this.writeEndianSeq!(Endianess.bigEndian)(value); 402 } 403 404 /** 405 Writes a UTF32 string to the stream. 406 407 Params: 408 value = The string to write to the stream. 409 */ 410 void writeUTF32(dstring value) @trusted { 411 stream.write(cast(ubyte[])cast(void[])value); 412 } 413 414 /** 415 Writes a UTF32 string to the stream. 416 417 Params: 418 value = The string to write to the stream. 419 */ 420 void writeUTF32LE(dstring value) @trusted { 421 this.writeEndianSeq!(Endianess.littleEndian)(value); 422 } 423 424 /** 425 Writes a UTF32 string to the stream. 426 427 Params: 428 value = The string to write to the stream. 429 */ 430 void writeUTF32BE(dstring value) @trusted { 431 this.writeEndianSeq!(Endianess.bigEndian)(value); 432 } 433 }