intnslib 0.1
A library to hold common functionality used across multiple projects.
Loading...
Searching...
No Matches
BinaryReader.hpp
1#ifndef INTNS_IO_BINARY_READER_HPP
2#define INTNS_IO_BINARY_READER_HPP
3
4#include <bit>
5#include <cstdint>
6#include <cstring>
7#include <fstream>
8#include <span>
9#include <stdexcept>
10#include <string>
11#include <vector>
12
13#include "IoTypes.hpp"
14
15namespace intns::io {
16
39template <Endianness E = Endianness::kLittle>
41 public:
49 explicit MemoryReader(const std::vector<uint8_t>& buffer, size_t position = 0)
50 : data_(buffer.data()), size_(buffer.size()), position_(position) {
51 if (position > size_) {
52 throw std::out_of_range("Initial position " + std::to_string(position) +
53 " exceeds buffer size " + std::to_string(size_));
54 }
55 }
56
64 explicit MemoryReader(std::span<const uint8_t> buffer, size_t position = 0)
65 : data_(buffer.data()), size_(buffer.size()), position_(position) {
66 if (position > size_) {
67 throw std::out_of_range("Initial position " + std::to_string(position) +
68 " exceeds buffer size " + std::to_string(size_));
69 }
70 }
71
77 [[nodiscard]] size_t size() const noexcept { return size_; }
78
84 [[nodiscard]] size_t position() const noexcept { return position_; }
85
91 [[nodiscard]] size_t remaining() const noexcept { return size_ - position_; }
92
102 void set_position(size_t pos) noexcept {
103 if (pos > size_) pos = size_;
104 position_ = pos;
105 }
106
116 void skip(size_t bytes) noexcept {
117 position_ = std::min(position_ + bytes, size_);
118 }
119
126 [[nodiscard]] uint8_t read_u8() {
127 if (position_ >= size_) {
128 throw std::out_of_range("Cannot read uint8_t: position " +
129 std::to_string(position_) + " >= size " +
130 std::to_string(size_));
131 }
132 return data_[position_++];
133 }
134
141 [[nodiscard]] uint16_t read_u16() {
142 if (position_ + 2 > size_) {
143 throw std::out_of_range("Cannot read uint16_t: need 2 bytes, only " +
144 std::to_string(remaining()) + " available");
145 }
146 uint16_t value;
147 std::memcpy(&value, data_ + position_, 2);
148 position_ += 2;
149 if constexpr (E != native_endian()) value = bswap_16(value);
150 return value;
151 }
152
159 [[nodiscard]] uint32_t read_u32() {
160 if (position_ + 4 > size_) {
161 throw std::out_of_range("Cannot read uint32_t: need 4 bytes, only " +
162 std::to_string(remaining()) + " available");
163 }
164 uint32_t value;
165 std::memcpy(&value, data_ + position_, 4);
166 position_ += 4;
167 if constexpr (E != native_endian()) value = bswap_32(value);
168 return value;
169 }
170
177 [[nodiscard]] uint64_t read_u64() {
178 if (position_ + 8 > size_) {
179 throw std::out_of_range("Cannot read uint64_t: need 8 bytes, only " +
180 std::to_string(remaining()) + " available");
181 }
182 uint64_t value;
183 std::memcpy(&value, data_ + position_, 8);
184 position_ += 8;
185 if constexpr (E != native_endian()) value = bswap_64(value);
186 return value;
187 }
188
195 [[nodiscard]] int8_t read_s8() { return static_cast<int8_t>(read_u8()); }
196
203 [[nodiscard]] int16_t read_s16() { return static_cast<int16_t>(read_u16()); }
204
211 [[nodiscard]] int32_t read_s32() { return static_cast<int32_t>(read_u32()); }
212
219 [[nodiscard]] int64_t read_s64() { return static_cast<int64_t>(read_u64()); }
220
227 [[nodiscard]] float read_f32() { return std::bit_cast<float>(read_u32()); }
228
235 [[nodiscard]] double read_f64() { return std::bit_cast<double>(read_u64()); }
236
245 void read_bytes(void* dest, size_t bytes) {
246 if (!dest) {
247 throw std::invalid_argument("Destination pointer cannot be null");
248 }
249 if (position_ + bytes > size_) {
250 throw std::out_of_range("Cannot read " + std::to_string(bytes) +
251 " bytes: only " + std::to_string(remaining()) +
252 " available");
253 }
254 std::memcpy(dest, data_ + position_, bytes);
255 position_ += bytes;
256 }
257
267 void read_u16_array(uint16_t* array, size_t count) {
268 if (!array) {
269 throw std::invalid_argument("Array pointer cannot be null");
270 }
271 read_bytes(array, count * 2);
272 if constexpr (E != native_endian()) {
273 for (size_t i = 0; i < count; ++i) {
274 array[i] = bswap_16(array[i]);
275 }
276 }
277 }
278
288 void read_u32_array(uint32_t* array, size_t count) {
289 if (!array) {
290 throw std::invalid_argument("Array pointer cannot be null");
291 }
292 read_bytes(array, count * 4);
293 if constexpr (E != native_endian()) {
294 for (size_t i = 0; i < count; ++i) {
295 array[i] = bswap_32(array[i]);
296 }
297 }
298 }
299
307 [[nodiscard]] std::string read_string(size_t length) {
308 if (position_ + length > size_) {
309 throw std::out_of_range("Cannot read string of length " +
310 std::to_string(length) + ": only " +
311 std::to_string(remaining()) + " bytes available");
312 }
313 std::string result(reinterpret_cast<const char*>(data_ + position_),
314 length);
315 position_ += length;
316 return result;
317 }
318
328 [[nodiscard]] std::string read_cstring() {
329 const uint8_t* start = data_ + position_;
330 const uint8_t* end =
331 static_cast<const uint8_t*>(std::memchr(start, 0, remaining()));
332
333 if (!end) {
334 // No null terminator - read to end
335 end = data_ + size_;
336 }
337
338 size_t length = end - start;
339 std::string result(reinterpret_cast<const char*>(start), length);
340 position_ += length + (end < data_ + size_ ? 1 : 0);
341 return result;
342 }
343
350 [[nodiscard]] uint8_t peek_u8() const {
351 if (position_ >= size_) {
352 throw std::out_of_range("Cannot peek uint8_t: position " +
353 std::to_string(position_) + " >= size " +
354 std::to_string(size_));
355 }
356 return data_[position_];
357 }
358
365 [[nodiscard]] uint16_t peek_u16() const {
366 if (position_ + 2 > size_) {
367 throw std::out_of_range("Cannot peek uint16_t: need 2 bytes, only " +
368 std::to_string(size_ - position_) + " available");
369 }
370 uint16_t value;
371 std::memcpy(&value, data_ + position_, 2);
372 if constexpr (E != native_endian()) value = bswap_16(value);
373 return value;
374 }
375
376 private:
382 static constexpr Endianness native_endian() noexcept {
383 return std::endian::native == std::endian::little ? Endianness::kLittle
384 : Endianness::kBig;
385 }
386
387 const uint8_t* data_;
388 size_t size_;
389 size_t position_;
390};
391
414template <Endianness E = Endianness::kLittle>
416 public:
425 explicit FileReader(const std::string& filename, size_t buffer_size = 8192)
426 : buffer_(buffer_size) {
427 if (buffer_size == 0) {
428 throw std::invalid_argument("Buffer size cannot be zero");
429 }
430
431 file_.open(filename, std::ios::binary);
432 if (!file_) {
433 throw std::runtime_error("Failed to open file: " + filename);
434 }
435
436 file_.seekg(0, std::ios::end);
437 file_size_ = file_.tellg();
438 file_.seekg(0);
439
440 fill_buffer();
441 }
442
448 [[nodiscard]] size_t size() const noexcept { return file_size_; }
449
455 [[nodiscard]] size_t position() const noexcept {
456 return file_pos_ - buffer_remaining();
457 }
458
464 [[nodiscard]] size_t remaining() const noexcept {
465 return file_size_ - position();
466 }
467
475 void set_position(size_t pos) {
476 if (pos > file_size_) pos = file_size_;
477 file_.seekg(pos);
478 if (!file_) {
479 throw std::runtime_error("Failed to seek to position " +
480 std::to_string(pos));
481 }
482 file_pos_ = pos;
483 fill_buffer();
484 }
485
492 void skip(size_t bytes) { set_position(position() + bytes); }
493
500 [[nodiscard]] uint8_t read_u8() {
501 ensure_available(1);
502 return buffer_[buffer_pos_++];
503 }
504
511 [[nodiscard]] uint16_t read_u16() {
512 ensure_available(2);
513 uint16_t value;
514 std::memcpy(&value, &buffer_[buffer_pos_], 2);
515 buffer_pos_ += 2;
516 if constexpr (E != native_endian()) value = bswap_16(value);
517 return value;
518 }
519
526 [[nodiscard]] uint32_t read_u32() {
527 ensure_available(4);
528 uint32_t value;
529 std::memcpy(&value, &buffer_[buffer_pos_], 4);
530 buffer_pos_ += 4;
531 if constexpr (E != native_endian()) value = bswap_32(value);
532 return value;
533 }
534
541 [[nodiscard]] uint64_t read_u64() {
542 ensure_available(8);
543 uint64_t value;
544 std::memcpy(&value, &buffer_[buffer_pos_], 8);
545 buffer_pos_ += 8;
546 if constexpr (E != native_endian()) value = bswap_64(value);
547 return value;
548 }
549
556 [[nodiscard]] int8_t read_s8() { return static_cast<int8_t>(read_u8()); }
557
564 [[nodiscard]] int16_t read_s16() { return static_cast<int16_t>(read_u16()); }
565
572 [[nodiscard]] int32_t read_s32() { return static_cast<int32_t>(read_u32()); }
573
580 [[nodiscard]] int64_t read_s64() { return static_cast<int64_t>(read_u64()); }
581
588 [[nodiscard]] float read_f32() { return std::bit_cast<float>(read_u32()); }
589
596 [[nodiscard]] double read_f64() { return std::bit_cast<double>(read_u64()); }
597
606 void read_bytes(void* dest, size_t bytes) {
607 if (!dest) {
608 throw std::invalid_argument("Destination pointer cannot be null");
609 }
610
611 uint8_t* out = static_cast<uint8_t*>(dest);
612 while (bytes > 0) {
613 ensure_available(1);
614 size_t chunk = std::min(bytes, buffer_remaining());
615 std::memcpy(out, &buffer_[buffer_pos_], chunk);
616 buffer_pos_ += chunk;
617 out += chunk;
618 bytes -= chunk;
619 }
620 }
621
629 [[nodiscard]] std::string read_string(size_t length) {
630 std::string result(length, '\0');
631 read_bytes(result.data(), length);
632 return result;
633 }
634
642 [[nodiscard]] std::string read_cstring() {
643 std::string result;
644 uint8_t ch;
645 while ((ch = read_u8()) != 0) {
646 result.push_back(static_cast<char>(ch));
647 }
648 return result;
649 }
650
651 private:
657 [[nodiscard]] size_t buffer_remaining() const noexcept {
658 return buffer_end_ - buffer_pos_;
659 }
660
666 void fill_buffer() {
667 file_.read(reinterpret_cast<char*>(buffer_.data()), buffer_.size());
668 buffer_end_ = file_.gcount();
669 buffer_pos_ = 0;
670 file_pos_ = file_.tellg();
671
672 if (file_.bad()) {
673 throw std::runtime_error("Failed to read from file");
674 }
675 }
676
684 void ensure_available(size_t bytes) {
685 if (buffer_remaining() < bytes) {
686 // Move remaining bytes to start of buffer
687 if (buffer_remaining() > 0) {
688 std::memmove(buffer_.data(), &buffer_[buffer_pos_], buffer_remaining());
689 }
690 buffer_end_ = buffer_remaining();
691 buffer_pos_ = 0;
692
693 // Fill rest of buffer
694 file_.read(reinterpret_cast<char*>(&buffer_[buffer_end_]),
695 buffer_.size() - buffer_end_);
696 buffer_end_ += file_.gcount();
697 file_pos_ = file_.tellg();
698
699 if (file_.bad()) {
700 throw std::runtime_error("Failed to read from file");
701 }
702 }
703
704 if (buffer_remaining() < bytes) {
705 throw std::out_of_range("Cannot read " + std::to_string(bytes) +
706 " bytes: reached end of file");
707 }
708 }
709
715 static constexpr Endianness native_endian() noexcept {
716 return std::endian::native == std::endian::little ? Endianness::kLittle
717 : Endianness::kBig;
718 }
719
720 std::ifstream file_;
721 std::vector<uint8_t> buffer_;
722 size_t file_size_ = 0;
723 size_t file_pos_ = 0;
724 size_t buffer_pos_ = 0;
725 size_t buffer_end_ = 0;
726};
727
731using LEMemoryReader = MemoryReader<Endianness::kLittle>;
732
736using BEMemoryReader = MemoryReader<Endianness::kBig>;
737
741using LEFileReader = FileReader<Endianness::kLittle>;
742
746using BEFileReader = FileReader<Endianness::kBig>;
747
748} // namespace intns::io
749
750#endif // INTNS_IO_BINARY_READER_HPP
A file-based binary reader with buffering and configurable endianness.
Definition BinaryReader.hpp:415
int8_t read_s8()
Reads a signed 8-bit integer.
Definition BinaryReader.hpp:556
int16_t read_s16()
Reads a signed 16-bit integer with endianness conversion.
Definition BinaryReader.hpp:564
FileReader(const std::string &filename, size_t buffer_size=8192)
Constructs a FileReader and opens the specified file.
Definition BinaryReader.hpp:425
uint32_t read_u32()
Reads an unsigned 32-bit integer with endianness conversion.
Definition BinaryReader.hpp:526
std::string read_string(size_t length)
Reads a fixed-length string from the file.
Definition BinaryReader.hpp:629
std::string read_cstring()
Reads a null-terminated C-style string from the file.
Definition BinaryReader.hpp:642
double read_f64()
Reads a 64-bit floating-point value with endianness conversion.
Definition BinaryReader.hpp:596
void skip(size_t bytes)
Advances the read position by the specified number of bytes.
Definition BinaryReader.hpp:492
float read_f32()
Reads a 32-bit floating-point value with endianness conversion.
Definition BinaryReader.hpp:588
int32_t read_s32()
Reads a signed 32-bit integer with endianness conversion.
Definition BinaryReader.hpp:572
void set_position(size_t pos)
Sets the read position within the file.
Definition BinaryReader.hpp:475
uint64_t read_u64()
Reads an unsigned 64-bit integer with endianness conversion.
Definition BinaryReader.hpp:541
uint16_t read_u16()
Reads an unsigned 16-bit integer with endianness conversion.
Definition BinaryReader.hpp:511
size_t size() const noexcept
Returns the total size of the file.
Definition BinaryReader.hpp:448
void read_bytes(void *dest, size_t bytes)
Reads raw bytes into a destination buffer.
Definition BinaryReader.hpp:606
size_t remaining() const noexcept
Returns the number of bytes remaining to be read.
Definition BinaryReader.hpp:464
size_t position() const noexcept
Returns the current read position in the file.
Definition BinaryReader.hpp:455
uint8_t read_u8()
Reads an unsigned 8-bit integer.
Definition BinaryReader.hpp:500
int64_t read_s64()
Reads a signed 64-bit integer with endianness conversion.
Definition BinaryReader.hpp:580
A memory-based binary reader with configurable endianness.
Definition BinaryReader.hpp:40
double read_f64()
Reads a 64-bit floating-point value with endianness conversion.
Definition BinaryReader.hpp:235
uint16_t peek_u16() const
Peeks at an unsigned 16-bit integer without advancing position.
Definition BinaryReader.hpp:365
MemoryReader(std::span< const uint8_t > buffer, size_t position=0)
Constructs a MemoryReader from a span buffer.
Definition BinaryReader.hpp:64
int16_t read_s16()
Reads a signed 16-bit integer with endianness conversion.
Definition BinaryReader.hpp:203
MemoryReader(const std::vector< uint8_t > &buffer, size_t position=0)
Constructs a MemoryReader from a vector buffer.
Definition BinaryReader.hpp:49
void read_u32_array(uint32_t *array, size_t count)
Reads an array of unsigned 32-bit integers with endianness conversion.
Definition BinaryReader.hpp:288
uint64_t read_u64()
Reads an unsigned 64-bit integer with endianness conversion.
Definition BinaryReader.hpp:177
void read_u16_array(uint16_t *array, size_t count)
Reads an array of unsigned 16-bit integers with endianness conversion.
Definition BinaryReader.hpp:267
void skip(size_t bytes) noexcept
Advances the read position by the specified number of bytes.
Definition BinaryReader.hpp:116
uint32_t read_u32()
Reads an unsigned 32-bit integer with endianness conversion.
Definition BinaryReader.hpp:159
float read_f32()
Reads a 32-bit floating-point value with endianness conversion.
Definition BinaryReader.hpp:227
int64_t read_s64()
Reads a signed 64-bit integer with endianness conversion.
Definition BinaryReader.hpp:219
size_t position() const noexcept
Returns the current read position.
Definition BinaryReader.hpp:84
size_t size() const noexcept
Returns the total size of the buffer.
Definition BinaryReader.hpp:77
int32_t read_s32()
Reads a signed 32-bit integer with endianness conversion.
Definition BinaryReader.hpp:211
int8_t read_s8()
Reads a signed 8-bit integer.
Definition BinaryReader.hpp:195
uint16_t read_u16()
Reads an unsigned 16-bit integer with endianness conversion.
Definition BinaryReader.hpp:141
void read_bytes(void *dest, size_t bytes)
Reads raw bytes into a destination buffer.
Definition BinaryReader.hpp:245
uint8_t peek_u8() const
Peeks at an unsigned 8-bit integer without advancing position.
Definition BinaryReader.hpp:350
std::string read_cstring()
Reads a null-terminated C-style string from the buffer.
Definition BinaryReader.hpp:328
size_t remaining() const noexcept
Returns the number of bytes remaining to be read.
Definition BinaryReader.hpp:91
std::string read_string(size_t length)
Reads a fixed-length string from the buffer.
Definition BinaryReader.hpp:307
void set_position(size_t pos) noexcept
Sets the read position within the buffer.
Definition BinaryReader.hpp:102
uint8_t read_u8()
Reads an unsigned 8-bit integer.
Definition BinaryReader.hpp:126