/* /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$$$ /$$ /$$ /$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$$ /$$$$$$$$ |_ $$_/| $$$ /$$$| $$__ $$| $$_____/| $$$ | $$ |__ $$|_ $$_/ | $$$ | $$| $$_____/|__ $$__/ | $$ | $$$$ /$$$$| $$ \ $$| $$ | $$$$| $$ | $$ | $$ | $$$$| $$| $$ | $$ | $$ | $$ $$/$$ $$| $$$$$$$ | $$$$$ | $$ $$ $$ | $$ | $$ | $$ $$ $$| $$$$$ | $$ | $$ | $$ $$$| $$| $$__ $$| $$__/ | $$ $$$$ /$$ | $$ | $$ | $$ $$$$| $$__/ | $$ | $$ | $$\ $ | $$| $$ \ $$| $$ | $$\ $$$| $$ | $$ | $$ | $$\ $$$| $$ | $$ /$$$$$$| $$ \/ | $$| $$$$$$$/| $$$$$$$$| $$ \ $$| $$$$$$/ /$$$$$$ /$$| $$ \ $$| $$$$$$$$ | $$ |______/|__/ |__/|_______/ |________/|__/ \__/ \______/ |______/|__/|__/ \__/|________/ |__/ © 2025-26 by Benjamin Watt of IMBENJI.NET LIMITED - All rights reserved. Use of this source code is governed by a MIT license that can be found in the LICENSE file. This file is part of the SweepStore (formerly Binary Table) package for Dart. */ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; enum BT_Type { POINTER (8), ADDRESS_TABLE (-1), INTEGER (4), FLOAT (4), STRING (-1), INTEGER_ARRAY (-1, arrayType: true), FLOAT_ARRAY (-1, arrayType: true),; final int size; final bool arrayType; const BT_Type(this.size, { this.arrayType = false, }); static BT_Type fromId(int id) { if (id < 0 || id >= BT_Type.values.length) { throw ArgumentError('Invalid BT_Type id: $id'); } return BT_Type.values[id]; } static BT_Type fromDynamic(dynamic value) { if (value is int) { return BT_Type.INTEGER; } else if (value is double) { return BT_Type.FLOAT; } else if (value is String) { return BT_Type.STRING; } else if (value is List) { return BT_Type.INTEGER_ARRAY; } else if (value is List) { return BT_Type.FLOAT_ARRAY; } else { throw ArgumentError('Unsupported type: ${value.runtimeType}'); } } } List encodeValue(dynamic value) { BT_Type valueType = BT_Type.fromDynamic(value); List valueBuffer = [valueType.index]; if (valueType == BT_Type.INTEGER) { valueBuffer.addAll((ByteData(4)..setInt32(0, value as int, Endian.little)).buffer.asUint8List()); } else if (valueType == BT_Type.FLOAT) { valueBuffer.addAll((ByteData(4)..setFloat32(0, value as double, Endian.little)).buffer.asUint8List()); } else if (valueType == BT_Type.STRING) { // String length valueBuffer.addAll((ByteData(4) ..setInt32(0, (value as String).length, Endian.little)).buffer .asUint8List()); // String bytes valueBuffer.addAll(value.codeUnits); } else if (valueType == BT_Type.INTEGER_ARRAY) { List list = value as List; // Length valueBuffer.addAll((ByteData(4) ..setInt32(0, list.length, Endian.little)).buffer.asUint8List()); // Entries for (var item in list) { valueBuffer.addAll(encodeValue(item)); } } else if (valueType == BT_Type.FLOAT_ARRAY) { List list = value as List; // Length valueBuffer.addAll((ByteData(4) ..setInt32(0, list.length, Endian.little)).buffer.asUint8List()); // Entries for (var item in list) { valueBuffer.addAll(encodeValue(item)); } } else { throw ArgumentError('Unsupported type: ${value.runtimeType}'); } return valueBuffer; } class BT_Pointer { final int address; const BT_Pointer(this.address); bool get isNull => address == -1; operator ==(Object other) { if (other is! BT_Pointer) return false; return address == other.address; } @override String toString() => '0x${address.toRadixString(16)} ($address)'; } const BT_Pointer BT_Null = BT_Pointer(-1); class BT_Reference { BinaryTable _table; BT_Pointer _pointer; BT_Reference(this._table, this._pointer); dynamic decodeValue() { if (_pointer.isNull) { return null; } _table._file.setPositionSync(_pointer.address); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); if (type == BT_Type.INTEGER) { return _table._file.readIntSync(4); } else if (type == BT_Type.FLOAT) { return _table._file.readFloat32Sync(); } else if (type == BT_Type.STRING) { int length = _table._file.readIntSync(4); List bytes = _table._file.readSync(length); return String.fromCharCodes(bytes); } else if (type == BT_Type.ADDRESS_TABLE) { // Address table decoding not implemented throw UnimplementedError('Address table decoding not implemented'); } else if (type == BT_Type.POINTER) { return _table._file.readPointerSync(); } else if (type == BT_Type.INTEGER_ARRAY) { return BT_UniformArray(_table, _pointer); } else if (type == BT_Type.FLOAT_ARRAY) { return BT_UniformArray(_table, _pointer); } else { throw Exception('Unsupported type: $type'); } } /// Determine the size in storage in bytes with the least amount of reads possible int get size { if (_pointer.isNull) { return 0; } _table._file.setPositionSync(_pointer.address); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); if (type == BT_Type.INTEGER) { return 1 + 4; // Type byte + Int32 } else if (type == BT_Type.FLOAT) { return 1 + 4; // Type byte + Float32 } else if (type == BT_Type.STRING) { int length = _table._file.readIntSync(4); return 1 + 4 + length; // Type byte + Length Int32 + String bytes } else if (type == BT_Type.ADDRESS_TABLE) { // Address table size is variable, need to read the count int count = _table._file.readIntSync(4); return 1 + 4 + count * (8 + BT_Type.POINTER.size); // Type byte + Count Int32 + Entries } else { throw Exception('Unsupported type: $type'); } } BT_Type? get type { if (_pointer.isNull) { return null; } _table._file.setPositionSync(_pointer.address); int typeId = _table._file.readByteSync(); return BT_Type.fromId(typeId); } @override String toString() => _pointer.toString(); } class BT_FreeListEntry { BT_Pointer pointer; int size; BT_FreeListEntry(this.pointer, this.size); } /// Wrapper for interacting with uniform arrays in the binary table. A uniform array is an array where all elements are of the same type, allowing for more efficient storage and retrieval using random access. class BT_UniformArray extends BT_Reference { BT_UniformArray(super._table, super._pointer); /// Get the length of the array - Don't mix up with size. int get length { if (_pointer.isNull) { return 0; } _table._file.setPositionSync(_pointer.address); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); if (!type.arrayType) { throw Exception('Not an array'); } return _table._file.readIntSync(4); } dynamic operator [](int index) { if (_pointer.isNull) { throw Exception('Null pointer'); } int len = length; if (index < 0 || index >= len) { throw RangeError.index(index, this, 'index', null, len); } // Determine the type of the array by reading the first items type _table._file.setPositionSync(_pointer.address + 1 + 4); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); int itemOffset = index * (1 + type.size); // Type byte + data size BT_Reference itemRef = BT_Reference(_table, BT_Pointer((_pointer.address + 1 + 4) + itemOffset)); return itemRef.decodeValue(); } void operator []=(int index, dynamic value) { if (_pointer.isNull) { throw Exception('Null pointer'); } int len = length; if (index < 0 || index >= len) { throw RangeError.index(index, this, 'index', null, len); } // Determine the type of the array by reading the first items type _table._file.setPositionSync(_pointer.address + 1 + 4); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); if (type.size == -1) { throw Exception("Types with variable size are not supported in uniform arrays. Use a non-uniform array instead."); } // Ensure the new value is of the correct type BT_Type newValueType = BT_Type.fromDynamic(value); if (newValueType != type) { throw Exception('Type mismatch. Expected $type but got $newValueType'); } // Calculate the offset of the item to update int itemOffset = index * (1 + type.size); // Type byte + data size BT_Pointer itemPointer = BT_Pointer((_pointer.address + 1 + 4) + itemOffset); // Encode the new value List valueBuffer = encodeValue(value); // Place the new value in the file _table._file.setPositionSync(itemPointer.address); _table._file.writeFromSync(valueBuffer); } void addAll(Iterable values) { _table.antiFreeListScope(() { // Determine the type of the array by reading the first items type BT_Type type = elementType ?? BT_Type.fromDynamic(values.first); // Validate all new values are of the correct type for (int i = 0; i < values.length; i++) { BT_Type newValueType = BT_Type.fromDynamic(values.elementAt(i)); if (newValueType != type) { throw Exception('Type mismatch at index $i. Expected $type but got $newValueType'); } else if (newValueType.size == -1) { throw Exception("Types with variable size are not supported in uniform arrays. Use a non-uniform array instead."); } } // Read the full array by loading the full buffer List fullBuffer = []; int bufferSize = 1 + 4 + length * (1 + type.size); _table._file.setPositionSync(_pointer.address); fullBuffer = _table._file.readSync(bufferSize).toList(); // Encode the new values and add them to the buffer for (var value in values) { fullBuffer.addAll(encodeValue(value)); } // Update the length in the buffer int newLength = length + values.length; List lengthBytes = (ByteData(4)..setInt32(0, newLength, Endian.little)).buffer.asUint8List(); fullBuffer.replaceRange(1, 5, lengthBytes); // Free the old array _table.free(_pointer, size); // Allocate new space for the updated array BT_Pointer newPointer = _table.alloc(fullBuffer.length); // Replace any references to the old pointer with the new one Map addressTable = _table._addressTable; addressTable.updateAll((key, value) { if (value == _pointer) { print('Updating address table entry for key $key from $value to $newPointer'); return newPointer; } return value; }); _table._addressTable = addressTable; _pointer = newPointer; // Write the updated buffer to the new location _table._file.setPositionSync(newPointer.address); _table._file.writeFromSync(fullBuffer); print('Array resized to new length $newLength at $newPointer'); }); } void add(dynamic value) { addAll([value]); } List fetchSublist([int start = 0, int end = -1]) { // Read the full array by loading the full buffer of only whats needed BT_Type? type = elementType; if (type == null) { return []; } if (type.size == -1) { throw Exception("Types with variable size are not supported in uniform arrays. Use a non-uniform array instead."); } end = end == -1 ? length : end; int bufferStart = 1 + 4 + start * (1 + type.size); int bufferEnd = 1 + 4 + end * (1 + type.size); int bufferSize = bufferEnd - bufferStart; _table._file.setPositionSync(_pointer.address + bufferStart); List buffer = _table._file.readSync(bufferSize).toList(); List values = []; for (int i = 0; i < (end - start); i++) { int offset = i * (1 + type.size); BT_Reference itemRef = BT_Reference(_table, BT_Pointer((_pointer.address + bufferStart) + offset)); values.add(itemRef.decodeValue()); } return values; } @override int get size { if (length == 0) { return 1 + 4; // Type byte + Length Int32 } _table._file.setPositionSync(_pointer.address); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); if (type.arrayType) { _table._file.setPositionSync(_pointer.address); int bufferSize = 1 + 4 + length * (1 + elementType!.size); return bufferSize; } return super.size; } BT_Type? get elementType { if (length == 0) { return null; } else { _table._file.setPositionSync(_pointer.address + 1 + 4); int typeId = _table._file.readByteSync(); BT_Type type = BT_Type.fromId(typeId); return type; } } @override String toString([bool readValues = false]) { if (readValues) { return 'Uniform Array of length $length'; } List preview = []; int len = length; for (int i = 0; i < length; i++) { preview.add(this[i]); } return 'Uniform Array: $preview'; } } extension FreeList on List { void removePointer(BT_Pointer pointer) { removeWhere((entry) => entry.pointer == pointer); } List bt_encode() { List buffer = []; /* New encoding should reflect a "read from the end" approach. So it should look like: - Entries (variable) - Entry count (4 bytes) Entry: - Pointer (8 bytes) - Size (4 bytes) This means that when reading, we can always assume that the last 4 bytes in the file are the entry count for the free list. The entries however, can be read from front to back. Once we know the count, we know where to start reading. */ for (var entry in this) { // Pointer buffer.addAll((ByteData(BT_Type.POINTER.size)..setInt64(0, entry.pointer.address, Endian.little)).buffer.asUint8List()); // Size buffer.addAll((ByteData(4)..setInt32(0, entry.size, Endian.little)).buffer.asUint8List()); } // Entry count buffer.addAll((ByteData(4)..setInt32(0, length, Endian.little)).buffer.asUint8List()); return buffer; } } extension fnv1a on String { int get bt_hash { List bytes = utf8.encode(this); int hash = 0xcbf29ce484222325; // FNV offset basis for (int byte in bytes) { hash ^= byte; hash *= 0x100000001b3; // FNV prime } return hash; } } class BinaryTable { RandomAccessFile _file; BinaryTable(String path) : _file = File(path).openSync(mode: FileMode.append); void initialise() { _file.setPositionSync(0); _file.writePointerSync(BT_Null); // Address table pointer _file.writeIntSync(0, 4); // Free list entry count } Map get _addressTable { _file.setPositionSync(0); BT_Reference tableRef = BT_Reference(this, _file.readPointerSync()); if (tableRef._pointer.isNull) { return {}; } _file.setPositionSync(tableRef._pointer.address + 1); int fileSize = _file.lengthSync(); int tableCount = _file.readIntSync(4); int tableSize = tableCount * (8 + BT_Type.POINTER.size); List buffer = _file.readSync(tableSize).toList(); Map addressTable = {}; for (int i = 0; i < tableCount; i++) { int offset = i * (8 + BT_Type.POINTER.size); // Key Hash List keyHashBytes = buffer.sublist(offset, offset + 8); int keyHash = ByteData.sublistView(Uint8List.fromList(keyHashBytes)).getInt64(0, Endian.little); // Value Pointer List valuePointerBytes = buffer.sublist(offset + 8, offset + 8 + BT_Type.POINTER.size); int valuePointerAddress = ByteData.sublistView(Uint8List.fromList(valuePointerBytes)).getInt64(0, Endian.little); BT_Pointer valuePointer = BT_Pointer(valuePointerAddress); // Add to address table addressTable[keyHash] = valuePointer; } return addressTable; } set _addressTable(Map table) { List buffer = [ BT_Type.ADDRESS_TABLE.index ]; buffer.addAll((ByteData(4)..setInt32(0, table.length, Endian.little)).buffer.asUint8List()); table.forEach((key, value) { buffer.addAll((ByteData(8)..setInt64(0, key, Endian.little)).buffer.asUint8List()); buffer.addAll((ByteData(BT_Type.POINTER.size)..setInt64(0, value.address, Endian.little)).buffer.asUint8List()); }); // Write new address table at end of file BT_Pointer tableAddress = alloc(buffer.length); _file.setPositionSync(tableAddress.address); _file.writeFromSync(buffer); // Read old table pointer before updating _file.setPositionSync(0); BT_Reference oldTableRef = BT_Reference(this, _file.readPointerSync()); // Update header to point to new table _file.setPositionSync(0); _file.writePointerSync(tableAddress); // Now free the old table if it exists and is not the same as the new one if (!oldTableRef._pointer.isNull && oldTableRef._pointer != tableAddress) { free(oldTableRef._pointer, oldTableRef.size); } } bool freeListLifted = false; List? _freeListCache; List get _freeList { if (freeListLifted) { return _freeListCache ?? []; } _file.setPositionSync(_file.lengthSync() - 4); int entryCount = _file.readIntSync(4); if (entryCount == 0) { return []; } int entrySize = BT_Type.POINTER.size + 4; // Pointer + Size int freeListSize = entryCount * entrySize; _file.setPositionSync(_file.lengthSync() - 4 - freeListSize); List buffer = _file.readSync(freeListSize); List freeList = []; for (int i = 0; i < entryCount; i++) { int offset = i * entrySize; // Pointer List pointerBytes = buffer.sublist(offset, offset + BT_Type.POINTER.size); int pointerAddress = ByteData.sublistView(Uint8List.fromList(pointerBytes)).getInt64(0, Endian.little); BT_Pointer pointer = BT_Pointer(pointerAddress); // Size List sizeBytes = buffer.sublist(offset + BT_Type.POINTER.size, offset + entrySize); int size = ByteData.sublistView(Uint8List.fromList(sizeBytes)).getInt32(0, Endian.little); freeList.add(BT_FreeListEntry(pointer, size)); } return freeList; } set _freeList(List list) { if (freeListLifted) { _freeListCache = list; return; } _file.setPositionSync(_file.lengthSync() - 4); int oldEntryCount = _file.readIntSync(4); int oldListSize = (oldEntryCount * (BT_Type.POINTER.size + 4)) + 4; // Entries + Count _file.truncateSync(_file.lengthSync() - oldListSize); List buffer = list.bt_encode(); _file.setPositionSync(_file.lengthSync()); _file.writeFromSync(buffer); } /// Caches the free list in memory, and removed it from the file. void liftFreeList() { if (freeListLifted) { throw StateError('Free list is already lifted'); } _freeListCache = _freeList; _file.setPositionSync(_file.lengthSync() - 4); int oldEntryCount = _file.readIntSync(4); int oldEntrySize = BT_Type.POINTER.size + 4; // Pointer + Size int oldFreeListSize = oldEntryCount * oldEntrySize + 4; // +4 for entry count _file.truncateSync(_file.lengthSync() - oldFreeListSize); freeListLifted = true; } /// Appends the cached free list back to the file, and clears the cache. void dropFreeList() { if (!freeListLifted) { throw StateError('Free list is not lifted'); } _file.setPositionSync(_file.lengthSync()); _file.writeIntSync(0, 4); // Placeholder for entry count freeListLifted = false; _freeList = _freeListCache!; _freeListCache = null; } void antiFreeListScope(void Function() fn) { liftFreeList(); try { fn(); } finally { dropFreeList(); } } void free(BT_Pointer pointer, int size) { if (!freeListLifted) { throw StateError('Free list must be lifted before freeing memory'); } if (pointer.isNull || size <= 0) { throw ArgumentError('Cannot free null pointer or zero size'); } // Fetch current free list List freeList = _freeList; // Add new free entry freeList.add(BT_FreeListEntry(pointer, size)); // Merge contiguous free entries List mergeContiguousFreeBlocks(List freeList) { if (freeList.isEmpty) return []; // Create a copy and sort by address to check for contiguous blocks List sorted = List.from(freeList); sorted.sort((a, b) => a.pointer.address.compareTo(b.pointer.address)); List merged = []; for (var entry in sorted) { if (merged.isEmpty) { // First entry, just add it merged.add(BT_FreeListEntry(entry.pointer, entry.size)); } else { var last = merged.last; // Check if current entry is contiguous with the last merged entry if (last.pointer.address + last.size == entry.pointer.address) { // Merge: extend the size of the last entry last.size += entry.size; } else { // Not contiguous, add as separate entry merged.add(BT_FreeListEntry(entry.pointer, entry.size)); } } } return merged; } freeList = mergeContiguousFreeBlocks(freeList); // Update free list _freeList = freeList; } BT_Pointer alloc(int size) { if (!freeListLifted) { throw StateError('Free list must be lifted before allocation'); } // Fetch current free list List freeList = _freeList; // If its empty, allocate at end of file if (freeList.isEmpty) { // "[ALLOC] CODE 1: No free blocks available, allocating at end of file".yellow.log(); return BT_Pointer(_file.lengthSync()); } // Find a free block that fits the size (exact fit or larger) BT_FreeListEntry? bestFit; for (var entry in freeList) { if (entry.size >= size) { bestFit = entry; break; } } if (bestFit == null) { // "[ALLOC] CODE 2: No suitable free block found, allocating at end of file".yellow.log(); return BT_Pointer(_file.lengthSync()); } bool exactFit = bestFit.size == size; if (exactFit) { // "[ALLOC] CODE 3: Found exact fit in free block at ${bestFit.pointer} of size ${bestFit.size} bytes".green.log(); // Remove from free list freeList.removePointer(bestFit.pointer); _freeList = freeList; return bestFit.pointer; } else { // "[ALLOC] CODE 4: Found larger free block at ${bestFit.pointer} of size ${bestFit.size} bytes, allocating ${size} bytes. Splitting block, remaining size ${bestFit.size - size} bytes".green.log(); // Allocate from start of block, reduce size and move pointer BT_Pointer allocatedPointer = bestFit.pointer; bestFit = BT_FreeListEntry(BT_Pointer(bestFit.pointer.address + size), bestFit.size - size); // Update free list freeList.removePointer(allocatedPointer); freeList.add(bestFit); _freeList = freeList; return allocatedPointer; } } operator []=(String key, dynamic value) { antiFreeListScope(() { Map addressTable = _addressTable; int keyHash = key.bt_hash; if (addressTable.containsKey(keyHash)) { throw Exception('Key already exists'); } List valueBuffer = encodeValue(value); // Write value to file BT_Pointer valueAddress = alloc(valueBuffer.length); _file.setPositionSync(valueAddress.address); _file.writeFromSync(valueBuffer); // Update address table addressTable[keyHash] = valueAddress; _addressTable = addressTable; }); } operator [](String key) { Map addressTable = _addressTable; int keyHash = key.bt_hash; if (!addressTable.containsKey(keyHash)) { throw Exception('Key does not exist'); } BT_Pointer valuePointer = addressTable[keyHash]!; BT_Reference valueRef = BT_Reference(this, valuePointer); return valueRef.decodeValue(); } void delete(String key) { antiFreeListScope(() { Map addressTable = _addressTable; int keyHash = key.bt_hash; if (!addressTable.containsKey(keyHash)) { throw Exception('Key does not exist'); } BT_Pointer valuePointer = addressTable[keyHash]!; BT_Reference valueRef = BT_Reference(this, valuePointer); // Free the value free(valuePointer, valueRef.size); // Remove from address table addressTable.remove(keyHash); _addressTable = addressTable; }); } void truncate() { antiFreeListScope(() { // Relocate the address table if possible _addressTable = _addressTable; // Check if the last free block is at the end of the file List freeList = _freeList; freeList.sort((a, b) => a.pointer.address.compareTo(b.pointer.address)); if (freeList.isEmpty) { return; } BT_FreeListEntry lastEntry = freeList.last; int fileEnd = _file.lengthSync(); int expectedEnd = lastEntry.pointer.address + lastEntry.size; if (expectedEnd != fileEnd) { return; } // Remove the last entry from the free list freeList.removeLast(); _freeList = freeList; // Truncate the file int newLength = lastEntry.pointer.address; _file.truncateSync(newLength); }); } } void main() { File file = File('example.bin'); if (file.existsSync()) { file.deleteSync(); } file.createSync(); BinaryTable table = BinaryTable(file.path); table.initialise(); print("File dump:"); print(binaryDump(file.readAsBytesSync())); print("File size: ${file.lengthSync()} bytes"); print(" "); table["int_array"] = [6, 3, 9, 2, 5]; table["float_array"] = [1.5, 2.5, 3.5]; table["empty"] = []; table["int_array"][0] = 1; table["float_array"][1] = 4.5; print("int_array pointer: ${table["int_array"]._pointer}"); print("float_array pointer: ${table["float_array"]._pointer}"); table["int_array"].add(10); table["float_array"].add(5.5); table["int_array"].addAll([420, 69, 1337, 1738]); table["float_array"].addAll([6.5, 7.5, 8.5]); // Test the full read method var intArrayFull = table["int_array"].fetchSublist(0, 3); var floatArrayFull = table["float_array"].fetchSublist(0, 2); print("Full read int_array (0-3): $intArrayFull"); print("Full read float_array (0-2): $floatArrayFull"); var readback1 = table["int_array"]; var readback2 = table["float_array"]; var readback3 = table["empty"]; print("Readback1: $readback1"); print("Readback2: $readback2"); print("Readback3: $readback3"); print(" "); print("File dump:"); print(binaryDump(file.readAsBytesSync())); print("File size: ${file.lengthSync()} bytes"); } extension on RandomAccessFile { // Read/Write Int Dynamic - Can specify size in bytes, does not have to align to 1, 2, 4, or 8 bytes. Default is 4 bytes (Int32) int readIntSync([int size = 4, Endian endianness = Endian.little]) { if (size < 1 || size > 8) { throw ArgumentError('Size must be between 1 and 8 bytes'); } List bytes = readSync(size); // Build integer from bytes with proper endianness int result = 0; if (endianness == Endian.little) { for (int i = size - 1; i >= 0; i--) { result = (result << 8) | bytes[i]; } } else { for (int i = 0; i < size; i++) { result = (result << 8) | bytes[i]; } } // Sign extend if MSB is set int signBit = 1 << (size * 8 - 1); if (result & signBit != 0) { result -= 1 << (size * 8); } return result; } void writeIntSync(int value, [int size = 4, Endian endianness = Endian.little]) { if (size < 1 || size > 8) { throw ArgumentError('Size must be between 1 and 8 bytes'); } List bytes = List.filled(size, 0); // Extract bytes with proper endianness if (endianness == Endian.little) { for (int i = 0; i < size; i++) { bytes[i] = (value >> (i * 8)) & 0xFF; } } else { for (int i = 0; i < size; i++) { bytes[size - 1 - i] = (value >> (i * 8)) & 0xFF; } } writeFromSync(bytes); } // Read/Write Pointers BT_Pointer readPointerSync() { int offset = readIntSync(BT_Type.POINTER.size); return BT_Pointer(offset); } void writePointerSync(BT_Pointer pointer) { writeIntSync(pointer.address, BT_Type.POINTER.size); } // Read/Write Float32 double readFloat32Sync([Endian endianness = Endian.little]) { List bytes = readSync(4); return ByteData.sublistView(Uint8List.fromList(bytes)).getFloat32(0, endianness); } void writeFloat32Sync(double value, [Endian endianness = Endian.little]) { ByteData byteData = ByteData(4); byteData.setFloat32(0, value, endianness); writeFromSync(byteData.buffer.asUint8List()); } // Read/Write Float64 (Double) double readFloat64Sync([Endian endianness = Endian.little]) { List bytes = readSync(8); return ByteData.sublistView(Uint8List.fromList(bytes)).getFloat64(0, endianness); } void writeFloat64Sync(double value, [Endian endianness = Endian.little]) { ByteData byteData = ByteData(8); byteData.setFloat64(0, value, endianness); writeFromSync(byteData.buffer.asUint8List()); } } String binaryDump(Uint8List data) { StringBuffer buffer = StringBuffer(); for (int i = 0; i < data.length; i += 16) { // Address buffer.write('0x${i.toRadixString(16).padLeft(4, '0').toUpperCase()} (${i.toString().padLeft(4)}) | '); // Hex bytes for (int j = 0; j < 16; j++) { if (i + j < data.length) { buffer.write('${data[i + j].toRadixString(16).padLeft(2, '0').toUpperCase()} '); } else { buffer.write(' '); } } buffer.write(' | '); // Integer representation for (int j = 0; j < 16; j++) { if (i + j < data.length) { buffer.write('${data[i + j].toString().padLeft(3)} '); } else { buffer.write(' '); } } buffer.write(' | '); // ASCII representation for (int j = 0; j < 16; j++) { if (i + j < data.length) { int byte = data[i + j]; if (byte >= 32 && byte <= 126) { buffer.write(String.fromCharCode(byte)); } else { buffer.write('.'); } } } buffer.write(' | '); if (i + 16 < data.length) buffer.writeln(); } return buffer.toString(); }