560 lines
13 KiB
Dart
560 lines
13 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
class ByteLayer {
|
|
Uint8List _byteBuffer = Uint8List(0);
|
|
int _position = 0;
|
|
|
|
ByteLayer() {
|
|
_byteBuffer = Uint8List(0); // Initial size, can be adjusted
|
|
_position = 0;
|
|
}
|
|
|
|
int get length => _byteBuffer.length;
|
|
|
|
int get currentPosition => _position;
|
|
|
|
Uint8List get bytes => _byteBuffer.sublist(0, _position);
|
|
|
|
void _ensureCapacity(int additionalBytes) {
|
|
final requiredCapacity = _position + additionalBytes;
|
|
if (requiredCapacity > _byteBuffer.length) {
|
|
final newCapacity =
|
|
_position + additionalBytes; // Adjust capacity as needed
|
|
final newBuffer = Uint8List(newCapacity);
|
|
newBuffer.setAll(0, _byteBuffer);
|
|
_byteBuffer = newBuffer;
|
|
}
|
|
}
|
|
|
|
void writeInt(int value) {
|
|
_ensureCapacity(4);
|
|
_byteBuffer.buffer.asByteData().setInt32(_position, value, Endian.big);
|
|
_position += 4;
|
|
}
|
|
|
|
void writeDouble(double value) {
|
|
_ensureCapacity(8);
|
|
_byteBuffer.buffer.asByteData().setFloat64(_position, value, Endian.big);
|
|
_position += 8;
|
|
}
|
|
|
|
void writeString(String value) {
|
|
final encoded = utf8.encode(value);
|
|
writeShort(encoded.length);
|
|
_ensureCapacity(encoded.length);
|
|
_byteBuffer.setAll(_position, encoded);
|
|
_position += encoded.length;
|
|
}
|
|
|
|
int readInt() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getInt32(_position, Endian.big);
|
|
_position += 4;
|
|
return value;
|
|
}
|
|
|
|
double readDouble() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getFloat64(_position, Endian.big);
|
|
_position += 8;
|
|
return value;
|
|
}
|
|
|
|
String readString() {
|
|
final length = readShort();
|
|
final encoded = _byteBuffer.sublist(_position, _position + length);
|
|
_position += length;
|
|
return utf8.decode(encoded);
|
|
}
|
|
|
|
void writeIntZigZag(int value) {
|
|
final zigzag = (value << 1) ^ (value >> 31);
|
|
writeInt(zigzag);
|
|
}
|
|
|
|
int readIntZigZag() {
|
|
final zigzag = readInt();
|
|
final value = (zigzag >> 1) ^ -(zigzag & 1);
|
|
return value;
|
|
}
|
|
|
|
void writeByte(int value) {
|
|
_ensureCapacity(1);
|
|
_byteBuffer[_position] = value & 0xFF;
|
|
_position++;
|
|
}
|
|
|
|
void writeUnsignedByte(int value) {
|
|
_ensureCapacity(1);
|
|
_byteBuffer.buffer.asByteData().setUint8(_position, value);
|
|
_position++;
|
|
}
|
|
|
|
int readByte() {
|
|
final value = _byteBuffer[_position];
|
|
_position++;
|
|
return value;
|
|
}
|
|
|
|
int readUnsignedByte() {
|
|
final value = _byteBuffer.buffer.asByteData().getUint8(_position);
|
|
_position++;
|
|
return value;
|
|
}
|
|
|
|
void writeVarInt(int value) {
|
|
while ((value & ~0x7F) != 0) {
|
|
writeByte((value & 0x7F) | 0x80);
|
|
value = (value >> 7) & 0x1FFFFFFF;
|
|
}
|
|
writeByte(value & 0x7F);
|
|
}
|
|
|
|
int readVarInt() {
|
|
int result = 0;
|
|
int shift = 0;
|
|
int byte;
|
|
do {
|
|
byte = readByte();
|
|
result |= (byte & 0x7F) << shift;
|
|
if ((byte & 0x80) == 0) {
|
|
break;
|
|
}
|
|
shift += 7;
|
|
} while (true);
|
|
|
|
return result;
|
|
}
|
|
|
|
void writeVarIntNoZigZag(int value) {
|
|
while ((value & ~0x7F) != 0) {
|
|
writeByte((value & 0x7F) | 0x80);
|
|
value >>= 7;
|
|
}
|
|
writeByte(value & 0x7F);
|
|
}
|
|
|
|
int readVarIntNoZigZag() {
|
|
int result = 0;
|
|
int shift = 0;
|
|
int byte;
|
|
do {
|
|
byte = readByte();
|
|
result |= (byte & 0x7F) << shift;
|
|
if ((byte & 0x80) == 0) {
|
|
break;
|
|
}
|
|
shift += 7;
|
|
} while (true);
|
|
|
|
return result;
|
|
}
|
|
|
|
void writeShort(int value) {
|
|
_ensureCapacity(2);
|
|
_byteBuffer.buffer.asByteData().setInt16(_position, value, Endian.big);
|
|
_position += 2;
|
|
}
|
|
|
|
int readShort() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getInt16(_position, Endian.big);
|
|
_position += 2;
|
|
return value;
|
|
}
|
|
|
|
void writeFloat(double value) {
|
|
_ensureCapacity(4);
|
|
// Round the float to 8 decimal places before writing
|
|
value = double.parse(value.toStringAsFixed(8));
|
|
_byteBuffer.buffer.asByteData().setFloat32(_position, value, Endian.big);
|
|
_position += 4;
|
|
}
|
|
|
|
double readFloat() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getFloat32(_position, Endian.big);
|
|
|
|
_position += 4;
|
|
|
|
// Round the float to 8 decimal places after reading
|
|
return double.parse(value.toStringAsFixed(8));
|
|
}
|
|
|
|
void writeTagName(String name) {
|
|
final encodedName = utf8.encode(name);
|
|
writeShort(encodedName.length);
|
|
_ensureCapacity(encodedName.length);
|
|
_byteBuffer.setAll(_position, encodedName);
|
|
_position += encodedName.length;
|
|
}
|
|
|
|
String readTagName() {
|
|
final length = readShort();
|
|
final encodedName = _byteBuffer.sublist(_position, _position + length);
|
|
_position += length;
|
|
return utf8.decode(encodedName);
|
|
}
|
|
|
|
void resetPosition() {
|
|
_position = 0;
|
|
}
|
|
|
|
void restorePosition(int position) {
|
|
_position = position;
|
|
}
|
|
|
|
void clear() {
|
|
resetPosition();
|
|
_byteBuffer = Uint8List(0);
|
|
}
|
|
|
|
Future<void> writeToFile(String filePath) async {
|
|
final file = File(filePath);
|
|
if (file.existsSync())
|
|
file.deleteSync(); // Ensure we flush the file to 0 bytes
|
|
await file.writeAsBytes(bytes);
|
|
}
|
|
|
|
Future<void> readFromFile(String filePath) async {
|
|
final file = File(filePath);
|
|
final exists = await file.exists();
|
|
if (!exists) {
|
|
print('File does not exist.');
|
|
return;
|
|
}
|
|
|
|
_byteBuffer = await file.readAsBytes();
|
|
resetPosition();
|
|
}
|
|
|
|
Future<void> compress() async {
|
|
final gzip = GZipCodec();
|
|
final compressedData = gzip.encode(_byteBuffer);
|
|
_byteBuffer = Uint8List.fromList(compressedData);
|
|
_position = _byteBuffer.length;
|
|
}
|
|
|
|
Future<void> decompress() async {
|
|
final gzip = GZipCodec();
|
|
final decompressedData = gzip.decode(_byteBuffer);
|
|
_byteBuffer = Uint8List.fromList(decompressedData);
|
|
_position = _byteBuffer.length;
|
|
}
|
|
|
|
void writeLong(int value) {
|
|
_ensureCapacity(8);
|
|
_byteBuffer.buffer.asByteData().setInt64(_position, value, Endian.big);
|
|
_position += 8;
|
|
}
|
|
|
|
void writeUnsignedLong(int value) {
|
|
_ensureCapacity(8);
|
|
_byteBuffer.buffer.asByteData().setUint64(_position, value, Endian.big);
|
|
_position += 8;
|
|
}
|
|
|
|
int readLong() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getInt64(_position, Endian.big);
|
|
_position += 8;
|
|
return value;
|
|
}
|
|
|
|
int readUnsignedLong() {
|
|
final value =
|
|
_byteBuffer.buffer.asByteData().getUint64(_position, Endian.big);
|
|
_position += 8;
|
|
return value;
|
|
}
|
|
|
|
void writeVarLongNoZigZag(int value) {
|
|
while (true) {
|
|
if ((value & ~0x7F) == 0) {
|
|
writeByte(value);
|
|
return;
|
|
}
|
|
writeByte((value & 0x7F) | 0x80);
|
|
value = value >> 7;
|
|
}
|
|
}
|
|
|
|
void writeVarLongZigZag(int value) {
|
|
value = (value << 1) ^ (value >> 63);
|
|
writeVarLongNoZigZag(value);
|
|
}
|
|
|
|
void writeLongZigZag(int value) {
|
|
value = (value << 1) & (value >> 63);
|
|
_byteBuffer.buffer.asByteData().setInt64(_position, value, Endian.big);
|
|
}
|
|
|
|
int readVarLongNoZigZag() {
|
|
int result = 0;
|
|
int shift = 0;
|
|
int b;
|
|
|
|
do {
|
|
b = readByte();
|
|
result |= (b & 0x7F) << shift;
|
|
shift += 7;
|
|
} while ((b & 0x80) != 0);
|
|
|
|
return result;
|
|
}
|
|
|
|
int readVarLongZigZag() {
|
|
int result = readVarLongNoZigZag();
|
|
return (result >> 1) ^ -(result & 1);
|
|
}
|
|
|
|
int readLongZigZag() {
|
|
int value = readLong();
|
|
return (value >> 1) ^ (-(value & 1));
|
|
}
|
|
|
|
void writeBytes(Iterable<int> bytes) {
|
|
for (int byte in bytes) {
|
|
writeUnsignedByte(byte);
|
|
}
|
|
}
|
|
|
|
List<int> readBytes(int num) {
|
|
List<int> lst = [];
|
|
for (int i = 0; i < num; i++) {
|
|
lst.add(readUnsignedByte());
|
|
}
|
|
|
|
return lst;
|
|
}
|
|
|
|
void setBit(int position, int maskToSet) {
|
|
if (position < _byteBuffer.length) {
|
|
// Set the value now
|
|
seek(position);
|
|
int current = readUnsignedByte();
|
|
seek(position);
|
|
current |= maskToSet;
|
|
writeUnsignedByte(current);
|
|
}
|
|
}
|
|
|
|
void clearBit(int position, int maskToClear) {
|
|
if (position < _byteBuffer.length) {
|
|
// Lets clear the bit
|
|
seek(position);
|
|
int current = readUnsignedByte();
|
|
current = current & ~maskToClear;
|
|
seek(position);
|
|
writeUnsignedByte(current);
|
|
}
|
|
}
|
|
|
|
bool checkBit(int position, int mask) {
|
|
if (position < _byteBuffer.length) {
|
|
seek(position);
|
|
int current = readUnsignedByte();
|
|
return (current & mask) == mask;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int getBit(int position) {
|
|
if (position < _byteBuffer.length) {
|
|
seek(position);
|
|
return readUnsignedByte();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void seek(int position) {
|
|
_position = 0;
|
|
_ensureCapacity(position);
|
|
_position = position;
|
|
}
|
|
|
|
/// Unsets a mask, then sets a mask
|
|
void unsetSetBit(int position, int maskToClear, int maskToSet) {
|
|
clearBit(position, maskToClear);
|
|
setBit(position, maskToSet);
|
|
}
|
|
|
|
void insertRandomBytes(int count) {
|
|
Random rng = Random();
|
|
for (int a = 0; a < count; a++) {
|
|
writeByte(rng.nextInt(255));
|
|
}
|
|
}
|
|
}
|
|
|
|
class StringBuilder {
|
|
String _buffer = "";
|
|
|
|
StringBuilder();
|
|
|
|
bool get isEmpty => _buffer.isEmpty;
|
|
|
|
void append(String value) {
|
|
_buffer += value;
|
|
}
|
|
|
|
void clear() {
|
|
_buffer = "";
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "${_buffer}";
|
|
}
|
|
}
|
|
|
|
class StringReader {
|
|
final String _buffer;
|
|
int _position = 0;
|
|
int _lastPostion = 0;
|
|
bool _quotedString = false;
|
|
|
|
StringReader(this._buffer);
|
|
|
|
/// Check if there's more to read
|
|
bool get canRead => _canRead();
|
|
bool _canRead() {
|
|
skipWhitespace();
|
|
return _position < _buffer.length;
|
|
}
|
|
|
|
// Get the number of chars seeked
|
|
int get getSeeked => _lastPostion - _position;
|
|
|
|
// Read the next character
|
|
String next() {
|
|
if (canRead) {
|
|
skipWhitespace();
|
|
return _buffer[_position++];
|
|
} else {
|
|
throw Exception("End of buffer reached");
|
|
}
|
|
}
|
|
|
|
/// Generates a snapshot of the text location if applicable
|
|
String getSnapshot() {
|
|
if (canRead) {
|
|
if (_position + 64 < _buffer.length) {
|
|
return _buffer.substring(_position, _position + 64);
|
|
} else {
|
|
return _buffer.substring(_position);
|
|
}
|
|
} else
|
|
return "";
|
|
}
|
|
|
|
// Peek the next character without advancing the position
|
|
String peek() {
|
|
skipWhitespace();
|
|
if (canRead) {
|
|
return _buffer[_position];
|
|
} else {
|
|
throw Exception("End of buffer reached");
|
|
}
|
|
}
|
|
|
|
// Skip any whitespace characters
|
|
void skipWhitespace() {
|
|
if (_quotedString) return; // We need whitespace for strings
|
|
while ((_position < _buffer.length) && isWhitespace(_buffer[_position])) {
|
|
_position++;
|
|
}
|
|
}
|
|
|
|
// Check if a character is a whitespace
|
|
bool isWhitespace(String char) {
|
|
return char.trim().isEmpty;
|
|
}
|
|
|
|
// Read until a specific character is found
|
|
String readUntil(String stopChar, int maxChars) {
|
|
StringBuffer result = StringBuffer();
|
|
int index = 0;
|
|
while (canRead && peek() != stopChar && index < maxChars) {
|
|
result.write(next());
|
|
index++;
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
// Read a string enclosed in double quotes
|
|
String readQuotedString() {
|
|
_quotedString = true;
|
|
if (next() != '"') {
|
|
throw Exception('Expected double quotes at the start of a string');
|
|
}
|
|
StringBuffer result = StringBuffer();
|
|
while (canRead) {
|
|
String char = next();
|
|
if (char == '"') {
|
|
break;
|
|
}
|
|
result.write(char);
|
|
}
|
|
_quotedString = false;
|
|
return result.toString();
|
|
}
|
|
|
|
// Read a number (int or double)
|
|
String readNumber() {
|
|
StringBuffer result = StringBuffer();
|
|
while (canRead && (isDigit(peek()) || peek() == '.' || peek() == '-')) {
|
|
result.write(next());
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
// Check if a character is a digit
|
|
bool isDigit(String char) {
|
|
return int.tryParse(char) != null;
|
|
}
|
|
|
|
// Read an unquoted string (used for keys in SNBT)
|
|
String readUnquotedString() {
|
|
StringBuffer result = StringBuffer();
|
|
while (canRead &&
|
|
!isWhitespace(peek()) &&
|
|
peek() != ':' &&
|
|
peek() != ',' &&
|
|
peek() != '{' &&
|
|
peek() != '}' &&
|
|
peek() != '[' &&
|
|
peek() != ']') {
|
|
result.write(next());
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
String readString() {
|
|
if (peek() == "\"") {
|
|
return readQuotedString();
|
|
} else
|
|
return readUnquotedString();
|
|
}
|
|
|
|
// Read a specific character and throw an exception if it's not found
|
|
void expect(String expectedChar) {
|
|
if (next().toLowerCase() != expectedChar.toLowerCase()) {
|
|
throw Exception('Expected $expectedChar');
|
|
}
|
|
}
|
|
|
|
void startSeek() {
|
|
_lastPostion = _position;
|
|
}
|
|
|
|
void endSeek() {
|
|
_position = _lastPostion;
|
|
_lastPostion = 0;
|
|
}
|
|
}
|