Fix #3: Implements converting to and from SNBT #9

Merged
zontreck merged 50 commits from feat/3 into main 2024-06-07 01:16:15 +00:00
34 changed files with 1785 additions and 157 deletions

4
.gitignore vendored
View file

@ -29,4 +29,6 @@ doc/api/
.idea
*.iml
*.iml
out

24
bin/client_test.dart Normal file
View file

@ -0,0 +1,24 @@
import 'package:libac_dart/nbt/Stream.dart';
import 'package:libac_dart/nbt/Tag.dart';
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
import 'package:libac_dart/packets/packets.dart';
void main() async {
PacketRegistry reg = PacketRegistry();
reg.registerDefaults();
PacketClient client = PacketClient();
await client.startConnect("127.0.0.1", 25306);
S2CResponse response = await client.send(C2SPing(), true);
CompoundTag tag = response.contents;
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
print("Response from server: \n${builder}");
await client.send(StopServerPacket(), false);
if (client.connected) await client.close();
return;
}

5
bin/server_test.dart Normal file
View file

@ -0,0 +1,5 @@
import 'package:libac_dart/packets/packets.dart';
void main() async {
await PacketServer.start(25306);
}

7
compile.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
mkdir out
rm -rf out/*
flutter pub publish -f --skip-validation
dart compile exe -o out/server_test bin/server_test.dart
dart compile exe -o out/client_test bin/client_test.dart

View file

@ -1,6 +1,9 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'dart:typed_data';
import '../utils/Converter.dart';
import 'Stream.dart';
import 'Tag.dart';
import 'impl/CompoundTag.dart';
class NbtIo {
static ByteLayer _io = ByteLayer();
@ -49,4 +52,44 @@ class NbtIo {
static ByteLayer getStream() {
return _io;
}
static Future<String> writeBase64String(CompoundTag tag) async {
_io = ByteLayer();
Tag.writeNamedTag(tag, _io);
_io.compress();
return base64Encoder.encode(_io.bytes);
}
static Future<CompoundTag> readBase64String(String encoded) async {
_io = ByteLayer();
List<int> bytes = base64Encoder.decode(encoded);
for (int byte in bytes) {
_io.writeByte(byte);
}
_io.decompress();
_io.resetPosition();
return Tag.readNamedTag(_io) as CompoundTag;
}
static Future<Uint8List> writeToStream(CompoundTag tag) async {
_io = ByteLayer();
Tag.writeNamedTag(tag, _io);
_io.compress();
return _io.bytes;
}
static Future<CompoundTag> readFromStream(Uint8List list) async {
_io = ByteLayer();
try {
_io.writeBytes(list);
_io.resetPosition();
_io.decompress();
_io.resetPosition();
} catch (E) {
print(E);
} finally {
return Tag.readNamedTag(_io) as CompoundTag;
}
}
}

View file

@ -1,13 +1,12 @@
import 'package:libac_flutter/nbt/impl/ByteTag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/DoubleTag.dart';
import 'package:libac_flutter/nbt/impl/IntArrayTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:libac_flutter/nbt/impl/ListTag.dart';
import 'package:libac_flutter/utils/Vector3.dart';
import '../utils/Vector2.dart';
import '../utils/uuid/UUID.dart';
import '../utils/Vector3.dart';
import '../utils/uuid/NbtUUID.dart';
import 'impl/ByteTag.dart';
import 'impl/CompoundTag.dart';
import 'impl/DoubleTag.dart';
import 'impl/IntArrayTag.dart';
import 'impl/IntTag.dart';
import 'impl/ListTag.dart';
class NbtUtils {
static void writeBoolean(CompoundTag tag, String name, bool b) {
@ -15,7 +14,7 @@ class NbtUtils {
}
static bool readBoolean(CompoundTag tag, String name) {
if (tag.contains(name)) {
if (tag.containsKey(name)) {
return tag.get(name)!.asByte() == 1 ? true : false;
} else {
return false;
@ -30,7 +29,7 @@ class NbtUtils {
}
static Vector2d readVector2d(CompoundTag tag, String name) {
if (tag.contains(name)) {
if (tag.containsKey(name)) {
ListTag lst = tag.get(name)! as ListTag;
return Vector2d(X: lst.get(0).asDouble(), Z: lst.get(1).asDouble());
} else {
@ -46,7 +45,7 @@ class NbtUtils {
}
static Vector2i readVector2i(CompoundTag tag, String name) {
if (tag.contains(name)) {
if (tag.containsKey(name)) {
ListTag lst = tag.get(name)! as ListTag;
return Vector2i(X: lst.get(0).asInt(), Z: lst.get(1).asInt());
} else {
@ -63,7 +62,7 @@ class NbtUtils {
}
static Vector3d readVector3d(CompoundTag tag, String name) {
if (tag.contains(name)) {
if (tag.containsKey(name)) {
ListTag lst = tag.get(name)! as ListTag;
return Vector3d(
X: lst.get(0).asDouble(),
@ -83,7 +82,7 @@ class NbtUtils {
}
static Vector3i readVector3i(CompoundTag tag, String name) {
if (tag.contains(name)) {
if (tag.containsKey(name)) {
ListTag lst = tag.get(name)! as ListTag;
return Vector3i(
X: lst.get(0).asInt(), Y: lst.get(1).asInt(), Z: lst.get(2).asInt());
@ -96,24 +95,25 @@ class NbtUtils {
return [msb >> 32, msb, lsb >> 32, lsb];
}
static List<int> _uuidToIntArray(UUID ID) {
static List<int> _uuidToIntArray(NbtUUID ID) {
return _msbLsbToIntArray(
ID.getMostSignificantBits(), ID.getLeastSignificantBits());
}
static UUID _uuidFromIntArray(List<int> values) {
return UUID((values[0] << 32 | values[1] & 4294967295),
static NbtUUID _uuidFromIntArray(List<int> values) {
return NbtUUID((values[0] << 32 | values[1] & 4294967295),
(values[2] << 32 | values[3] & 4294967295));
}
static void writeUUID(CompoundTag tag, String name, UUID ID) {
static void writeUUID(CompoundTag tag, String name, NbtUUID ID) {
tag.put(name, IntArrayTag.valueOf(_uuidToIntArray(ID)));
}
static UUID readUUID(CompoundTag tag, String name) {
if (!tag.contains(name))
return UUID.ZERO;
else
static NbtUUID readUUID(CompoundTag tag, String name) {
if (!tag.containsKey(name)) {
return NbtUUID.ZERO;
} else {
return _uuidFromIntArray(tag.get(name)!.asIntArray());
}
}
}

36
lib/nbt/SnbtIo.dart Normal file
View file

@ -0,0 +1,36 @@
import 'dart:io';
import 'Stream.dart';
import 'Tag.dart';
import 'impl/CompoundTag.dart';
class SnbtIo {
static void writeToFile(String file, CompoundTag tag) {
File handle = File(file);
if (handle.existsSync())
handle.deleteSync(); // Ensure we flush the file to 0 bytes
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
handle.writeAsString(builder.toString());
}
static Future<Tag> readFromFile(String file) async {
File fi = File(file);
String data = await fi.readAsString();
StringReader reader = StringReader(data);
return Tag.readStringifiedNamedTag(reader);
}
static String writeToString(CompoundTag tag) {
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
return builder.toString();
}
static Future<Tag> readFromString(String data) async {
return Tag.readStringifiedNamedTag(StringReader(data));
}
}

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
class ByteLayer {
@ -209,6 +210,8 @@ class ByteLayer {
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);
}
@ -351,8 +354,9 @@ class ByteLayer {
seek(position);
int current = readUnsignedByte();
return (current & mask) == mask;
} else
} else {
return false;
}
}
int getBit(int position) {
@ -375,4 +379,156 @@ class ByteLayer {
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;
StringReader(this._buffer);
// Check if there's more to read
bool get canRead => _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");
}
}
// 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() {
while (canRead && 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) {
StringBuffer result = StringBuffer();
while (canRead && peek() != stopChar) {
result.write(next());
}
return result.toString();
}
// Read a string enclosed in double quotes
String readQuotedString() {
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);
}
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;
}
}

View file

@ -1,17 +1,16 @@
import 'package:libac_flutter/nbt/impl/ByteArrayTag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/DoubleTag.dart';
import 'package:libac_flutter/nbt/impl/EndTag.dart';
import 'package:libac_flutter/nbt/impl/FloatTag.dart';
import 'package:libac_flutter/nbt/impl/IntArrayTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:libac_flutter/nbt/impl/LongTag.dart';
import 'package:libac_flutter/nbt/impl/ShortTag.dart';
import 'Stream.dart';
import 'impl/ByteArrayTag.dart';
import 'impl/ByteTag.dart';
import 'impl/CompoundTag.dart';
import 'impl/DoubleTag.dart';
import 'impl/EndTag.dart';
import 'impl/FloatTag.dart';
import 'impl/IntArrayTag.dart';
import 'impl/IntTag.dart';
import 'impl/ListTag.dart';
import 'impl/LongArrayTag.dart';
import 'impl/LongTag.dart';
import 'impl/ShortTag.dart';
import 'impl/StringTag.dart';
enum TagType {
@ -41,6 +40,105 @@ enum TagType {
return TagType.End;
}
static TagType getStringifiedTagType(StringReader reader) {
reader.startSeek();
TagType ret = TagType.End;
bool isNumber = true;
// Start to determine the next tag type
while (reader.canRead && ret == TagType.End) {
var val = reader.next().toUpperCase();
switch (val) {
case "{":
{
ret = TagType.Compound;
break;
}
case "[":
{
// Check for a type Prefix
var X = reader.readUntil(";");
switch (X.toUpperCase()) {
case "B":
{
ret = TagType.ByteArray;
break;
}
case "I":
{
ret = TagType.IntArray;
break;
}
case "L":
{
ret = TagType.LongArray;
break;
}
default:
{
ret = TagType.List;
break;
}
}
break;
}
case "B":
{
ret = TagType.Byte;
break;
}
case "D":
{
ret = TagType.Double;
break;
}
case "F":
{
ret = TagType.Float;
break;
}
case "I":
{
ret = TagType.Int;
break;
}
case "L":
{
ret = TagType.Long;
break;
}
case "S":
{
ret = TagType.Short;
break;
}
case "\"":
{
ret = TagType.String;
break;
}
case ",":
case "\n":
{
if (reader.getSeeked >= 1) ret = TagType.String;
if (isNumber) ret = TagType.Int;
break;
}
default:
{
if (!reader.isDigit(val)) {
if (isNumber) isNumber = false;
}
break;
}
}
}
reader.endSeek();
return ret;
}
}
abstract class Tag {
@ -82,6 +180,60 @@ abstract class Tag {
}
}
static void writeStringifiedNamedTag(
Tag tag, StringBuilder builder, int indents) {
if (tag.getType() != 0) {
if (!builder.isEmpty) {
// Write name
if (tag._key == "") {
builder.append("${"".padLeft(indents, '\t')}");
} else {
if (tag.shouldQuoteName()) {
builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": ");
} else
builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: ");
}
}
tag.writeStringifiedValue(builder, indents + 1, false);
}
}
static Tag readStringifiedNamedTag(StringReader string) {
String name = "";
if (string.peek() == "{" || string.peek() == "[") {
// No name
name = "";
} else {
name = string.readString();
string.expect(":");
}
TagType type = TagType.getStringifiedTagType(string);
Tag tag = Tag.makeTagOfType(type);
tag._key = name;
tag.readStringifiedValue(string);
return tag;
}
bool shouldQuoteName() {
if (getKey() == "") {
return false;
} else {
String letters = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i < getKey().length; i++) {
String digit = getKey().substring(i, i + 1);
if (letters.indexOf(digit) == -1) {
return true;
}
}
return false;
}
}
void writeStringifiedValue(StringBuilder builder, int indent, bool isList);
void readStringifiedValue(StringReader reader);
bool equals(dynamic object) {
if (object == null || object is! Tag) return false;
@ -232,6 +384,13 @@ abstract class Tag {
}
}
CompoundTag asCompoundTag() {
if (this is CompoundTag) {
return this as CompoundTag;
} else
return CompoundTag();
}
void prettyPrint(int indent, bool recurse);
static String getCanonicalName(TagType type) {

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class ByteArrayTag extends Tag {
final List<int> value = [];
@ -46,4 +46,24 @@ class ByteArrayTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append(
"${isList ? "".padLeft(indent, '\t') : ""}[B; ${value.join("B, ")}B]");
}
@override
void readStringifiedValue(StringReader reader) {
reader.expect("[");
reader.expect("B");
reader.expect(";");
while (reader.peek() != "]") {
value.add(int.parse(reader.readNumber()));
reader.expect("b");
if (reader.peek() == ",") reader.next();
}
reader.expect("]");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class ByteTag extends Tag {
int value = 0;
@ -34,4 +34,17 @@ class ByteTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${value}b");
}
@override
void readStringifiedValue(StringReader reader) {
String val = reader.readNumber();
value = int.parse(val);
reader.expect("b");
}
}

View file

@ -1,7 +1,7 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class CompoundTag extends Tag {
class CompoundTag extends Tag implements Map<String, Tag> {
late final Map<String, Tag> value = {};
CompoundTag();
@ -48,12 +48,8 @@ class CompoundTag extends Tag {
tag.setKey(name);
}
bool contains(String name) {
return value.containsKey(name);
}
Tag? get(String name) {
if (contains(name)) {
if (containsKey(name)) {
return value[name] as Tag;
} else {
// Does not exist!
@ -61,10 +57,6 @@ class CompoundTag extends Tag {
}
}
void remove(String name) {
value.remove(name);
}
@override
TagType getTagType() {
return TagType.Compound;
@ -85,4 +77,131 @@ class CompoundTag extends Tag {
void endPrettyPrint(int indent) {
print("${"".padLeft(indent, '\t')}}");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
Iterator<Tag> it = value.values.iterator;
builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}{\n");
bool firstEntry = true;
while (it.moveNext()) {
Tag t = it.current;
if (firstEntry) {
firstEntry = false;
} else {
builder.append(",\n");
}
Tag.writeStringifiedNamedTag(t, builder, indent);
}
builder.append("\n${"".padLeft(indent - 1, '\t')}}");
}
@override
Tag? operator [](Object? key) {
return value[key];
}
@override
void operator []=(String key, Tag value) {
this.value[key] = value;
}
@override
void addAll(Map<String, Tag> other) {
value.addAll(other);
}
@override
void addEntries(Iterable<MapEntry<String, Tag>> newEntries) {
value.addEntries(newEntries);
}
@override
Map<RK, RV> cast<RK, RV>() {
return value.cast();
}
@override
void clear() {
value.clear();
}
@override
bool containsKey(Object? key) {
return value.containsKey(key);
}
@override
bool containsValue(Object? value) {
return this.value.containsValue(value);
}
@override
Iterable<MapEntry<String, Tag>> get entries => value.entries;
@override
void forEach(void Function(String key, Tag value) action) {
value.forEach(action);
}
@override
bool get isEmpty => value.isEmpty;
@override
bool get isNotEmpty => value.isNotEmpty;
@override
Iterable<String> get keys => value.keys;
@override
int get length => value.length;
@override
Map<K2, V2> map<K2, V2>(
MapEntry<K2, V2> Function(String key, Tag value) convert) {
return value.map(convert);
}
@override
Tag putIfAbsent(String key, Tag Function() ifAbsent) {
return this.value.putIfAbsent(key, ifAbsent);
}
@override
Tag? remove(Object? key) {
return value.remove(key);
}
@override
void removeWhere(bool Function(String key, Tag value) test) {
value.removeWhere(test);
}
@override
Tag update(String key, Tag Function(Tag value) update,
{Tag Function()? ifAbsent}) {
return value.update(key, update);
}
@override
void updateAll(Tag Function(String key, Tag value) update) {
value.updateAll(update);
}
@override
Iterable<Tag> get values => value.values;
@override
void readStringifiedValue(StringReader reader) {
reader.expect("{");
while (reader.peek() != "}") {
Tag tag = Tag.readStringifiedNamedTag(reader);
put(tag.getKey(), tag);
if (reader.peek() == ",") reader.next();
}
reader.expect("}");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class DoubleTag extends Tag {
double value = 0.0;
@ -34,4 +34,17 @@ class DoubleTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}d");
}
@override
void readStringifiedValue(StringReader reader) {
double val = double.parse(reader.readNumber());
value = val;
reader.expect("d");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class EndTag extends Tag {
EndTag();
@ -19,4 +19,10 @@ class EndTag extends Tag {
void prettyPrint(int indent, bool recurse) {
print("${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {}
@override
void readStringifiedValue(StringReader reader) {}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class FloatTag extends Tag {
double value = 0.0;
@ -33,4 +33,17 @@ class FloatTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}f");
}
@override
void readStringifiedValue(StringReader reader) {
double val = double.parse(reader.readNumber());
value = val;
reader.expect("f");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class IntArrayTag extends Tag {
final List<int> value = [];
@ -45,4 +45,23 @@ class IntArrayTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append(
"${isList ? "".padLeft(indent, '\t') : ""}[I; ${value.join('I, ')}I]");
}
@override
void readStringifiedValue(StringReader reader) {
reader.expect("[");
reader.expect("I");
reader.expect(";");
while (reader.peek() != "]") {
value.add(int.parse(reader.readNumber()));
if (reader.peek() == ",") reader.next();
}
reader.expect("]");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class IntTag extends Tag {
int value = 0;
@ -33,4 +33,21 @@ class IntTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}i");
}
@override
void readStringifiedValue(StringReader reader) {
String val = reader.readNumber();
value = int.parse(val);
// Since a type indicator is optional for a int, check for a comma
if (reader.peek() == ",")
return;
else
reader.expect("i");
}
}

View file

@ -1,6 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
import 'EndTag.dart';
class ListTag extends Tag {
@ -86,4 +85,36 @@ class ListTag extends Tag {
void endPrettyPrint(int indent) {
print("${"".padLeft(indent, '\t')}]");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}[\n");
Iterator<Tag> it = value.iterator;
bool firstTag = true;
while (it.moveNext()) {
Tag tag = it.current;
if (firstTag)
firstTag = !firstTag;
else {
builder.append(",\n");
}
tag.writeStringifiedValue(builder, indent + 1, true);
}
builder.append("\n${"".padLeft(indent - 1, '\t')}]");
}
@override
void readStringifiedValue(StringReader reader) {
reader.expect("[");
while (reader.peek() != "]") {
TagType type = TagType.getStringifiedTagType(reader);
Tag newTag = Tag.makeTagOfType(type);
newTag.readStringifiedValue(reader);
add(newTag);
if (reader.peek() == ",") reader.next();
}
reader.expect("]");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class LongArrayTag extends Tag {
final List<int> value = [];
@ -49,4 +49,24 @@ class LongArrayTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append(
"${isList ? "".padLeft(indent, '\t') : ""}[L; ${value.join('L, ')}L]");
}
@override
void readStringifiedValue(StringReader reader) {
reader.expect("[");
reader.expect("L");
reader.expect(";");
while (reader.peek() != "]") {
value.add(int.parse(reader.readNumber()));
reader.expect("l");
if (reader.peek() == ",") reader.next();
}
reader.expect("]");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class LongTag extends Tag {
int value = 0;
@ -33,4 +33,17 @@ class LongTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}L");
}
@override
void readStringifiedValue(StringReader reader) {
int val = int.parse(reader.readNumber());
value = val;
reader.expect("l");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class ShortTag extends Tag {
int value = 0;
@ -33,4 +33,17 @@ class ShortTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}S");
}
@override
void readStringifiedValue(StringReader reader) {
int val = int.parse(reader.readNumber());
value = val;
reader.expect("s");
}
}

View file

@ -1,5 +1,5 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import '../Stream.dart';
import '../Tag.dart';
class StringTag extends Tag {
String value = "";
@ -33,4 +33,15 @@ class StringTag extends Tag {
print(
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
}
@override
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
builder.append("${isList ? "".padLeft(indent, '\t') : ""}\"${value}\"");
}
@override
void readStringifiedValue(StringReader reader) {
String str = reader.readString();
value = str;
}
}

451
lib/packets/packets.dart Normal file
View file

@ -0,0 +1,451 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import '../nbt/NbtIo.dart';
import '../nbt/Stream.dart';
import '../nbt/Tag.dart';
import '../nbt/impl/CompoundTag.dart';
import '../nbt/impl/StringTag.dart';
class PacketServer {
static ServerSocket? socket;
static bool shouldRestart = true;
static Future<void> start(int port) async {
socket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
print("Server now listening on port ${port}");
await for (var sock in socket!) {
S2CResponse response = S2CResponse();
print(
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
try {
sock.listen((data) async {
try {
CompoundTag tag = await NbtIo.readFromStream(data);
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
print("Request from client: \n${builder}");
C2SRequestPacket request = C2SRequestPacket();
request.decodeTag(tag);
PacketResponse reply = await request.handleServerPacket();
// Server uses NBT to communicate
builder = StringBuilder();
Tag.writeStringifiedNamedTag(reply.replyDataTag, builder, 0);
print("Response to client: \n${builder}");
sock.add(await NbtIo.writeToStream(reply.replyDataTag));
} catch (E, stack) {
response.contents
.put("error", StringTag.valueOf("Malformed request packet"));
print(
"Something went wrong. Malformed request? \n\n${E}\n\n${stack}\n\n\n\n");
} finally {
await sock.flush();
sock.close();
}
}, onDone: () {
sock.close();
}, onError: (E) {
print("ERROR: ${E}");
sock.close();
});
} catch (E) {
sock.close();
}
}
}
}
class PacketClient {
Socket? socket;
bool connected = false;
String lastIP = "";
int port = 25306;
PacketClient();
Future<void> startConnect(String IPAddress, int port) async {
try {
socket = await Socket.connect(IPAddress, port);
connected = true;
lastIP = IPAddress;
this.port = port;
} catch (E, stack) {
connected = false;
socket = null;
}
}
/// Tries to send a packet to the connected server
///
/// On success, returns either, the decoded [S2CResponse], or on error a S2CResponse containing an error and a stacktrace as [StringTag]
Future<S2CResponse> send(IPacket packet, bool shouldReconnect) async {
if (!connected) {
return S2CResponse();
}
C2SRequestPacket request = C2SRequestPacket();
request.payload = packet;
request.cap = packet.getChannelID();
socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag()));
CompoundTag ct = CompoundTag();
Completer<void> onCompletion = Completer();
socket!.listen((data) async {
CompoundTag result = await NbtIo.readFromStream(data);
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(result, builder, 0);
print("Response from server: \n${builder}");
ct.put("result", result);
onCompletion.complete();
}, onError: (E, stack) {
print("ERROR: ${E}\n${stack}");
if (!onCompletion.isCompleted) onCompletion.complete();
}, onDone: () {
print("Request completed");
});
await onCompletion.future;
await close();
await startConnect(lastIP, port);
S2CResponse reply = S2CResponse();
try {
reply.decodeTag(ct.get("result")!.asCompoundTag());
} catch (E, stack) {
reply.contents = CompoundTag(); // This is essentially a null response
reply.contents.put("error", StringTag.valueOf(E.toString()));
reply.contents.put("stacktrace", StringTag.valueOf(stack.toString()));
}
return reply;
}
Future<void> close() async {
await socket!.close();
connected = false;
}
}
abstract class IPacket with NbtEncodable, JsonEncodable {
String getChannelID();
// This function handles the packet
Future<PacketResponse> handleServerPacket();
Future<void> handleClientPacket();
NetworkDirection direction();
}
class StopServerPacket extends IPacket {
@override
void decodeJson(String params) {}
@override
void decodeTag(Tag tag) {}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode({});
}
@override
Tag encodeTag() {
return CompoundTag();
}
@override
void fromJson(Map<String, dynamic> js) {}
@override
String getChannelID() {
return "StopServer";
}
@override
Future<PacketResponse> handleServerPacket() async {
// We're now on the server. Handle the packet with a response to the client
PacketServer.shouldRestart = false;
S2CResponse response = S2CResponse();
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
}
@override
Map<String, dynamic> toJson() {
return {};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
class PacketResponse {
static final nil = PacketResponse(replyDataTag: CompoundTag());
PacketResponse({required this.replyDataTag});
CompoundTag replyDataTag = CompoundTag();
}
class PacketRegistry {
Map<String, IPacket Function()> _registry = {};
static PacketRegistry _inst = PacketRegistry._();
PacketRegistry._() {
registerDefaults();
}
factory PacketRegistry() {
return _inst;
}
int get count => _registry.length;
void register(IPacket packet, IPacket Function() packetResolver) {
_registry[packet.getChannelID()] = packetResolver;
}
IPacket getPacket(String channel) {
if (_registry.containsKey(channel)) {
IPacket Function() callback = _registry[channel]!;
return callback();
} else
throw Exception("No such channel has been registered");
}
void registerDefaults() {
register(S2CResponse(), () {
return S2CResponse();
});
register(C2SRequestPacket(), () {
return C2SRequestPacket();
});
register(StopServerPacket(), () {
return StopServerPacket();
});
register(C2SPing(), () {
return C2SPing();
});
}
}
enum NetworkDirection { ClientToServer, ServerToClient }
enum PacketOperation { Encode, Decode }
class S2CResponse implements IPacket {
CompoundTag contents = CompoundTag();
@override
NetworkDirection direction() {
return NetworkDirection.ServerToClient;
}
@override
String getChannelID() {
return "Response";
}
@override
Future<PacketResponse> handleServerPacket() async {
// We can't predict handling for this type, it is a data packet response with no pre-defined structure.
return PacketResponse.nil;
}
@override
void decodeJson(String encoded) {
fromJson(json.decode(encoded));
}
@override
void decodeTag(Tag encoded) {
CompoundTag ct = encoded as CompoundTag;
contents = ct.get("contents")!.asCompoundTag();
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("contents", contents);
return tag;
}
@override
void fromJson(Map<String, dynamic> params) {}
@override
Map<String, dynamic> toJson() {
return {}; // Operation is not supported at this time.
}
@override
Future<void> handleClientPacket() async {
// We haven't got anything to process. This is structured data
}
}
class C2SRequestPacket implements IPacket {
String cap = ""; // Packet channel
late IPacket payload;
@override
void decodeJson(String encoded) {
fromJson(json.decode(encoded));
}
@override
void decodeTag(Tag encoded) {
CompoundTag tag = encoded.asCompoundTag();
String cap = tag.get("cap")!.asString();
payload = PacketRegistry().getPacket(cap);
payload.decodeTag(tag.get("payload")!.asCompoundTag());
}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("cap", StringTag.valueOf(payload.getChannelID()));
tag.put("payload", payload.encodeTag());
return tag;
}
@override
void fromJson(Map<String, dynamic> params) {
String cap = params['cap'] as String;
payload = PacketRegistry().getPacket(cap);
payload.fromJson(params['payload']);
}
@override
String getChannelID() {
return "C2SRequest";
}
@override
Future<PacketResponse> handleServerPacket() async {
// This has no internal handling
return payload.handleServerPacket();
}
@override
Map<String, dynamic> toJson() {
return {"cap": payload.getChannelID(), "payload": payload.toJson()};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
class C2SPing implements IPacket {
String clientVersion = "";
@override
void decodeJson(String params) {
fromJson(json.decode(params));
}
@override
void decodeTag(Tag tag) {
clientVersion = tag.asCompoundTag().get("version")!.asString();
}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("version", StringTag.valueOf(clientVersion));
return tag;
}
@override
void fromJson(Map<String, dynamic> js) {
clientVersion = js['version'] as String;
}
@override
String getChannelID() {
return "Ping";
}
@override
Future<PacketResponse> handleServerPacket() async {
CompoundTag tag = CompoundTag();
tag.put("pong", StringTag.valueOf(Platform.version));
S2CResponse response = S2CResponse();
response.contents = tag;
PacketResponse reply =
PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
return reply;
}
@override
Map<String, dynamic> toJson() {
return {"version": clientVersion};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
mixin JsonEncodable {
String encodeJson();
void decodeJson(String params);
Map<String, dynamic> toJson();
void fromJson(Map<String, dynamic> js);
}
mixin NbtEncodable {
Tag encodeTag();
void decodeTag(Tag tag);
}

12
lib/utils/Converter.dart Normal file
View file

@ -0,0 +1,12 @@
import 'dart:convert';
import 'dart:typed_data';
class base64Encoder {
static String encode(List<int> value) {
return base64.encode(value);
}
static Uint8List decode(String value) {
return base64.decode(value);
}
}

35
lib/utils/Hashing.dart Normal file
View file

@ -0,0 +1,35 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class Hashing {
static String md5Hash(String input) {
var hash = md5.convert(utf8.encode(input)).bytes;
String x = "";
for (int byte in hash) {
x += byte.toRadixString(16).padLeft(2, '0');
}
return x;
}
static String sha1Hash(String input) {
var hash = sha1.convert(utf8.encode(input)).bytes;
String x = "";
for (int byte in hash) {
x += byte.toRadixString(16).padLeft(2, '0');
}
return x;
}
static String sha256Hash(String input) {
var hash = sha256.convert(utf8.encode(input)).bytes;
String x = "";
for (int byte in hash) {
x += byte.toRadixString(16).padLeft(2, '0');
}
return x;
}
}

124
lib/utils/IOTools.dart Normal file
View file

@ -0,0 +1,124 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
class PathHelper {
String pth = "";
PathHelper({required this.pth});
static String combine(String path1, String path2) {
return path1 + Platform.pathSeparator + path2;
}
static PathHelper builder(String startPath) {
return PathHelper(pth: startPath);
}
bool exists() {
File fi = File(build());
Directory dir = Directory(build());
return fi.existsSync() || dir.existsSync();
}
PathHelper clone() {
return PathHelper.builder(build());
}
PathHelper resolve(String path2) {
pth += Platform.pathSeparator + path2;
return this;
}
PathHelper conditionalResolve(bool flag, String path) {
if (flag) pth += Platform.pathSeparator + path;
return this;
}
bool deleteDirectory({bool recursive = false}) {
Directory dir = new Directory(build());
try {
dir.deleteSync(recursive: recursive);
return true;
} catch (E) {
return false;
}
}
bool deleteFile() {
File file = new File(build());
try {
file.deleteSync(recursive: true);
return true;
} catch (E) {
return false;
}
}
PathHelper removeDir() {
deleteDirectory(recursive: true);
return this;
}
PathHelper removeFile() {
deleteFile();
return this;
}
PathHelper mkdir() {
Directory dir = new Directory(build());
dir.createSync(recursive: true);
return this;
}
String build() {
return pth;
}
}
Stream<List<int>> tail(final File file) async* {
final randomAccess = await file.open(mode: FileMode.read);
var pos = await randomAccess.position();
var len = await randomAccess.length();
// Increase/decrease buffer size as needed.
var buf = Uint8List(8192);
Stream<Uint8List> _read() async* {
while (pos < len) {
try {
final bytesRead = await randomAccess.readInto(buf);
pos += bytesRead;
yield buf.sublist(0, bytesRead);
} catch (E) {}
}
}
// Step 1: read whole file
yield* _read();
// Step 2: wait for modify events and read more bytes from file
await for (final event in file.watch(events: FileSystemEvent.modify)) {
if ((event as FileSystemModifyEvent).contentChanged) {
try {
len = await (randomAccess.length());
yield* _read();
} catch (E) {}
}
}
}
void tailAndPrint(File file) {
try {
tail(file)
.transform(utf8.decoder)
.transform(LineSplitter())
.forEach((line) {
// Do something with the line that has been read, e.g. print it out...
print(line);
});
} catch (E) {}
}

146
lib/utils/TimeUtils.dart Normal file
View file

@ -0,0 +1,146 @@
import '../nbt/Stream.dart';
class Time {
int hours;
int minutes;
int seconds;
Time({required this.hours, required this.minutes, required this.seconds}) {
autofix();
}
int getTotalSeconds() {
int current = 0;
current += seconds;
current += (minutes * 60); // 60 seconds in a minute
current += (hours * 60 * 60); // 60 seconds, 60 minutes in an hour
return current;
}
void add(Time time) {
seconds += time.getTotalSeconds();
autofix();
}
void subtract(Time time) {
int sec = getTotalSeconds();
sec -= time.getTotalSeconds();
apply(sec);
}
void tickDown() {
int sec = getTotalSeconds();
sec--;
apply(sec);
}
void tickUp() {
int sec = getTotalSeconds();
sec++;
apply(sec);
}
void apply(int seconds) {
hours = 0;
minutes = 0;
this.seconds = seconds;
if (this.seconds < 0) this.seconds = 0;
autofix();
}
void autofix() {
int totalSeconds = getTotalSeconds();
if (totalSeconds < 0) totalSeconds = 0;
int one_hour = (1 * 60 * 60);
int one_minute = (1 * 60);
int hours = (totalSeconds / 60 / 60).round();
totalSeconds -= (hours * one_hour);
int minutes = (totalSeconds / 60).round();
totalSeconds -= (minutes * one_minute);
int seconds = totalSeconds;
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
Time copy() {
return Time(hours: hours, minutes: minutes, seconds: seconds);
}
factory Time.copy(Time other) {
return Time(
hours: other.hours, minutes: other.minutes, seconds: other.seconds);
}
factory Time.fromNotation(String notation) {
int hours = 0;
int minutes = 0;
int seconds = 0;
List<String> current = [];
String val = notation;
if (val.indexOf('h') == -1) {
hours = 0;
} else {
current = val.split('h');
hours = int.parse(current[0]);
if (current.length == 2)
val = current[1];
else
val = "";
}
if (val.indexOf('m') == -1) {
minutes = 0;
} else {
current = val.split('m');
minutes = int.parse(current[0]);
if (current.length == 2)
val = current[1];
else
val = "";
}
if (val.indexOf('s') == -1) {
seconds = 0;
} else {
current = val.split('s');
seconds = int.parse(current[0]);
if (current.length == 2)
val = current[1];
else
val = "";
}
if (val != "") {
seconds += int.parse(val);
}
return Time(hours: hours, minutes: minutes, seconds: seconds);
}
@override
String toString() {
StringBuilder builder = StringBuilder();
if (hours > 0) builder.append("${hours}h");
if (minutes > 0) builder.append("${minutes}m");
if (seconds > 0) builder.append("${seconds}s");
return "${builder}";
}
}

View file

@ -0,0 +1,42 @@
import '../../nbt/Stream.dart';
import 'UUID.dart';
class NbtUUID {
final int MSB;
final int LSB;
const NbtUUID(this.MSB, this.LSB);
static final NbtUUID ZERO = NbtUUID.fromUUID(UUID.ZERO);
factory NbtUUID.fromUUID(UUID id) {
ByteLayer layer = ByteLayer();
layer.writeBytes(id.getBytes());
layer.resetPosition();
int MSB = layer.readLong();
int LSB = layer.readLong();
return NbtUUID(MSB, LSB);
}
/// Returns the Most Significant Bits (MSB) long value of the UUID.
int getMostSignificantBits() => MSB;
/// Returns the Least Significant Bits (LSB) long value of the UUID.
int getLeastSignificantBits() => LSB;
UUID toUUID() {
ByteLayer layer = ByteLayer();
layer.writeLong(MSB);
layer.writeLong(LSB);
layer.resetPosition();
return UUID(layer.readBytes(16));
}
@override
String toString() {
return toUUID().toString();
}
}

View file

@ -2,23 +2,14 @@ import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:libac_flutter/nbt/Stream.dart';
import '../../nbt/Stream.dart';
class UUID {
late final int LSB;
late final int MSB;
late final List<int> _bytes;
UUID(int msb, int lsb) {
MSB = msb;
LSB = lsb;
ByteLayer layer = ByteLayer();
layer.writeLong(MSB);
layer.writeLong(LSB);
layer.resetPosition();
_bytes = layer.readBytes(16);
UUID(List<int> bytes) {
_bytes = bytes;
}
static final UUID ZERO = UUID.generate(0);
@ -27,8 +18,9 @@ class UUID {
static bool validate(String uuid) {
if (uuid.length == ((16 * 2) + 4)) {
return true; // Likely is true. This is just a surface level check
} else
} else {
return false;
}
}
/// Parses the given [uuid] string and returns a UUID object.
@ -42,30 +34,26 @@ class UUID {
final msbString = segments.sublist(0, 3).join('');
final lsbString = segments.sublist(3, 5).join('');
int msb = 0;
int lsb = 0;
int i = 0;
ByteLayer layer = ByteLayer();
for (i = 0; i < msbString.length; i += 2) {
String hex = "${msbString.substring(i, i + 2)}";
String hex = msbString.substring(i, i + 2);
int byte = int.parse(hex, radix: 16);
layer.writeByte(byte);
}
for (i = 0; i < lsbString.length; i += 2) {
String hex = "${lsbString.substring(i, i + 2)}";
String hex = lsbString.substring(i, i + 2);
int byte = int.parse(hex, radix: 16);
layer.writeByte(byte);
}
layer.resetPosition();
msb = layer.readLong();
lsb = layer.readLong();
return UUID(msb, lsb);
} else
return UUID(layer.readBytes(16));
} else {
return UUID.ZERO;
}
}
@override
@ -77,29 +65,33 @@ class UUID {
return '${hexBuilder.substring(0, 8)}-${hexBuilder.substring(8, 12)}-${hexBuilder.substring(12, 16)}-${hexBuilder.substring(16, 20)}-${hexBuilder.substring(20, 32)}';
}
/// Returns the Most Significant Bits (MSB) long value of the UUID.
int getMostSignificantBits() => MSB;
/// Returns the Least Significant Bits (LSB) long value of the UUID.
int getLeastSignificantBits() => LSB;
/// Factory method to generate UUID of the specific version.
factory UUID.generate(int version, {List<Object>? parameters}) {
List<Object> params = [];
if (parameters == null) {
if (version != 4) {
if (version != 4 && version != 0) {
return UUID.generate(4);
}
} else
params = parameters!;
} else {
params = parameters;
}
switch (version) {
case 0:
return UUID(0, 0);
{
ByteLayer layer = ByteLayer();
layer.writeLong(0);
layer.writeLong(0);
layer.resetPosition();
return UUID(layer.readBytes(16));
}
case 3:
{
if (params.length != 2)
if (params.length != 2) {
throw Exception(
"UUID v3 requires two parameters, [namespace,name]");
}
String namespace = params[0] as String;
String name = params[1] as String;
@ -118,45 +110,38 @@ class UUID {
layer.unsetSetBit(8, 0xC0, 0x80);
layer.resetPosition();
var msb = layer.readUnsignedLong();
var lsb = layer.readUnsignedLong();
layer.resetPosition();
return UUID(msb, lsb);
return UUID(layer.readBytes(16));
}
case 4:
{
ByteLayer layer = ByteLayer();
final random = Random.secure();
layer.writeLong(
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
layer.writeLong(
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
layer.insertRandomBytes(16);
layer.unsetSetBit(6, 0xF0, 0x40);
layer.unsetSetBit(8, 0xC0, 0x80);
layer.resetPosition();
return UUID(layer.readUnsignedLong(), layer.readUnsignedLong());
return UUID(layer.readBytes(16));
}
case 5:
{
ByteLayer layer = ByteLayer();
if (params.length != 2)
if (params.length != 2) {
throw Exception(
"UUID v5 requires two parameters, [namespace,name]");
}
String namespace = params[0] as String;
String name = params[1] as String;
if (!namespace.isEmpty) {
if (namespace.isNotEmpty) {
final namespaceBytes = utf8.encode(namespace);
layer.writeBytes(namespaceBytes);
}
if (!name.isEmpty) {
if (name.isNotEmpty) {
final nameBytes = utf8.encode(name);
layer.writeBytes(nameBytes);
}
@ -170,10 +155,14 @@ class UUID {
layer.resetPosition();
return UUID(layer.readUnsignedLong(), layer.readUnsignedLong());
return UUID(layer.readBytes(16));
}
default:
throw ArgumentError('Unsupported UUID version: $version');
}
}
Iterable<int> getBytes() {
return _bytes.take(16);
}
}

View file

@ -1,28 +1,23 @@
name: libac_flutter
name: libac_dart
description: "Aria's Creations code library"
version: 1.0.0
homepage:
version: 1.0.32
homepage: "https://zontreck.com"
environment:
sdk: '>=3.3.4 <4.0.0'
flutter: ">=1.17.0"
sdk: ^3.4.0
# Add regular dependencies here.
dependencies:
crypto: ^3.0.3
flutter:
sdk: flutter
# path: ^1.8.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
lints: ^3.0.0
test: ^1.24.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:

9
test/hash_test.dart Normal file
View file

@ -0,0 +1,9 @@
import 'package:libac_dart/utils/Hashing.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
test("Test md5", () {
expect(Hashing.md5Hash("Hello World"), "b10a8db164e0754105b7a99be72e3fe5");
});
}

View file

@ -1,11 +1,17 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:libac_flutter/nbt/NbtIo.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
import 'package:libac_dart/nbt/NbtIo.dart';
import 'package:libac_dart/nbt/NbtUtils.dart';
import 'package:libac_dart/nbt/SnbtIo.dart';
import 'package:libac_dart/nbt/Stream.dart';
import 'package:libac_dart/nbt/Tag.dart';
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
import 'package:libac_dart/nbt/impl/StringTag.dart';
import 'package:libac_dart/utils/IOTools.dart';
import 'package:libac_dart/utils/uuid/NbtUUID.dart';
import 'package:libac_dart/utils/uuid/UUID.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
test('read non-compressed helloworld NBT', () async {
@ -13,7 +19,7 @@ void main() {
CompoundTag tag =
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
expect(tag.getKey(), "hello world");
expect(tag.contains("name"), true);
expect(tag.containsKey("name"), true);
expect(tag.get("name")!.asString(), "Bananrama");
});
@ -33,7 +39,7 @@ void main() {
CompoundTag tag =
await NbtIo.read("${Directory.current.path}/build/hello_world.nbt");
expect(tag.getKey(), "hello world");
expect(tag.contains("name"), true);
expect(tag.containsKey("name"), true);
expect(tag.get("name")!.asString(), "Bananrama");
});
@ -50,9 +56,56 @@ void main() {
test("Generate a UUID v4, save to NBT, and read it back again", () async {
var id = UUID.generate(4);
CompoundTag tag = CompoundTag();
NbtUtils.writeUUID(tag, "test", id);
NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(id));
var newID = NbtUtils.readUUID(tag, "test");
expect(id.toString(), newID.toString());
});
test("Read HelloWorld, Output to SNBT", () async {
CompoundTag ct =
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
StringBuilder sb = StringBuilder();
Tag.writeStringifiedNamedTag(ct, sb, 0);
print(sb.toString());
});
test("Read BigTest, Output to SNBT", () async {
CompoundTag ct =
await NbtIo.read("${Directory.current.path}/test/bigtest.nbt");
StringBuilder sb = StringBuilder();
Tag.writeStringifiedNamedTag(ct, sb, 0);
print(sb.toString());
});
test("Write BigTest to SNBT file", () async {
CompoundTag ct =
await NbtIo.read("${Directory.current.path}/test/bigtest.nbt");
String output = "${Directory.current.path}/build/bigtest.snbt";
File file = File(output);
SnbtIo.writeToFile(output, ct);
// Expect that the file exists
PathHelper ph = PathHelper.builder(Directory.current.path)
.resolve("build")
.resolve("bigtest.snbt");
expect(ph.exists(), true);
});
test("Read BigTest from SNBT file", () async {
CompoundTag tag = await SnbtIo.readFromFile(
"${Directory.current.path}/build/bigtest.snbt") as CompoundTag;
expect(tag.containsKey("stringTest"), true);
expect(tag.get("doubleTest")!.asDouble(), 0.4931287132182315);
});
test("Write NULL UUID to NBT", () async {
CompoundTag tag = CompoundTag();
NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(UUID.ZERO));
NbtUUID ID = NbtUtils.readUUID(tag, "test");
expect(ID.MSB, 0);
expect(ID.LSB, 0);
});
}

24
test/time_test.dart Normal file
View file

@ -0,0 +1,24 @@
import 'package:libac_dart/utils/TimeUtils.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
test("Parse time notation", () {
Time time = Time.fromNotation("4h9s");
expect(time.toString(), "4h9s");
expect(time.hours, 4);
expect(time.minutes, 0);
expect(time.seconds, 9);
});
test("Add time", () {
// Depends on first test working!
Time time = Time.fromNotation("4h9s");
time.add(Time(hours: 0, minutes: 80, seconds: 1));
expect(time.hours, 5);
expect(time.minutes, 20);
expect(time.seconds, 10);
});
}

View file

@ -1,6 +1,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
import 'package:libac_dart/nbt/Stream.dart';
import 'package:libac_dart/utils/uuid/UUID.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
test("Store a 64bit value in int", () {
@ -20,15 +21,15 @@ void main() {
}
for (UUID sID in ID) {
print("ID : ${sID}}");
print("ID : $sID}");
}
});
test("Check UUIDv4 for validity", () {
var ID = UUID.generate(4);
ByteLayer layer = ByteLayer();
layer.writeLong(ID.getMostSignificantBits().toInt());
layer.writeLong(ID.getLeastSignificantBits().toInt());
//layer.writeLong(ID.getMostSignificantBits().toInt());
//layer.writeLong(ID.getLeastSignificantBits().toInt());
print(
"Checking version bit: ${layer.checkBit(6, 0x40)} - ${layer.getBit(6)}");
@ -38,8 +39,8 @@ void main() {
test("Generate and check a UUIDv3", () {
var ID3 = UUID.generate(3, parameters: ["Test", "Test2"]);
ByteLayer layer = ByteLayer();
layer.writeLong(ID3.getMostSignificantBits().toInt());
layer.writeLong(ID3.getLeastSignificantBits().toInt());
//layer.writeLong(ID3.getMostSignificantBits().toInt());
//layer.writeLong(ID3.getLeastSignificantBits().toInt());
print(
"Checking version bit: ${layer.checkBit(6, 0x30)} - ${layer.getBit(6)}");
@ -70,9 +71,16 @@ void main() {
var ID3 = UUID.parse(asString);
var ID3X = UUID.generate(3, parameters: ["OfflinePlayer:zontreck", ""]);
expect(ID3.MSB, ID3X.MSB);
expect(ID3.LSB, ID3X.LSB);
//expect(ID3.MSB, ID3X.MSB);
//expect(ID3.LSB, ID3X.LSB);
expect(ID3.toString(), asString);
});
test("Null UUID", () {
var expected = "00000000-0000-0000-0000-000000000000";
var actual = UUID.ZERO.toString();
expect(actual, expected);
});
}