From c10ffc58b81f3f202ca674d2466731ec574c243b Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 8 May 2024 14:44:37 -0700 Subject: [PATCH 01/50] Apply dart autofixes --- lib/nbt/NbtUtils.dart | 5 +++-- lib/nbt/SnbtIo.dart | 1 + lib/nbt/Stream.dart | 3 ++- lib/utils/uuid/UUID.dart | 25 +++++++++++++++---------- test/uuid_test.dart | 2 +- 5 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 lib/nbt/SnbtIo.dart diff --git a/lib/nbt/NbtUtils.dart b/lib/nbt/NbtUtils.dart index 3165fe6..16497ba 100644 --- a/lib/nbt/NbtUtils.dart +++ b/lib/nbt/NbtUtils.dart @@ -111,9 +111,10 @@ class NbtUtils { } static UUID readUUID(CompoundTag tag, String name) { - if (!tag.contains(name)) + if (!tag.contains(name)) { return UUID.ZERO; - else + } else { return _uuidFromIntArray(tag.get(name)!.asIntArray()); + } } } diff --git a/lib/nbt/SnbtIo.dart b/lib/nbt/SnbtIo.dart new file mode 100644 index 0000000..777a9d2 --- /dev/null +++ b/lib/nbt/SnbtIo.dart @@ -0,0 +1 @@ +class SnbtIo {} diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index c568310..3dd5d84 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -351,8 +351,9 @@ class ByteLayer { seek(position); int current = readUnsignedByte(); return (current & mask) == mask; - } else + } else { return false; + } } int getBit(int position) { diff --git a/lib/utils/uuid/UUID.dart b/lib/utils/uuid/UUID.dart index d496e33..374f9f8 100644 --- a/lib/utils/uuid/UUID.dart +++ b/lib/utils/uuid/UUID.dart @@ -27,8 +27,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. @@ -48,13 +49,13 @@ class UUID { 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); } @@ -64,8 +65,9 @@ class UUID { lsb = layer.readLong(); return UUID(msb, lsb); - } else + } else { return UUID.ZERO; + } } @override @@ -90,16 +92,18 @@ class UUID { if (version != 4) { return UUID.generate(4); } - } else - params = parameters!; + } else { + params = parameters; + } switch (version) { case 0: return UUID(0, 0); 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; @@ -145,18 +149,19 @@ class UUID { 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); } diff --git a/test/uuid_test.dart b/test/uuid_test.dart index 2859a74..d81fbfb 100644 --- a/test/uuid_test.dart +++ b/test/uuid_test.dart @@ -20,7 +20,7 @@ void main() { } for (UUID sID in ID) { - print("ID : ${sID}}"); + print("ID : $sID}"); } }); From 84b37f8b04f81608508ba43e6ab6fe150b92785c Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 8 May 2024 18:52:20 -0700 Subject: [PATCH 02/50] Initial SNBT implementation --- lib/nbt/Stream.dart | 19 +++++++++++++++++++ lib/nbt/Tag.dart | 32 ++++++++++++++++++++++++++++++++ lib/nbt/impl/ByteArrayTag.dart | 5 +++++ lib/nbt/impl/ByteTag.dart | 5 +++++ lib/nbt/impl/CompoundTag.dart | 18 ++++++++++++++++++ lib/nbt/impl/DoubleTag.dart | 5 +++++ lib/nbt/impl/EndTag.dart | 3 +++ lib/nbt/impl/FloatTag.dart | 5 +++++ lib/nbt/impl/IntArrayTag.dart | 5 +++++ lib/nbt/impl/IntTag.dart | 5 +++++ lib/nbt/impl/ListTag.dart | 5 +++++ lib/nbt/impl/LongArrayTag.dart | 5 +++++ lib/nbt/impl/LongTag.dart | 5 +++++ lib/nbt/impl/ShortTag.dart | 5 +++++ lib/nbt/impl/StringTag.dart | 5 +++++ test/nbt_test.dart | 10 ++++++++++ 16 files changed, 137 insertions(+) diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index 3dd5d84..0b21ebe 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -377,3 +377,22 @@ class ByteLayer { setBit(position, maskToSet); } } + +class StringBuilder { + String _buffer = ""; + + StringBuilder(); + + void append(String value) { + _buffer += value; + } + + void clear() { + _buffer = ""; + } + + @override + String toString() { + return "${_buffer}"; + } +} diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index 6e92017..37134b6 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -82,6 +82,38 @@ abstract class Tag { } } + static void writeStringifiedNamedTag( + Tag tag, StringBuilder builder, int indents) { + if (tag.getType() != 0) { + if (builder != "") { + // Write name + if (tag.shouldQuoteName()) { + builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": "); + } else + builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: "); + } + + tag.writeStringifiedValue(builder, indents + 1); + } + } + + 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 equals(dynamic object) { if (object == null || object is! Tag) return false; diff --git a/lib/nbt/impl/ByteArrayTag.dart b/lib/nbt/impl/ByteArrayTag.dart index 4cc8dad..43dced2 100644 --- a/lib/nbt/impl/ByteArrayTag.dart +++ b/lib/nbt/impl/ByteArrayTag.dart @@ -46,4 +46,9 @@ class ByteArrayTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("[B; ${value.join("B, ")}B]"); + } } diff --git a/lib/nbt/impl/ByteTag.dart b/lib/nbt/impl/ByteTag.dart index dc538d8..5ba7189 100644 --- a/lib/nbt/impl/ByteTag.dart +++ b/lib/nbt/impl/ByteTag.dart @@ -34,4 +34,9 @@ class ByteTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}b"); + } } diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index 1c6a632..0ba5cf6 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -85,4 +85,22 @@ class CompoundTag extends Tag { void endPrettyPrint(int indent) { print("${"".padLeft(indent, '\t')}}"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + Iterator it = value.values.iterator; + builder.append("{\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')}}\n"); + } } diff --git a/lib/nbt/impl/DoubleTag.dart b/lib/nbt/impl/DoubleTag.dart index f674968..b60780c 100644 --- a/lib/nbt/impl/DoubleTag.dart +++ b/lib/nbt/impl/DoubleTag.dart @@ -34,4 +34,9 @@ class DoubleTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}d"); + } } diff --git a/lib/nbt/impl/EndTag.dart b/lib/nbt/impl/EndTag.dart index 24b1ab2..25ecabf 100644 --- a/lib/nbt/impl/EndTag.dart +++ b/lib/nbt/impl/EndTag.dart @@ -19,4 +19,7 @@ class EndTag extends Tag { void prettyPrint(int indent, bool recurse) { print("${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) {} } diff --git a/lib/nbt/impl/FloatTag.dart b/lib/nbt/impl/FloatTag.dart index e446d35..073ae7e 100644 --- a/lib/nbt/impl/FloatTag.dart +++ b/lib/nbt/impl/FloatTag.dart @@ -33,4 +33,9 @@ class FloatTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}f"); + } } diff --git a/lib/nbt/impl/IntArrayTag.dart b/lib/nbt/impl/IntArrayTag.dart index 7a344cf..c864b57 100644 --- a/lib/nbt/impl/IntArrayTag.dart +++ b/lib/nbt/impl/IntArrayTag.dart @@ -45,4 +45,9 @@ class IntArrayTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("[I; ${value.join('I, ')}I]"); + } } diff --git a/lib/nbt/impl/IntTag.dart b/lib/nbt/impl/IntTag.dart index be32a44..4c92d28 100644 --- a/lib/nbt/impl/IntTag.dart +++ b/lib/nbt/impl/IntTag.dart @@ -33,4 +33,9 @@ class IntTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}i"); + } } diff --git a/lib/nbt/impl/ListTag.dart b/lib/nbt/impl/ListTag.dart index 8e47a96..f1269c2 100644 --- a/lib/nbt/impl/ListTag.dart +++ b/lib/nbt/impl/ListTag.dart @@ -86,4 +86,9 @@ class ListTag extends Tag { void endPrettyPrint(int indent) { print("${"".padLeft(indent, '\t')}]"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("[\n"); + } } diff --git a/lib/nbt/impl/LongArrayTag.dart b/lib/nbt/impl/LongArrayTag.dart index bc1f4ab..efa2bc5 100644 --- a/lib/nbt/impl/LongArrayTag.dart +++ b/lib/nbt/impl/LongArrayTag.dart @@ -49,4 +49,9 @@ class LongArrayTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("[L; ${value.join('L, ')}L]"); + } } diff --git a/lib/nbt/impl/LongTag.dart b/lib/nbt/impl/LongTag.dart index 3307cee..b7af5bc 100644 --- a/lib/nbt/impl/LongTag.dart +++ b/lib/nbt/impl/LongTag.dart @@ -33,4 +33,9 @@ class LongTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}L"); + } } diff --git a/lib/nbt/impl/ShortTag.dart b/lib/nbt/impl/ShortTag.dart index bc68fb7..753e4db 100644 --- a/lib/nbt/impl/ShortTag.dart +++ b/lib/nbt/impl/ShortTag.dart @@ -33,4 +33,9 @@ class ShortTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("${value}S"); + } } diff --git a/lib/nbt/impl/StringTag.dart b/lib/nbt/impl/StringTag.dart index 1e8f89f..74569f8 100644 --- a/lib/nbt/impl/StringTag.dart +++ b/lib/nbt/impl/StringTag.dart @@ -33,4 +33,9 @@ class StringTag extends Tag { print( "${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value"); } + + @override + void writeStringifiedValue(StringBuilder builder, int indent) { + builder.append("\"${value}\""); + } } diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 182b184..666c8a5 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -3,6 +3,8 @@ 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/Stream.dart'; +import 'package:libac_flutter/nbt/Tag.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'; @@ -55,4 +57,12 @@ void main() { 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()); + }); } From daf9cb7376d78c5b717474f73ac1f67f03da2af7 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 8 May 2024 20:35:08 -0700 Subject: [PATCH 03/50] SNBT Serialization now implemented --- lib/nbt/Stream.dart | 2 ++ lib/nbt/Tag.dart | 6 +++--- lib/nbt/impl/ByteArrayTag.dart | 5 +++-- lib/nbt/impl/ByteTag.dart | 2 +- lib/nbt/impl/CompoundTag.dart | 6 +++--- lib/nbt/impl/DoubleTag.dart | 4 ++-- lib/nbt/impl/EndTag.dart | 2 +- lib/nbt/impl/FloatTag.dart | 4 ++-- lib/nbt/impl/IntArrayTag.dart | 5 +++-- lib/nbt/impl/IntTag.dart | 4 ++-- lib/nbt/impl/ListTag.dart | 17 +++++++++++++++-- lib/nbt/impl/LongArrayTag.dart | 5 +++-- lib/nbt/impl/LongTag.dart | 4 ++-- lib/nbt/impl/ShortTag.dart | 4 ++-- lib/nbt/impl/StringTag.dart | 4 ++-- test/nbt_test.dart | 8 ++++++++ 16 files changed, 54 insertions(+), 28 deletions(-) diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index 0b21ebe..6849d26 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -383,6 +383,8 @@ class StringBuilder { StringBuilder(); + bool get isEmpty => _buffer.isEmpty; + void append(String value) { _buffer += value; } diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index 37134b6..fa339fb 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -85,7 +85,7 @@ abstract class Tag { static void writeStringifiedNamedTag( Tag tag, StringBuilder builder, int indents) { if (tag.getType() != 0) { - if (builder != "") { + if (!builder.isEmpty) { // Write name if (tag.shouldQuoteName()) { builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": "); @@ -93,7 +93,7 @@ abstract class Tag { builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: "); } - tag.writeStringifiedValue(builder, indents + 1); + tag.writeStringifiedValue(builder, indents + 1, false); } } @@ -112,7 +112,7 @@ abstract class Tag { } } - void writeStringifiedValue(StringBuilder builder, int indent); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList); bool equals(dynamic object) { if (object == null || object is! Tag) return false; diff --git a/lib/nbt/impl/ByteArrayTag.dart b/lib/nbt/impl/ByteArrayTag.dart index 43dced2..bb62a6d 100644 --- a/lib/nbt/impl/ByteArrayTag.dart +++ b/lib/nbt/impl/ByteArrayTag.dart @@ -48,7 +48,8 @@ class ByteArrayTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("[B; ${value.join("B, ")}B]"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append( + "${isList ? "".padLeft(indent, '\t') : ""}[B; ${value.join("B, ")}B]"); } } diff --git a/lib/nbt/impl/ByteTag.dart b/lib/nbt/impl/ByteTag.dart index 5ba7189..68f3ed7 100644 --- a/lib/nbt/impl/ByteTag.dart +++ b/lib/nbt/impl/ByteTag.dart @@ -36,7 +36,7 @@ class ByteTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { builder.append("${value}b"); } } diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index 0ba5cf6..5f549d3 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -87,9 +87,9 @@ class CompoundTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { Iterator it = value.values.iterator; - builder.append("{\n"); + builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}{\n"); bool firstEntry = true; while (it.moveNext()) { @@ -101,6 +101,6 @@ class CompoundTag extends Tag { } Tag.writeStringifiedNamedTag(t, builder, indent); } - builder.append("\n${"".padLeft(indent - 1, '\t')}}\n"); + builder.append("\n${"".padLeft(indent - 1, '\t')}}"); } } diff --git a/lib/nbt/impl/DoubleTag.dart b/lib/nbt/impl/DoubleTag.dart index b60780c..3881473 100644 --- a/lib/nbt/impl/DoubleTag.dart +++ b/lib/nbt/impl/DoubleTag.dart @@ -36,7 +36,7 @@ class DoubleTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("${value}d"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}d"); } } diff --git a/lib/nbt/impl/EndTag.dart b/lib/nbt/impl/EndTag.dart index 25ecabf..50429fd 100644 --- a/lib/nbt/impl/EndTag.dart +++ b/lib/nbt/impl/EndTag.dart @@ -21,5 +21,5 @@ class EndTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) {} + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {} } diff --git a/lib/nbt/impl/FloatTag.dart b/lib/nbt/impl/FloatTag.dart index 073ae7e..7b6ffc4 100644 --- a/lib/nbt/impl/FloatTag.dart +++ b/lib/nbt/impl/FloatTag.dart @@ -35,7 +35,7 @@ class FloatTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("${value}f"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}f"); } } diff --git a/lib/nbt/impl/IntArrayTag.dart b/lib/nbt/impl/IntArrayTag.dart index c864b57..d015351 100644 --- a/lib/nbt/impl/IntArrayTag.dart +++ b/lib/nbt/impl/IntArrayTag.dart @@ -47,7 +47,8 @@ class IntArrayTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("[I; ${value.join('I, ')}I]"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append( + "${isList ? "".padLeft(indent, '\t') : ""}[I; ${value.join('I, ')}I]"); } } diff --git a/lib/nbt/impl/IntTag.dart b/lib/nbt/impl/IntTag.dart index 4c92d28..067c517 100644 --- a/lib/nbt/impl/IntTag.dart +++ b/lib/nbt/impl/IntTag.dart @@ -35,7 +35,7 @@ class IntTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("${value}i"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}i"); } } diff --git a/lib/nbt/impl/ListTag.dart b/lib/nbt/impl/ListTag.dart index f1269c2..d908226 100644 --- a/lib/nbt/impl/ListTag.dart +++ b/lib/nbt/impl/ListTag.dart @@ -88,7 +88,20 @@ class ListTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("[\n"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}[\n"); + Iterator 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')}]"); } } diff --git a/lib/nbt/impl/LongArrayTag.dart b/lib/nbt/impl/LongArrayTag.dart index efa2bc5..8984cf4 100644 --- a/lib/nbt/impl/LongArrayTag.dart +++ b/lib/nbt/impl/LongArrayTag.dart @@ -51,7 +51,8 @@ class LongArrayTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("[L; ${value.join('L, ')}L]"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append( + "${isList ? "".padLeft(indent, '\t') : ""}[L; ${value.join('L, ')}L]"); } } diff --git a/lib/nbt/impl/LongTag.dart b/lib/nbt/impl/LongTag.dart index b7af5bc..6f1d90e 100644 --- a/lib/nbt/impl/LongTag.dart +++ b/lib/nbt/impl/LongTag.dart @@ -35,7 +35,7 @@ class LongTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("${value}L"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}L"); } } diff --git a/lib/nbt/impl/ShortTag.dart b/lib/nbt/impl/ShortTag.dart index 753e4db..9642295 100644 --- a/lib/nbt/impl/ShortTag.dart +++ b/lib/nbt/impl/ShortTag.dart @@ -35,7 +35,7 @@ class ShortTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("${value}S"); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}S"); } } diff --git a/lib/nbt/impl/StringTag.dart b/lib/nbt/impl/StringTag.dart index 74569f8..e6cf258 100644 --- a/lib/nbt/impl/StringTag.dart +++ b/lib/nbt/impl/StringTag.dart @@ -35,7 +35,7 @@ class StringTag extends Tag { } @override - void writeStringifiedValue(StringBuilder builder, int indent) { - builder.append("\"${value}\""); + void writeStringifiedValue(StringBuilder builder, int indent, bool isList) { + builder.append("${isList ? "".padLeft(indent, '\t') : ""}\"${value}\""); } } diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 666c8a5..426bc7b 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -65,4 +65,12 @@ void main() { 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()); + }); } From 0766a436221a71d94e46a6694d089cc43a1bfc75 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 8 May 2024 20:37:41 -0700 Subject: [PATCH 04/50] Implement SNBT write to file --- lib/nbt/SnbtIo.dart | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/nbt/SnbtIo.dart b/lib/nbt/SnbtIo.dart index 777a9d2..6d39f37 100644 --- a/lib/nbt/SnbtIo.dart +++ b/lib/nbt/SnbtIo.dart @@ -1 +1,14 @@ -class SnbtIo {} +import 'dart:io'; + +import 'package:libac_flutter/nbt/Stream.dart'; +import 'package:libac_flutter/nbt/Tag.dart'; +import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; + +class SnbtIo { + static void write(String file, CompoundTag tag) { + File handle = File(file); + StringBuilder builder = StringBuilder(); + Tag.writeStringifiedNamedTag(tag, builder, 0); + handle.writeAsString(builder.toString()); + } +} From e9d4d51b700428d276047f0de6d6380b1c23f0ed Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 8 May 2024 20:40:37 -0700 Subject: [PATCH 05/50] Adds a unit test that writes a snbt file --- test/nbt_test.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 426bc7b..37fa734 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -3,6 +3,7 @@ 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/SnbtIo.dart'; import 'package:libac_flutter/nbt/Stream.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; @@ -73,4 +74,15 @@ void main() { 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.write(output, ct); + + // Expect that the file exists + expect(file.existsSync(), true); + }); } From f14a10a381cc94462cd4ed3079eb773624210e14 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 15 May 2024 03:15:17 -0700 Subject: [PATCH 06/50] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 6ebce4d..954e2f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.0 +version: 1.0.1 homepage: environment: From b2f12eae15faf62b4127432a2b26224128a879a9 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 15 May 2024 13:25:57 -0700 Subject: [PATCH 07/50] Adds several helper hash functions --- lib/utils/Hashing.dart | 35 +++++++++++++++++++++++++++++++++++ pubspec.yaml | 4 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 lib/utils/Hashing.dart diff --git a/lib/utils/Hashing.dart b/lib/utils/Hashing.dart new file mode 100644 index 0000000..163e0ec --- /dev/null +++ b/lib/utils/Hashing.dart @@ -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); + } + + 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); + } + + 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); + } + + return x; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 954e2f3..8bf0358 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.1 -homepage: +version: 1.0.2 +homepage: "https://zontreck.com" environment: sdk: '>=3.3.4 <4.0.0' From 9348bf1370ed001f171c174440d57fb47b72c78e Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 16 May 2024 02:59:10 -0700 Subject: [PATCH 08/50] Fixes a invalid length hash due to the way dart outputs hex --- lib/utils/Hashing.dart | 6 +++--- test/hash_test.dart | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test/hash_test.dart diff --git a/lib/utils/Hashing.dart b/lib/utils/Hashing.dart index 163e0ec..89f44e5 100644 --- a/lib/utils/Hashing.dart +++ b/lib/utils/Hashing.dart @@ -7,7 +7,7 @@ class Hashing { var hash = md5.convert(utf8.encode(input)).bytes; String x = ""; for (int byte in hash) { - x += byte.toRadixString(16); + x += byte.toRadixString(16).padLeft(2, '0'); } return x; @@ -17,7 +17,7 @@ class Hashing { var hash = sha1.convert(utf8.encode(input)).bytes; String x = ""; for (int byte in hash) { - x += byte.toRadixString(16); + x += byte.toRadixString(16).padLeft(2, '0'); } return x; @@ -27,7 +27,7 @@ class Hashing { var hash = sha256.convert(utf8.encode(input)).bytes; String x = ""; for (int byte in hash) { - x += byte.toRadixString(16); + x += byte.toRadixString(16).padLeft(2, '0'); } return x; diff --git a/test/hash_test.dart b/test/hash_test.dart new file mode 100644 index 0000000..7243f1e --- /dev/null +++ b/test/hash_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:libac_flutter/utils/Hashing.dart'; + +void main() { + test("Test md5", () { + expect(Hashing.md5Hash("Hello World"), "b10a8db164e0754105b7a99be72e3fe5"); + }); +} From 7c194f2dd4dc26517c45cea1c95ced293c76e485 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 16 May 2024 02:59:31 -0700 Subject: [PATCH 09/50] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8bf0358..f7f09b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.2 +version: 1.0.3 homepage: "https://zontreck.com" environment: From 1024c4dbd70dc70e1871d2be91f73afc26a2824b Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 16 May 2024 13:22:05 -0700 Subject: [PATCH 10/50] Adds a base64 encode wrapper, and a way to encode NBT to Base64 --- lib/nbt/NbtIo.dart | 19 +++++++++++++++++++ lib/utils/Converter.dart | 12 ++++++++++++ pubspec.yaml | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 lib/utils/Converter.dart diff --git a/lib/nbt/NbtIo.dart b/lib/nbt/NbtIo.dart index f7ffcdd..810600e 100644 --- a/lib/nbt/NbtIo.dart +++ b/lib/nbt/NbtIo.dart @@ -1,6 +1,7 @@ import 'package:libac_flutter/nbt/Stream.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; +import 'package:libac_flutter/utils/Converter.dart'; class NbtIo { static ByteLayer _io = ByteLayer(); @@ -49,4 +50,22 @@ class NbtIo { static ByteLayer getStream() { return _io; } + + static Future writeBase64String(CompoundTag tag) async { + _io = ByteLayer(); + Tag.writeNamedTag(tag, _io); + _io.compress(); + return base64Encoder.encode(_io.bytes); + } + + static Future readBase64String(String encoded) async { + _io = ByteLayer(); + List bytes = base64Encoder.decode(encoded); + for (int byte in bytes) { + _io.writeByte(byte); + } + _io.decompress(); + _io.resetPosition(); + return Tag.readNamedTag(_io) as CompoundTag; + } } diff --git a/lib/utils/Converter.dart b/lib/utils/Converter.dart new file mode 100644 index 0000000..4f873c6 --- /dev/null +++ b/lib/utils/Converter.dart @@ -0,0 +1,12 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +class base64Encoder { + static String encode(List value) { + return base64.encode(value); + } + + static Uint8List decode(String value) { + return base64.decode(value); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f7f09b0..7096051 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.3 +version: 1.0.4 homepage: "https://zontreck.com" environment: From 960cf0d78952dfb9a7f08d6b12b706296c239041 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 16 May 2024 23:11:30 -0700 Subject: [PATCH 11/50] Fix null UUIDs --- lib/utils/uuid/UUID.dart | 2 +- pubspec.yaml | 2 +- test/uuid_test.dart | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/utils/uuid/UUID.dart b/lib/utils/uuid/UUID.dart index 374f9f8..594a869 100644 --- a/lib/utils/uuid/UUID.dart +++ b/lib/utils/uuid/UUID.dart @@ -89,7 +89,7 @@ class UUID { factory UUID.generate(int version, {List? parameters}) { List params = []; if (parameters == null) { - if (version != 4) { + if (version != 4 && version != 0) { return UUID.generate(4); } } else { diff --git a/pubspec.yaml b/pubspec.yaml index 7096051..506f400 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.4 +version: 1.0.5 homepage: "https://zontreck.com" environment: diff --git a/test/uuid_test.dart b/test/uuid_test.dart index d81fbfb..b4db5eb 100644 --- a/test/uuid_test.dart +++ b/test/uuid_test.dart @@ -75,4 +75,11 @@ void main() { expect(ID3.toString(), asString); }); + + test("Null UUID", () { + var expected = "00000000-0000-0000-0000-000000000000"; + var actual = UUID.ZERO.toString(); + + expect(actual, expected); + }); } From d6f0e05713c9c5276b6d97f778dbbc9d8f447907 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 19 May 2024 18:43:26 -0700 Subject: [PATCH 12/50] Make LibAC be web compatible for UUIDs --- lib/nbt/NbtUtils.dart | 14 +-- lib/nbt/Stream.dart | 8 ++ lib/utils/uuid/NbtUUID.dart | 209 ++++++++++++++++++++++++++++++++++++ lib/utils/uuid/UUID.dart | 58 ++++------ pubspec.yaml | 2 +- test/nbt_test.dart | 3 +- test/uuid_test.dart | 12 +-- 7 files changed, 254 insertions(+), 52 deletions(-) create mode 100644 lib/utils/uuid/NbtUUID.dart diff --git a/lib/nbt/NbtUtils.dart b/lib/nbt/NbtUtils.dart index 16497ba..54bce25 100644 --- a/lib/nbt/NbtUtils.dart +++ b/lib/nbt/NbtUtils.dart @@ -7,7 +7,7 @@ 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/uuid/NbtUUID.dart'; class NbtUtils { static void writeBoolean(CompoundTag tag, String name, bool b) { @@ -96,23 +96,23 @@ class NbtUtils { return [msb >> 32, msb, lsb >> 32, lsb]; } - static List _uuidToIntArray(UUID ID) { + static List _uuidToIntArray(NbtUUID ID) { return _msbLsbToIntArray( ID.getMostSignificantBits(), ID.getLeastSignificantBits()); } - static UUID _uuidFromIntArray(List values) { - return UUID((values[0] << 32 | values[1] & 4294967295), + static NbtUUID _uuidFromIntArray(List 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) { + static NbtUUID readUUID(CompoundTag tag, String name) { if (!tag.contains(name)) { - return UUID.ZERO; + return NbtUUID.ZERO; } else { return _uuidFromIntArray(tag.get(name)!.asIntArray()); } diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index 6849d26..2a64cea 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; class ByteLayer { @@ -376,6 +377,13 @@ 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 { diff --git a/lib/utils/uuid/NbtUUID.dart b/lib/utils/uuid/NbtUUID.dart new file mode 100644 index 0000000..0a13788 --- /dev/null +++ b/lib/utils/uuid/NbtUUID.dart @@ -0,0 +1,209 @@ +import 'package:libac_flutter/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()); + + 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; +} + +/* +class NbtUUID extends UUID{ + late final int LSB; + late final int MSB; + late final List _bytes; + + NbtUUID(int msb, int lsb) { + MSB = msb; + LSB = lsb; + + ByteLayer layer = ByteLayer(); + layer.writeLong(MSB); + layer.writeLong(LSB); + layer.resetPosition(); + + _bytes = layer.readBytes(16); + } + + static final UUID ZERO = UUID.generate(0); + + /// Validates whether the given [uuid] is a valid 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 { + return false; + } + } + + /// Parses the given [uuid] string and returns a UUID object. + static UUID parse(String uuid) { + if (validate(uuid)) { + final segments = uuid.split('-'); + if (segments.length != 5) { + throw const FormatException('Invalid UUID format'); + } + + 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); + 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); + 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.ZERO; + } + } + + @override + String toString() { + String hexBuilder = ""; + for (int byte in _bytes) { + hexBuilder += byte.toRadixString(16).padLeft(2, '0'); + } + 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? parameters}) { + List params = []; + if (parameters == null) { + if (version != 4 && version != 0) { + return UUID.generate(4); + } + } else { + params = parameters; + } + switch (version) { + case 0: + return UUID(0, 0); + case 3: + { + 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; + + ByteLayer layer = ByteLayer(); + + final namespaceBytes = utf8.encode(namespace); + layer.writeBytes(namespaceBytes); + final nameBytes = utf8.encode(name); + layer.writeBytes(nameBytes); + + var bytes = md5.convert(List.from(layer.bytes)).bytes; + layer.clear(); + layer.writeBytes(bytes); + + layer.unsetSetBit(6, 0xF0, 0x30); + layer.unsetSetBit(8, 0xC0, 0x80); + + layer.resetPosition(); + + var msb = layer.readUnsignedLong(); + var lsb = layer.readUnsignedLong(); + + layer.resetPosition(); + return UUID(msb, lsb); + } + 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.unsetSetBit(6, 0xF0, 0x40); + layer.unsetSetBit(8, 0xC0, 0x80); + + layer.resetPosition(); + + return UUID(layer.readUnsignedLong(), layer.readUnsignedLong()); + } + case 5: + { + ByteLayer layer = ByteLayer(); + 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.isNotEmpty) { + final namespaceBytes = utf8.encode(namespace); + layer.writeBytes(namespaceBytes); + } + + if (name.isNotEmpty) { + final nameBytes = utf8.encode(name); + layer.writeBytes(nameBytes); + } + + final hashBytes = sha1.convert(List.from(layer.bytes)).bytes; + layer.clear(); + layer.writeBytes(hashBytes); + + layer.unsetSetBit(6, 0xF0, 0x50); + layer.unsetSetBit(8, 0xC0, 0x80); + + layer.resetPosition(); + + return UUID(layer.readUnsignedLong(), layer.readUnsignedLong()); + } + default: + throw ArgumentError('Unsupported UUID version: $version'); + } + } +} +*/ diff --git a/lib/utils/uuid/UUID.dart b/lib/utils/uuid/UUID.dart index 594a869..9d53783 100644 --- a/lib/utils/uuid/UUID.dart +++ b/lib/utils/uuid/UUID.dart @@ -5,20 +5,10 @@ import 'package:crypto/crypto.dart'; import 'package:libac_flutter/nbt/Stream.dart'; class UUID { - late final int LSB; - late final int MSB; late final List _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 bytes) { + _bytes = bytes; } static final UUID ZERO = UUID.generate(0); @@ -43,9 +33,6 @@ 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) { @@ -61,10 +48,8 @@ class UUID { } layer.resetPosition(); - msb = layer.readLong(); - lsb = layer.readLong(); - return UUID(msb, lsb); + return UUID(layer.readBytes(16)); } else { return UUID.ZERO; } @@ -79,12 +64,6 @@ 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? parameters}) { List params = []; @@ -97,7 +76,16 @@ class UUID { } switch (version) { case 0: - return UUID(0, 0); + { + int i = 0; + int end = 16; + List bytes = []; + for (i = 0; i < end; i++) { + bytes.add(0); + } + + return UUID(bytes); + } case 3: { if (params.length != 2) { @@ -122,29 +110,21 @@ 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: { @@ -175,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 getBytes() { + return _bytes.take(16); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 506f400..1609632 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.5 +version: 1.0.6 homepage: "https://zontreck.com" environment: diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 37fa734..9f16f76 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -8,6 +8,7 @@ import 'package:libac_flutter/nbt/Stream.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart'; +import 'package:libac_flutter/utils/uuid/NbtUUID.dart'; import 'package:libac_flutter/utils/uuid/UUID.dart'; void main() { @@ -53,7 +54,7 @@ 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()); diff --git a/test/uuid_test.dart b/test/uuid_test.dart index b4db5eb..16467fb 100644 --- a/test/uuid_test.dart +++ b/test/uuid_test.dart @@ -27,8 +27,8 @@ void main() { 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 +38,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,8 +70,8 @@ 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); }); From 71f028f25c6d550de664df0109979d0193d0659e Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 01:06:22 -0700 Subject: [PATCH 13/50] Add some more helpers --- lib/utils/IOTools.dart | 67 ++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 lib/utils/IOTools.dart diff --git a/lib/utils/IOTools.dart b/lib/utils/IOTools.dart new file mode 100644 index 0000000..53d9fc6 --- /dev/null +++ b/lib/utils/IOTools.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +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); + } + + 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; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 1609632..b705875 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.6 +version: 1.0.7 homepage: "https://zontreck.com" environment: From a082210fa9668636f842378405fef195b823525d Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 14:22:08 -0700 Subject: [PATCH 14/50] Add a network packet system --- lib/nbt/NbtIo.dart | 20 ++++ lib/nbt/Tag.dart | 7 ++ lib/packets/packets.dart | 238 +++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 lib/packets/packets.dart diff --git a/lib/nbt/NbtIo.dart b/lib/nbt/NbtIo.dart index 810600e..cdcb537 100644 --- a/lib/nbt/NbtIo.dart +++ b/lib/nbt/NbtIo.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:libac_flutter/nbt/Stream.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; @@ -68,4 +70,22 @@ class NbtIo { _io.resetPosition(); return Tag.readNamedTag(_io) as CompoundTag; } + + static Future writeToStream(CompoundTag tag) async { + _io = ByteLayer(); + Tag.writeNamedTag(tag, _io); + _io.compress(); + + return _io.bytes; + } + + static Future readFromStream(Uint8List list) async { + _io = ByteLayer(); + _io.writeBytes(list); + _io.resetPosition(); + _io.decompress(); + _io.resetPosition(); + + return Tag.readNamedTag(_io) as CompoundTag; + } } diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index fa339fb..8caaa5e 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -264,6 +264,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) { diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart new file mode 100644 index 0000000..7951e1c --- /dev/null +++ b/lib/packets/packets.dart @@ -0,0 +1,238 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:libac_flutter/nbt/NbtIo.dart'; +import 'package:libac_flutter/nbt/NbtUtils.dart'; +import 'package:libac_flutter/nbt/Tag.dart'; +import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; +import 'package:libac_flutter/nbt/impl/StringTag.dart'; + +class PacketServer { + static Future start() async { + final ServerSocket socket = + await ServerSocket.bind(InternetAddress.anyIPv4, 25306); + print("Server now listening on port 25306"); + + await for (var sock in socket) { + print( + "New connection from ${sock.remoteAddress.address}:${sock.remotePort}"); + + sock.listen((data) async { + S2CStatusResponse response = S2CStatusResponse(); + try { + CompoundTag tag = await NbtIo.readFromStream(data); + } catch (E) { + response.status = false; + response.reason = "Malformed request packet"; + + sock.write( + await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); + } + }, onDone: () { + sock.close(); + }, onError: () { + sock.close(); + }); + } + } +} + +abstract class IPacket with NbtEncodable, JsonEncodable { + String getChannelID(); + + // This function handles the packet + Future handlePacket(); + NetworkDirection direction(); +} + +class PacketRegistry { + Map _registry = {}; + static PacketRegistry _inst = PacketRegistry._(); + + PacketRegistry._(); + + factory PacketRegistry() { + return _inst; + } + + 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(S2CStatusResponse(), () { + return S2CStatusResponse(); + }); + } +} + +enum NetworkDirection { ClientToServer, ServerToClient } + +enum PacketOperation { Encode, Decode } + +class S2CStatusResponse implements IPacket { + bool status = false; + String reason = ""; + late IPacket payload; + + @override + NetworkDirection direction() { + return NetworkDirection.ServerToClient; + } + + @override + String getChannelID() { + return "StatusResponse"; + } + + @override + Future handlePacket() async { + // No handling is required for this packet type + if (status) { + payload.handlePacket(); + } + } + + @override + void decodeJson(String encoded) { + fromJson(json.decode(encoded)); + } + + @override + void decodeTag(Tag encoded) { + CompoundTag ct = encoded as CompoundTag; + status = NbtUtils.readBoolean(ct, "status"); + reason = ct.get("reason")!.asString(); + + if (ct.contains("payload")) { + String channel = ct.get("pc")!.asString(); + payload = PacketRegistry().getPacket(channel); + + payload.decodeTag(ct.get("payload")!); + } + } + + @override + String encodeJson() { + return json.encode(toJson()); + } + + @override + Tag encodeTag() { + CompoundTag tag = CompoundTag(); + NbtUtils.writeBoolean(tag, "status", status); + tag.put("reason", StringTag.valueOf(reason)); + if (status) { + tag.put("pc", StringTag.valueOf(payload.getChannelID())); + tag.put("payload", payload.encodeTag()); + } + + return tag; + } + + @override + void fromJson(Map params) { + status = params["status"] as bool; + reason = params["reason"] as String; + + if (status) { + String channel = params['pc'] as String; + payload = PacketRegistry().getPacket(channel); + payload.fromJson(params['payload']); + } + } + + @override + Map toJson() { + Map map = { + "status": status, + "reason": reason, + }; + + if (status) { + map.addAll({"pc": payload.getChannelID(), "payload": payload.toJson()}); + } + + return map; + } +} + +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 params) { + String cap = params['cap'] as String; + payload = PacketRegistry().getPacket(cap); + payload.fromJson(params['payload']); + } + + @override + String getChannelID() { + return "C2SRequest"; + } + + @override + Future handlePacket() async { + // This has no internal handling + } + + @override + Map toJson() { + return {"cap": payload.getChannelID(), "payload": payload.toJson()}; + } +} + +mixin JsonEncodable { + String encodeJson(); + void decodeJson(String params); + + Map toJson(); + void fromJson(Map js); +} + +mixin NbtEncodable { + Tag encodeTag(); + void decodeTag(Tag tag); +} diff --git a/pubspec.yaml b/pubspec.yaml index b705875..9d2672a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.7 +version: 1.0.8 homepage: "https://zontreck.com" environment: From 178cc201c1627bf909ac83fe3b7fd378b74c70bb Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 15:53:29 -0700 Subject: [PATCH 15/50] Finish basic server and client impl --- lib/nbt/NbtUtils.dart | 12 +-- lib/nbt/impl/CompoundTag.dart | 107 ++++++++++++++++++++++--- lib/packets/packets.dart | 147 +++++++++++++++++++++++++--------- test/nbt_test.dart | 4 +- 4 files changed, 213 insertions(+), 57 deletions(-) diff --git a/lib/nbt/NbtUtils.dart b/lib/nbt/NbtUtils.dart index 54bce25..8c2cd76 100644 --- a/lib/nbt/NbtUtils.dart +++ b/lib/nbt/NbtUtils.dart @@ -15,7 +15,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 +30,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 +46,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 +63,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 +83,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()); @@ -111,7 +111,7 @@ class NbtUtils { } static NbtUUID readUUID(CompoundTag tag, String name) { - if (!tag.contains(name)) { + if (!tag.containsKey(name)) { return NbtUUID.ZERO; } else { return _uuidFromIntArray(tag.get(name)!.asIntArray()); diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index 5f549d3..f552583 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -1,7 +1,7 @@ import 'package:libac_flutter/nbt/Stream.dart'; import 'package:libac_flutter/nbt/Tag.dart'; -class CompoundTag extends Tag { +class CompoundTag extends Tag implements Map { late final Map 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; @@ -103,4 +95,99 @@ class CompoundTag extends Tag { } 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 other) { + value.addAll(other); + } + + @override + void addEntries(Iterable> newEntries) { + value.addEntries(newEntries); + } + + @override + Map cast() { + 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> 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 get keys => value.keys; + + @override + int get length => value.length; + + @override + Map map( + MapEntry 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 get values => value.values; } diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 7951e1c..4159441 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -2,18 +2,17 @@ import 'dart:convert'; import 'dart:io'; import 'package:libac_flutter/nbt/NbtIo.dart'; -import 'package:libac_flutter/nbt/NbtUtils.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart'; class PacketServer { + static ServerSocket? socket; static Future start() async { - final ServerSocket socket = - await ServerSocket.bind(InternetAddress.anyIPv4, 25306); + socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306); print("Server now listening on port 25306"); - await for (var sock in socket) { + await for (var sock in socket!) { print( "New connection from ${sock.remoteAddress.address}:${sock.remotePort}"); @@ -21,11 +20,16 @@ class PacketServer { S2CStatusResponse response = S2CStatusResponse(); try { CompoundTag tag = await NbtIo.readFromStream(data); + C2SRequestPacket request = C2SRequestPacket(); + request.decodeTag(tag); + + PacketResponse reply = await request.handlePacket(); + // Server uses NBT to communicate + sock.add(await NbtIo.writeToStream(reply.replyDataTag)); } catch (E) { - response.status = false; response.reason = "Malformed request packet"; - sock.write( + sock.add( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); } }, onDone: () { @@ -37,14 +41,99 @@ class PacketServer { } } +class PacketClient { + Socket? socket; + bool connected = false; + + PacketClient(String IPAddress) { + startConnect(IPAddress); + } + + Future startConnect(String IPAddress) async { + try { + socket = await Socket.connect(IPAddress, 25306); + connected = true; + } catch (E) { + connected = false; + socket = null; + } + } + + Future send(IPacket packet) async { + socket!.add(await NbtIo.writeToStream(packet.encodeTag().asCompoundTag())); + CompoundTag ct = CompoundTag(); + socket!.listen((data) async { + ct = await NbtIo.readFromStream(data); + }); + + return ct; + } + + void close() { + socket!.close(); + connected = false; + } +} + abstract class IPacket with NbtEncodable, JsonEncodable { String getChannelID(); // This function handles the packet - Future handlePacket(); + Future handlePacket(); 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 js) {} + + @override + String getChannelID() { + return "StopServer"; + } + + @override + Future handlePacket() async { + PacketServer.socket!.close(); + + return PacketResponse(replyDataTag: CompoundTag()); + } + + @override + Map toJson() { + return {}; + } +} + +class PacketResponse { + static final nil = PacketResponse(replyDataTag: CompoundTag()); + + PacketResponse({required this.replyDataTag}); + + CompoundTag replyDataTag = CompoundTag(); +} + class PacketRegistry { Map _registry = {}; static PacketRegistry _inst = PacketRegistry._(); @@ -55,6 +144,8 @@ class PacketRegistry { return _inst; } + int get count => _registry.length; + void register(IPacket packet, IPacket Function() packetResolver) { _registry[packet.getChannelID()] = packetResolver; } @@ -71,6 +162,12 @@ class PacketRegistry { register(S2CStatusResponse(), () { return S2CStatusResponse(); }); + register(C2SRequestPacket(), () { + return C2SRequestPacket(); + }); + register(StopServerPacket(), () { + return StopServerPacket(); + }); } } @@ -79,9 +176,7 @@ enum NetworkDirection { ClientToServer, ServerToClient } enum PacketOperation { Encode, Decode } class S2CStatusResponse implements IPacket { - bool status = false; String reason = ""; - late IPacket payload; @override NetworkDirection direction() { @@ -94,11 +189,9 @@ class S2CStatusResponse implements IPacket { } @override - Future handlePacket() async { + Future handlePacket() async { // No handling is required for this packet type - if (status) { - payload.handlePacket(); - } + return PacketResponse.nil; } @override @@ -109,15 +202,7 @@ class S2CStatusResponse implements IPacket { @override void decodeTag(Tag encoded) { CompoundTag ct = encoded as CompoundTag; - status = NbtUtils.readBoolean(ct, "status"); reason = ct.get("reason")!.asString(); - - if (ct.contains("payload")) { - String channel = ct.get("pc")!.asString(); - payload = PacketRegistry().getPacket(channel); - - payload.decodeTag(ct.get("payload")!); - } } @override @@ -128,39 +213,22 @@ class S2CStatusResponse implements IPacket { @override Tag encodeTag() { CompoundTag tag = CompoundTag(); - NbtUtils.writeBoolean(tag, "status", status); tag.put("reason", StringTag.valueOf(reason)); - if (status) { - tag.put("pc", StringTag.valueOf(payload.getChannelID())); - tag.put("payload", payload.encodeTag()); - } return tag; } @override void fromJson(Map params) { - status = params["status"] as bool; reason = params["reason"] as String; - - if (status) { - String channel = params['pc'] as String; - payload = PacketRegistry().getPacket(channel); - payload.fromJson(params['payload']); - } } @override Map toJson() { Map map = { - "status": status, "reason": reason, }; - if (status) { - map.addAll({"pc": payload.getChannelID(), "payload": payload.toJson()}); - } - return map; } } @@ -214,8 +282,9 @@ class C2SRequestPacket implements IPacket { } @override - Future handlePacket() async { + Future handlePacket() async { // This has no internal handling + return payload.handlePacket(); } @override diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 9f16f76..b30be1f 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -17,7 +17,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"); }); @@ -37,7 +37,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"); }); From f5d1d741cb14bc820719bea4f96e080bcca0f936 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 16:33:49 -0700 Subject: [PATCH 16/50] Return a invalid value when no reply --- lib/packets/packets.dart | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 4159441..fb8d90d 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -60,6 +60,9 @@ class PacketClient { } Future send(IPacket packet) async { + if (!connected) { + return CompoundTag(); + } socket!.add(await NbtIo.writeToStream(packet.encodeTag().asCompoundTag())); CompoundTag ct = CompoundTag(); socket!.listen((data) async { diff --git a/pubspec.yaml b/pubspec.yaml index 9d2672a..0fad868 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.8 +version: 1.0.9 homepage: "https://zontreck.com" environment: From fe181555ef7d531e1dfe21b3d81bdd41b4a8537e Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 16:41:36 -0700 Subject: [PATCH 17/50] Add a failsafe to prevent server crash --- lib/packets/packets.dart | 44 +++++++++++++++++++++++----------------- pubspec.yaml | 2 +- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index fb8d90d..00b87ce 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -16,27 +16,31 @@ class PacketServer { print( "New connection from ${sock.remoteAddress.address}:${sock.remotePort}"); - sock.listen((data) async { - S2CStatusResponse response = S2CStatusResponse(); - try { - CompoundTag tag = await NbtIo.readFromStream(data); - C2SRequestPacket request = C2SRequestPacket(); - request.decodeTag(tag); + try { + sock.listen((data) async { + S2CStatusResponse response = S2CStatusResponse(); + try { + CompoundTag tag = await NbtIo.readFromStream(data); + C2SRequestPacket request = C2SRequestPacket(); + request.decodeTag(tag); - PacketResponse reply = await request.handlePacket(); - // Server uses NBT to communicate - sock.add(await NbtIo.writeToStream(reply.replyDataTag)); - } catch (E) { - response.reason = "Malformed request packet"; + PacketResponse reply = await request.handlePacket(); + // Server uses NBT to communicate + sock.add(await NbtIo.writeToStream(reply.replyDataTag)); + } catch (E, stack) { + response.reason = "Malformed request packet"; - sock.add( - await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); - } - }, onDone: () { + sock.add( + await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); + } + }, onDone: () { + sock.close(); + }, onError: () { + sock.close(); + }); + } catch (E) { sock.close(); - }, onError: () { - sock.close(); - }); + } } } } @@ -53,9 +57,11 @@ class PacketClient { try { socket = await Socket.connect(IPAddress, 25306); connected = true; - } catch (E) { + } catch (E, stack) { connected = false; socket = null; + + print(stack); } } diff --git a/pubspec.yaml b/pubspec.yaml index 0fad868..e4d818b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.9 +version: 1.0.10 homepage: "https://zontreck.com" environment: From 485619eea6bf65176a414793bf5b390a3ab7ad01 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 16:44:04 -0700 Subject: [PATCH 18/50] Hotfix: Fix a small oversight --- lib/packets/packets.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 00b87ce..2bde3f7 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -49,9 +49,7 @@ class PacketClient { Socket? socket; bool connected = false; - PacketClient(String IPAddress) { - startConnect(IPAddress); - } + PacketClient(String IPAddress); Future startConnect(String IPAddress) async { try { From 5c4a8c6a5874b89147730c0e6cedfa178b46972f Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 16:44:27 -0700 Subject: [PATCH 19/50] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e4d818b..3f90342 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.10 +version: 1.0.11 homepage: "https://zontreck.com" environment: From 3be0a9ab5fc52772359197171960a703cd4c0504 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 20:10:41 -0700 Subject: [PATCH 20/50] Finish testing and assembling a basic server and client structure --- bin/client_test.dart | 24 +++++ bin/server_test.dart | 5 + compile.sh | 7 ++ lib/packets/packets.dart | 192 +++++++++++++++++++++++++++++++-------- pubspec.yaml | 2 +- 5 files changed, 192 insertions(+), 38 deletions(-) create mode 100644 bin/client_test.dart create mode 100644 bin/server_test.dart create mode 100755 compile.sh diff --git a/bin/client_test.dart b/bin/client_test.dart new file mode 100644 index 0000000..3fed86a --- /dev/null +++ b/bin/client_test.dart @@ -0,0 +1,24 @@ +import 'package:libac_flutter/nbt/Stream.dart'; +import 'package:libac_flutter/nbt/Tag.dart'; +import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; +import 'package:libac_flutter/packets/packets.dart'; + +void main() async { + PacketRegistry reg = PacketRegistry(); + reg.registerDefaults(); + + PacketClient client = PacketClient(); + await client.startConnect("127.0.0.1"); + + S2CResponse response = await client.send(C2SPing()); + CompoundTag tag = response.contents; + StringBuilder builder = StringBuilder(); + Tag.writeStringifiedNamedTag(tag, builder, 0); + + print("Response from server: \n${builder}"); + + await client.send(StopServerPacket()); + + if (client.connected) await client.close(); + return; +} diff --git a/bin/server_test.dart b/bin/server_test.dart new file mode 100644 index 0000000..51d11bb --- /dev/null +++ b/bin/server_test.dart @@ -0,0 +1,5 @@ +import 'package:libac_flutter/packets/packets.dart'; + +void main() async { + await PacketServer.start(); +} diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..42dd2b7 --- /dev/null +++ b/compile.sh @@ -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 \ No newline at end of file diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 2bde3f7..9556314 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -6,8 +7,11 @@ import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart'; +import '../nbt/Stream.dart'; + class PacketServer { static ServerSocket? socket; + static bool shouldRestart = true; static Future start() async { socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306); print("Server now listening on port 25306"); @@ -18,24 +22,38 @@ class PacketServer { try { sock.listen((data) async { - S2CStatusResponse response = S2CStatusResponse(); + S2CResponse response = S2CResponse(); 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.handlePacket(); + PacketResponse reply = await request.handleServerPacket(); // Server uses NBT to communicate sock.add(await NbtIo.writeToStream(reply.replyDataTag)); } catch (E, stack) { - response.reason = "Malformed request packet"; + response.contents + .put("error", StringTag.valueOf("Malformed request packet")); sock.add( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); + } finally { + await sock.flush(); + sock.close(); + + if (!shouldRestart) { + await socket!.close(); + } } }, onDone: () { sock.close(); - }, onError: () { + }, onError: (E) { + print("ERROR: ${E}"); sock.close(); }); } catch (E) { @@ -48,36 +66,53 @@ class PacketServer { class PacketClient { Socket? socket; bool connected = false; + String lastIP = ""; - PacketClient(String IPAddress); + PacketClient(); Future startConnect(String IPAddress) async { try { socket = await Socket.connect(IPAddress, 25306); connected = true; + lastIP = IPAddress; } catch (E, stack) { connected = false; socket = null; - - print(stack); } } - Future send(IPacket packet) async { + Future send(IPacket packet) async { if (!connected) { - return CompoundTag(); + return S2CResponse(); } - socket!.add(await NbtIo.writeToStream(packet.encodeTag().asCompoundTag())); + C2SRequestPacket request = C2SRequestPacket(); + request.payload = packet; + request.cap = packet.getChannelID(); + + socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag())); CompoundTag ct = CompoundTag(); + Completer onCompletion = Completer(); socket!.listen((data) async { - ct = await NbtIo.readFromStream(data); + CompoundTag result = await NbtIo.readFromStream(data); + ct.put("result", result); + }, onError: (E) { + print("ERROR: ${E}"); + }, onDone: () { + print("Request completed"); + onCompletion.complete(); }); - return ct; + await onCompletion.future; + await close(); + await startConnect(lastIP); + S2CResponse reply = S2CResponse(); + reply.decodeTag(ct.get("result")!.asCompoundTag()); + + return reply; } - void close() { - socket!.close(); + Future close() async { + await socket!.close(); connected = false; } } @@ -86,7 +121,8 @@ abstract class IPacket with NbtEncodable, JsonEncodable { String getChannelID(); // This function handles the packet - Future handlePacket(); + Future handleServerPacket(); + Future handleClientPacket(); NetworkDirection direction(); } @@ -121,16 +157,24 @@ class StopServerPacket extends IPacket { } @override - Future handlePacket() async { - PacketServer.socket!.close(); + Future handleServerPacket() async { + // We're now on the server. Handle the packet with a response to the client + PacketServer.shouldRestart = false; - return PacketResponse(replyDataTag: CompoundTag()); + S2CResponse response = S2CResponse(); + + return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag()); } @override Map toJson() { return {}; } + + @override + Future handleClientPacket() { + throw UnimplementedError(); + } } class PacketResponse { @@ -145,7 +189,9 @@ class PacketRegistry { Map _registry = {}; static PacketRegistry _inst = PacketRegistry._(); - PacketRegistry._(); + PacketRegistry._() { + registerDefaults(); + } factory PacketRegistry() { return _inst; @@ -166,8 +212,8 @@ class PacketRegistry { } void registerDefaults() { - register(S2CStatusResponse(), () { - return S2CStatusResponse(); + register(S2CResponse(), () { + return S2CResponse(); }); register(C2SRequestPacket(), () { return C2SRequestPacket(); @@ -175,6 +221,9 @@ class PacketRegistry { register(StopServerPacket(), () { return StopServerPacket(); }); + register(C2SPing(), () { + return C2SPing(); + }); } } @@ -182,8 +231,8 @@ enum NetworkDirection { ClientToServer, ServerToClient } enum PacketOperation { Encode, Decode } -class S2CStatusResponse implements IPacket { - String reason = ""; +class S2CResponse implements IPacket { + CompoundTag contents = CompoundTag(); @override NetworkDirection direction() { @@ -192,12 +241,12 @@ class S2CStatusResponse implements IPacket { @override String getChannelID() { - return "StatusResponse"; + return "Response"; } @override - Future handlePacket() async { - // No handling is required for this packet type + Future handleServerPacket() async { + // We can't predict handling for this type, it is a data packet response with no pre-defined structure. return PacketResponse.nil; } @@ -209,7 +258,7 @@ class S2CStatusResponse implements IPacket { @override void decodeTag(Tag encoded) { CompoundTag ct = encoded as CompoundTag; - reason = ct.get("reason")!.asString(); + contents = ct.get("contents")!.asCompoundTag(); } @override @@ -220,23 +269,22 @@ class S2CStatusResponse implements IPacket { @override Tag encodeTag() { CompoundTag tag = CompoundTag(); - tag.put("reason", StringTag.valueOf(reason)); + tag.put("contents", contents); return tag; } @override - void fromJson(Map params) { - reason = params["reason"] as String; - } + void fromJson(Map params) {} @override Map toJson() { - Map map = { - "reason": reason, - }; + return {}; // Operation is not supported at this time. + } - return map; + @override + Future handleClientPacket() async { + // We haven't got anything to process. This is structured data } } @@ -289,15 +337,85 @@ class C2SRequestPacket implements IPacket { } @override - Future handlePacket() async { + Future handleServerPacket() async { // This has no internal handling - return payload.handlePacket(); + return payload.handleServerPacket(); } @override Map toJson() { return {"cap": payload.getChannelID(), "payload": payload.toJson()}; } + + @override + Future 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 js) { + clientVersion = js['version'] as String; + } + + @override + String getChannelID() { + return "Ping"; + } + + @override + Future 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 toJson() { + return {"version": clientVersion}; + } + + @override + Future handleClientPacket() { + throw UnimplementedError(); + } } mixin JsonEncodable { diff --git a/pubspec.yaml b/pubspec.yaml index 3f90342..51268cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.11 +version: 1.0.12 homepage: "https://zontreck.com" environment: From d4b7a12187e7a00d65dabec8a9469de792072c58 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 May 2024 20:10:58 -0700 Subject: [PATCH 21/50] Ignore files placed in the out folder. --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 362ff8e..2ec0cee 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,6 @@ doc/api/ .idea -*.iml \ No newline at end of file +*.iml + +out \ No newline at end of file From cc151e910934a4b0a6b83705c560d4bd48cb7c4e Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 10:13:51 -0700 Subject: [PATCH 22/50] Add a converter method --- lib/utils/uuid/NbtUUID.dart | 8 ++++++++ pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/utils/uuid/NbtUUID.dart b/lib/utils/uuid/NbtUUID.dart index 0a13788..c3e2b3b 100644 --- a/lib/utils/uuid/NbtUUID.dart +++ b/lib/utils/uuid/NbtUUID.dart @@ -25,6 +25,14 @@ class NbtUUID { /// Returns the Least Significant Bits (LSB) long value of the UUID. int getLeastSignificantBits() => LSB; + + UUID toUUID() { + ByteLayer layer = ByteLayer(); + layer.writeUnsignedLong(MSB); + layer.writeUnsignedLong(LSB); + + return UUID(layer.readBytes(16)); + } } /* diff --git a/pubspec.yaml b/pubspec.yaml index 51268cb..5f6d158 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.12 +version: 1.0.13 homepage: "https://zontreck.com" environment: From 86386f97a29726227c15bac8aef857b8196da568 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 11:25:14 -0700 Subject: [PATCH 23/50] Add some more verbosity to the server --- lib/packets/packets.dart | 21 ++++++++++++++++++--- pubspec.yaml | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 9556314..b7c89d3 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -35,11 +35,19 @@ class PacketServer { 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, sending malformed request response\n${stack}\n\n${E}\n\n"); + sock.add( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); } finally { @@ -94,12 +102,19 @@ class PacketClient { Completer 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); - }, onError: (E) { - print("ERROR: ${E}"); + + onCompletion.complete(); + }, onError: (E, stack) { + print("ERROR: ${E}\n${stack}"); + if (!onCompletion.isCompleted) onCompletion.complete(); }, onDone: () { print("Request completed"); - onCompletion.complete(); }); await onCompletion.future; diff --git a/pubspec.yaml b/pubspec.yaml index 5f6d158..16c5da3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.13 +version: 1.0.14 homepage: "https://zontreck.com" environment: From 95d769bf891de134562bab83566913aea6d8e938 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 11:35:41 -0700 Subject: [PATCH 24/50] Attempt to fix null UUIDs being handled strangely when serializing to NbtUUID --- lib/utils/uuid/UUID.dart | 13 ++++++------- pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/utils/uuid/UUID.dart b/lib/utils/uuid/UUID.dart index 9d53783..5f4fdc7 100644 --- a/lib/utils/uuid/UUID.dart +++ b/lib/utils/uuid/UUID.dart @@ -77,14 +77,13 @@ class UUID { switch (version) { case 0: { - int i = 0; - int end = 16; - List bytes = []; - for (i = 0; i < end; i++) { - bytes.add(0); - } + ByteLayer layer = ByteLayer(); + layer.writeLong(0); + layer.writeLong(0); - return UUID(bytes); + layer.resetPosition(); + + return UUID(layer.readBytes(16)); } case 3: { diff --git a/pubspec.yaml b/pubspec.yaml index 16c5da3..0eb0ba3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.14 +version: 1.0.15 homepage: "https://zontreck.com" environment: From 312176d37f06c5275ea77e95bf500ba91b5ecd73 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 11:40:16 -0700 Subject: [PATCH 25/50] Add a missing instruction, and a testcase --- lib/utils/uuid/NbtUUID.dart | 181 +----------------------------------- test/nbt_test.dart | 9 ++ 2 files changed, 12 insertions(+), 178 deletions(-) diff --git a/lib/utils/uuid/NbtUUID.dart b/lib/utils/uuid/NbtUUID.dart index c3e2b3b..e17f028 100644 --- a/lib/utils/uuid/NbtUUID.dart +++ b/lib/utils/uuid/NbtUUID.dart @@ -13,6 +13,7 @@ class NbtUUID { factory NbtUUID.fromUUID(UUID id) { ByteLayer layer = ByteLayer(); layer.writeBytes(id.getBytes()); + layer.resetPosition(); int MSB = layer.readLong(); int LSB = layer.readLong(); @@ -27,191 +28,15 @@ class NbtUUID { int getLeastSignificantBits() => LSB; UUID toUUID() { - ByteLayer layer = ByteLayer(); - layer.writeUnsignedLong(MSB); - layer.writeUnsignedLong(LSB); - - return UUID(layer.readBytes(16)); - } -} - -/* -class NbtUUID extends UUID{ - late final int LSB; - late final int MSB; - late final List _bytes; - - NbtUUID(int msb, int lsb) { - MSB = msb; - LSB = lsb; - ByteLayer layer = ByteLayer(); layer.writeLong(MSB); layer.writeLong(LSB); - layer.resetPosition(); - _bytes = layer.readBytes(16); - } - - static final UUID ZERO = UUID.generate(0); - - /// Validates whether the given [uuid] is a valid 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 { - return false; - } - } - - /// Parses the given [uuid] string and returns a UUID object. - static UUID parse(String uuid) { - if (validate(uuid)) { - final segments = uuid.split('-'); - if (segments.length != 5) { - throw const FormatException('Invalid UUID format'); - } - - 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); - 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); - 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.ZERO; - } + return UUID(layer.readBytes(16)); } @override String toString() { - String hexBuilder = ""; - for (int byte in _bytes) { - hexBuilder += byte.toRadixString(16).padLeft(2, '0'); - } - 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? parameters}) { - List params = []; - if (parameters == null) { - if (version != 4 && version != 0) { - return UUID.generate(4); - } - } else { - params = parameters; - } - switch (version) { - case 0: - return UUID(0, 0); - case 3: - { - 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; - - ByteLayer layer = ByteLayer(); - - final namespaceBytes = utf8.encode(namespace); - layer.writeBytes(namespaceBytes); - final nameBytes = utf8.encode(name); - layer.writeBytes(nameBytes); - - var bytes = md5.convert(List.from(layer.bytes)).bytes; - layer.clear(); - layer.writeBytes(bytes); - - layer.unsetSetBit(6, 0xF0, 0x30); - layer.unsetSetBit(8, 0xC0, 0x80); - - layer.resetPosition(); - - var msb = layer.readUnsignedLong(); - var lsb = layer.readUnsignedLong(); - - layer.resetPosition(); - return UUID(msb, lsb); - } - 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.unsetSetBit(6, 0xF0, 0x40); - layer.unsetSetBit(8, 0xC0, 0x80); - - layer.resetPosition(); - - return UUID(layer.readUnsignedLong(), layer.readUnsignedLong()); - } - case 5: - { - ByteLayer layer = ByteLayer(); - 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.isNotEmpty) { - final namespaceBytes = utf8.encode(namespace); - layer.writeBytes(namespaceBytes); - } - - if (name.isNotEmpty) { - final nameBytes = utf8.encode(name); - layer.writeBytes(nameBytes); - } - - final hashBytes = sha1.convert(List.from(layer.bytes)).bytes; - layer.clear(); - layer.writeBytes(hashBytes); - - layer.unsetSetBit(6, 0xF0, 0x50); - layer.unsetSetBit(8, 0xC0, 0x80); - - layer.resetPosition(); - - return UUID(layer.readUnsignedLong(), layer.readUnsignedLong()); - } - default: - throw ArgumentError('Unsupported UUID version: $version'); - } + return toUUID().toString(); } } -*/ diff --git a/test/nbt_test.dart b/test/nbt_test.dart index b30be1f..2930c57 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -86,4 +86,13 @@ void main() { // Expect that the file exists expect(file.existsSync(), true); }); + + 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); + }); } From 18d55666e68e5f3677517f8e35e66cc0307f2bbf Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 11:44:48 -0700 Subject: [PATCH 26/50] Fix another error --- lib/utils/uuid/NbtUUID.dart | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/uuid/NbtUUID.dart b/lib/utils/uuid/NbtUUID.dart index e17f028..af766e8 100644 --- a/lib/utils/uuid/NbtUUID.dart +++ b/lib/utils/uuid/NbtUUID.dart @@ -31,6 +31,7 @@ class NbtUUID { ByteLayer layer = ByteLayer(); layer.writeLong(MSB); layer.writeLong(LSB); + layer.resetPosition(); return UUID(layer.readBytes(16)); } diff --git a/pubspec.yaml b/pubspec.yaml index 0eb0ba3..d5b5360 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.15 +version: 1.0.16 homepage: "https://zontreck.com" environment: From 302595d45daed1ae3937e4024a48999a1fe054b1 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 12:15:52 -0700 Subject: [PATCH 27/50] Add two helper API functions to PathHelper --- lib/utils/IOTools.dart | 8 ++++++++ pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/utils/IOTools.dart b/lib/utils/IOTools.dart index 53d9fc6..3152655 100644 --- a/lib/utils/IOTools.dart +++ b/lib/utils/IOTools.dart @@ -12,6 +12,14 @@ class PathHelper { return PathHelper(pth: startPath); } + bool exists() { + return File(build()).existsSync(); + } + + PathHelper clone() { + return PathHelper.builder(build()); + } + PathHelper resolve(String path2) { pth += Platform.pathSeparator + path2; return this; diff --git a/pubspec.yaml b/pubspec.yaml index d5b5360..8d15db5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.16 +version: 1.0.17 homepage: "https://zontreck.com" environment: From 6ba00044af44402cda3d3a7f01d2386b2b9558b8 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 17:15:02 -0700 Subject: [PATCH 28/50] Adds a time utility for keeping track of time --- lib/utils/TimeUtils.dart | 122 +++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 lib/utils/TimeUtils.dart diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart new file mode 100644 index 0000000..f326c73 --- /dev/null +++ b/lib/utils/TimeUtils.dart @@ -0,0 +1,122 @@ +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; + autofix(); + } + + void autofix() { + int totalSeconds = getTotalSeconds(); + + 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; + } + + factory Time.fromNotation(String notation) { + int hours = 0; + int minutes = 0; + int seconds = 0; + + List 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); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8d15db5..8e53ac3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.17 +version: 1.0.18 homepage: "https://zontreck.com" environment: From d4a6a3609efffa0d0a1f937d8d08a49181c3b38f Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 17:25:14 -0700 Subject: [PATCH 29/50] Adds a testsuite to Time API. --- lib/utils/TimeUtils.dart | 12 ++++++++++++ test/time_test.dart | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 test/time_test.dart diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart index f326c73..5785fd5 100644 --- a/lib/utils/TimeUtils.dart +++ b/lib/utils/TimeUtils.dart @@ -1,3 +1,5 @@ +import 'package:libac_flutter/nbt/Stream.dart'; + class Time { int hours; int minutes; @@ -119,4 +121,14 @@ class Time { 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}"; + } } diff --git a/test/time_test.dart b/test/time_test.dart new file mode 100644 index 0000000..91eba61 --- /dev/null +++ b/test/time_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:libac_flutter/utils/TimeUtils.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); + }); +} From 730535d4b8860e75aa0f18ed48d8e2093e957a88 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 17:43:52 -0700 Subject: [PATCH 30/50] Sanity check seconds, and lock to 0 or above. Disallow negative time --- lib/utils/TimeUtils.dart | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart index 5785fd5..20d04b6 100644 --- a/lib/utils/TimeUtils.dart +++ b/lib/utils/TimeUtils.dart @@ -50,11 +50,14 @@ class Time { 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); diff --git a/pubspec.yaml b/pubspec.yaml index 8e53ac3..231c7f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.18 +version: 1.0.19 homepage: "https://zontreck.com" environment: From 5f9f0e08effa676c76db97391a11b1ed0fd90979 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 17:47:50 -0700 Subject: [PATCH 31/50] Add a copy method to Time --- lib/utils/TimeUtils.dart | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart index 20d04b6..07ec36f 100644 --- a/lib/utils/TimeUtils.dart +++ b/lib/utils/TimeUtils.dart @@ -75,6 +75,11 @@ class Time { this.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; diff --git a/pubspec.yaml b/pubspec.yaml index 231c7f7..856b5c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_flutter description: "Aria's Creations code library" -version: 1.0.19 +version: 1.0.20 homepage: "https://zontreck.com" environment: From dd40eace38dda2b15cd98a29ae1d913762b1f3ff Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 23 May 2024 17:49:35 -0700 Subject: [PATCH 32/50] Adds a secondary copy method --- lib/utils/TimeUtils.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart index 07ec36f..6778cba 100644 --- a/lib/utils/TimeUtils.dart +++ b/lib/utils/TimeUtils.dart @@ -75,6 +75,10 @@ class Time { 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); From 1133992078a5624c4e6e71c7072d1799615261ba Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 14:36:51 -0700 Subject: [PATCH 33/50] Change package name, remove flutter as SDK --- bin/client_test.dart | 8 ++++---- bin/server_test.dart | 2 +- lib/nbt/NbtIo.dart | 8 ++++---- lib/nbt/NbtUtils.dart | 15 +++++++-------- lib/nbt/SnbtIo.dart | 6 +++--- lib/nbt/Tag.dart | 19 +++++++++---------- lib/nbt/impl/ByteArrayTag.dart | 4 ++-- lib/nbt/impl/ByteTag.dart | 4 ++-- lib/nbt/impl/CompoundTag.dart | 4 ++-- lib/nbt/impl/DoubleTag.dart | 4 ++-- lib/nbt/impl/EndTag.dart | 4 ++-- lib/nbt/impl/FloatTag.dart | 4 ++-- lib/nbt/impl/IntArrayTag.dart | 4 ++-- lib/nbt/impl/IntTag.dart | 4 ++-- lib/nbt/impl/ListTag.dart | 5 ++--- lib/nbt/impl/LongArrayTag.dart | 4 ++-- lib/nbt/impl/LongTag.dart | 4 ++-- lib/nbt/impl/ShortTag.dart | 4 ++-- lib/nbt/impl/StringTag.dart | 4 ++-- lib/packets/packets.dart | 9 ++++----- lib/utils/TimeUtils.dart | 2 +- lib/utils/uuid/NbtUUID.dart | 3 +-- lib/utils/uuid/UUID.dart | 3 ++- pubspec.yaml | 23 +++++++++-------------- test/hash_test.dart | 5 +++-- test/nbt_test.dart | 21 +++++++++++---------- test/time_test.dart | 5 +++-- test/uuid_test.dart | 7 ++++--- 28 files changed, 92 insertions(+), 97 deletions(-) diff --git a/bin/client_test.dart b/bin/client_test.dart index 3fed86a..5d5aa92 100644 --- a/bin/client_test.dart +++ b/bin/client_test.dart @@ -1,7 +1,7 @@ -import 'package:libac_flutter/nbt/Stream.dart'; -import 'package:libac_flutter/nbt/Tag.dart'; -import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; -import 'package:libac_flutter/packets/packets.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/packets/packets.dart'; void main() async { PacketRegistry reg = PacketRegistry(); diff --git a/bin/server_test.dart b/bin/server_test.dart index 51d11bb..85e65b7 100644 --- a/bin/server_test.dart +++ b/bin/server_test.dart @@ -1,4 +1,4 @@ -import 'package:libac_flutter/packets/packets.dart'; +import 'package:libac_dart/packets/packets.dart'; void main() async { await PacketServer.start(); diff --git a/lib/nbt/NbtIo.dart b/lib/nbt/NbtIo.dart index cdcb537..f0ab573 100644 --- a/lib/nbt/NbtIo.dart +++ b/lib/nbt/NbtIo.dart @@ -1,9 +1,9 @@ import 'dart:typed_data'; -import 'package:libac_flutter/nbt/Stream.dart'; -import 'package:libac_flutter/nbt/Tag.dart'; -import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; -import 'package:libac_flutter/utils/Converter.dart'; +import '../utils/Converter.dart'; +import 'Stream.dart'; +import 'Tag.dart'; +import 'impl/CompoundTag.dart'; class NbtIo { static ByteLayer _io = ByteLayer(); diff --git a/lib/nbt/NbtUtils.dart b/lib/nbt/NbtUtils.dart index 8c2cd76..d69170f 100644 --- a/lib/nbt/NbtUtils.dart +++ b/lib/nbt/NbtUtils.dart @@ -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/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) { diff --git a/lib/nbt/SnbtIo.dart b/lib/nbt/SnbtIo.dart index 6d39f37..b59f40f 100644 --- a/lib/nbt/SnbtIo.dart +++ b/lib/nbt/SnbtIo.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:libac_flutter/nbt/Stream.dart'; -import 'package:libac_flutter/nbt/Tag.dart'; -import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; +import 'Stream.dart'; +import 'Tag.dart'; +import 'impl/CompoundTag.dart'; class SnbtIo { static void write(String file, CompoundTag tag) { diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index 8caaa5e..ca0b4a6 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -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 { diff --git a/lib/nbt/impl/ByteArrayTag.dart b/lib/nbt/impl/ByteArrayTag.dart index bb62a6d..c80ef47 100644 --- a/lib/nbt/impl/ByteArrayTag.dart +++ b/lib/nbt/impl/ByteArrayTag.dart @@ -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 value = []; diff --git a/lib/nbt/impl/ByteTag.dart b/lib/nbt/impl/ByteTag.dart index 68f3ed7..a52738b 100644 --- a/lib/nbt/impl/ByteTag.dart +++ b/lib/nbt/impl/ByteTag.dart @@ -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; diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index f552583..88bcdac 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -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 CompoundTag extends Tag implements Map { late final Map value = {}; diff --git a/lib/nbt/impl/DoubleTag.dart b/lib/nbt/impl/DoubleTag.dart index 3881473..a2ec956 100644 --- a/lib/nbt/impl/DoubleTag.dart +++ b/lib/nbt/impl/DoubleTag.dart @@ -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; diff --git a/lib/nbt/impl/EndTag.dart b/lib/nbt/impl/EndTag.dart index 50429fd..1e7d0e3 100644 --- a/lib/nbt/impl/EndTag.dart +++ b/lib/nbt/impl/EndTag.dart @@ -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(); diff --git a/lib/nbt/impl/FloatTag.dart b/lib/nbt/impl/FloatTag.dart index 7b6ffc4..f037d01 100644 --- a/lib/nbt/impl/FloatTag.dart +++ b/lib/nbt/impl/FloatTag.dart @@ -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; diff --git a/lib/nbt/impl/IntArrayTag.dart b/lib/nbt/impl/IntArrayTag.dart index d015351..009a02e 100644 --- a/lib/nbt/impl/IntArrayTag.dart +++ b/lib/nbt/impl/IntArrayTag.dart @@ -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 value = []; diff --git a/lib/nbt/impl/IntTag.dart b/lib/nbt/impl/IntTag.dart index 067c517..429d9a6 100644 --- a/lib/nbt/impl/IntTag.dart +++ b/lib/nbt/impl/IntTag.dart @@ -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; diff --git a/lib/nbt/impl/ListTag.dart b/lib/nbt/impl/ListTag.dart index d908226..ee8d5f4 100644 --- a/lib/nbt/impl/ListTag.dart +++ b/lib/nbt/impl/ListTag.dart @@ -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 { diff --git a/lib/nbt/impl/LongArrayTag.dart b/lib/nbt/impl/LongArrayTag.dart index 8984cf4..d517438 100644 --- a/lib/nbt/impl/LongArrayTag.dart +++ b/lib/nbt/impl/LongArrayTag.dart @@ -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 value = []; diff --git a/lib/nbt/impl/LongTag.dart b/lib/nbt/impl/LongTag.dart index 6f1d90e..89ac809 100644 --- a/lib/nbt/impl/LongTag.dart +++ b/lib/nbt/impl/LongTag.dart @@ -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; diff --git a/lib/nbt/impl/ShortTag.dart b/lib/nbt/impl/ShortTag.dart index 9642295..13ed6aa 100644 --- a/lib/nbt/impl/ShortTag.dart +++ b/lib/nbt/impl/ShortTag.dart @@ -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; diff --git a/lib/nbt/impl/StringTag.dart b/lib/nbt/impl/StringTag.dart index e6cf258..4829e31 100644 --- a/lib/nbt/impl/StringTag.dart +++ b/lib/nbt/impl/StringTag.dart @@ -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 = ""; diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index b7c89d3..a60d1a5 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -2,12 +2,11 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:libac_flutter/nbt/NbtIo.dart'; -import 'package:libac_flutter/nbt/Tag.dart'; -import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; -import 'package:libac_flutter/nbt/impl/StringTag.dart'; - +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; diff --git a/lib/utils/TimeUtils.dart b/lib/utils/TimeUtils.dart index 6778cba..5b715b2 100644 --- a/lib/utils/TimeUtils.dart +++ b/lib/utils/TimeUtils.dart @@ -1,4 +1,4 @@ -import 'package:libac_flutter/nbt/Stream.dart'; +import '../nbt/Stream.dart'; class Time { int hours; diff --git a/lib/utils/uuid/NbtUUID.dart b/lib/utils/uuid/NbtUUID.dart index af766e8..306b757 100644 --- a/lib/utils/uuid/NbtUUID.dart +++ b/lib/utils/uuid/NbtUUID.dart @@ -1,5 +1,4 @@ -import 'package:libac_flutter/nbt/Stream.dart'; - +import '../../nbt/Stream.dart'; import 'UUID.dart'; class NbtUUID { diff --git a/lib/utils/uuid/UUID.dart b/lib/utils/uuid/UUID.dart index 5f4fdc7..9858cb0 100644 --- a/lib/utils/uuid/UUID.dart +++ b/lib/utils/uuid/UUID.dart @@ -2,7 +2,8 @@ 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 List _bytes; diff --git a/pubspec.yaml b/pubspec.yaml index 856b5c7..fdd6f7a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,28 +1,23 @@ -name: libac_flutter +name: libac_dart description: "Aria's Creations code library" version: 1.0.20 homepage: "https://zontreck.com" -environment: - sdk: '>=3.3.4 <4.0.0' - flutter: ">=1.17.0" +environment: + 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: diff --git a/test/hash_test.dart b/test/hash_test.dart index 7243f1e..9539fa7 100644 --- a/test/hash_test.dart +++ b/test/hash_test.dart @@ -1,5 +1,6 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:libac_flutter/utils/Hashing.dart'; +import 'package:libac_dart/utils/Hashing.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; void main() { test("Test md5", () { diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 2930c57..69273f7 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -1,15 +1,16 @@ 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/SnbtIo.dart'; -import 'package:libac_flutter/nbt/Stream.dart'; -import 'package:libac_flutter/nbt/Tag.dart'; -import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; -import 'package:libac_flutter/nbt/impl/StringTag.dart'; -import 'package:libac_flutter/utils/uuid/NbtUUID.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/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 { diff --git a/test/time_test.dart b/test/time_test.dart index 91eba61..ff52ff4 100644 --- a/test/time_test.dart +++ b/test/time_test.dart @@ -1,5 +1,6 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:libac_flutter/utils/TimeUtils.dart'; +import 'package:libac_dart/utils/TimeUtils.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; void main() { test("Parse time notation", () { diff --git a/test/uuid_test.dart b/test/uuid_test.dart index 16467fb..086563c 100644 --- a/test/uuid_test.dart +++ b/test/uuid_test.dart @@ -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", () { From 5af94f39880268ecacc500bede1af3ef5ed64dd5 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 14:40:28 -0700 Subject: [PATCH 34/50] Add rcon from https://github.com/aidanlok/mc_rcon/tree/master but without flutter requirement --- lib/utils/rcon/rcon_api.dart | 98 ++++++++++ lib/utils/rcon/rcon_helpers.dart | 301 +++++++++++++++++++++++++++++++ lib/utils/rcon/rcon_vars.dart | 12 ++ 3 files changed, 411 insertions(+) create mode 100644 lib/utils/rcon/rcon_api.dart create mode 100644 lib/utils/rcon/rcon_helpers.dart create mode 100644 lib/utils/rcon/rcon_vars.dart diff --git a/lib/utils/rcon/rcon_api.dart b/lib/utils/rcon/rcon_api.dart new file mode 100644 index 0000000..97b5fb3 --- /dev/null +++ b/lib/utils/rcon/rcon_api.dart @@ -0,0 +1,98 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:libac_dart/utils/rcon/rcon_helpers.dart'; +import 'package:libac_dart/utils/rcon/rcon_vars.dart'; + +/// Creates and stores a socket connected to the RCON server +/// with the given host (IP/FQDN) and port. Port defaults to +/// 25575 if no port is specified. +Future createSocket(String host, {int port = 25575}) async { + // Creates the socket by connecting the socket to the specified + // host and port. + rconSck = await Socket.connect(host, port); +} + +/// Closes the socket to the RCON server. Returns a bool that +/// specified whether the socket was successfully destroyed. +bool close() { + // Checks to ensure that the RCON socket exists. + if (rconSck == null) { + return false; + } + + // Destroys the socket, which also closes the connection. + rconSck!.destroy(); + + return true; +} + +/// Send a message with the given message ID and message payload. +/// Returns a boolean that specifies if the command was successful. +bool sendMsg(int msgID, String payload) { + // Ensures that the RCON socket exists. + if (rconSck == null) { + return false; + } + + // Message length is the payload length + 10 to account + // for the headers and suffix. + int msgLen = 10 + payload.length; + + // Creates the full RCON message. + Uint8List fullMsg = cM(msgLen, msgID, payload); + + // Add the RCON message to the socket stream. + rconSck!.add(fullMsg); + print("mc_rcon: sent payload ($fullMsg) on socket"); + + return true; +} + +/// Log in to the RCON server using the given socket and password. +/// Returns a boolean that specifies if the command was successful. +bool login(String password) { + // Sends an RCON message with request ID = 3 (authenticate) + // with the password as the payload. + return sendMsg(3, password); +} + +/// Send the provided command to the RCON server using the +/// Returns a boolean that specifies if the command was successful. +bool sendCommand(String command) { + // Sends an RCON message with request ID = 2 (command) + // with the String command as the payload. + return sendMsg(2, command); +} + +/// Starts listening on the socket for packets sent by the RCON +/// server. Returns a boolean that specifies if the socket has +/// started listening. Note: onData must accept a List and +/// a String as the only parameters. +bool listen(Function onData) { + // Checks to ensure that the RCON socket exists. + if (rconSck == null) { + return false; + } + + // Starts listening on the RCON socket. + // Calls the first handler if we receive data, calls onError + // if there is an error on the stream, calls onDone when the + // client or the server ends the connection. + rconSck!.listen( + (Uint8List data) { + pSR(data, onData); + }, + onError: (error) { + print('mc_rcon: Error with the connection to the server: $error'); + rconSck!.destroy(); + }, + onDone: () { + print('mc_rcon: The server has ended the connection.'); + rconSck!.destroy(); + }, + cancelOnError: false, + ); + + return true; +} diff --git a/lib/utils/rcon/rcon_helpers.dart b/lib/utils/rcon/rcon_helpers.dart new file mode 100644 index 0000000..3f31463 --- /dev/null +++ b/lib/utils/rcon/rcon_helpers.dart @@ -0,0 +1,301 @@ +import 'dart:typed_data'; + +import 'rcon_vars.dart'; + +/// Returns a Uint8Lit that contains only 4 integers starting +/// from original[start]. Start defaults to 0 if not set. If +/// 4 integers cannot be copied from the list (e.g. list length +/// is less than 4 or start is too large to copy 4 integers), a +/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place +/// of any integer if that location is empty in the source list. +Uint8List _u8LCopy4(Uint8List original, {int start = 0}) { + // Ensure that the Uint8List is at least len 4 and that + // start is list.length-4 or less + if (original.length < 4 || start + 3 > original.length - 1) { + // If not, return a list that is all -1. + Uint8List errorList = Uint8List(4) + ..[0] = -1 + ..[1] = -1 + ..[2] = -1 + ..[3] = -1; + + return errorList; + } + + // Creates a new length 4 Uint8List and sets each of the + // values to a value from the original list. + Uint8List copiedUint8s = Uint8List(4) + ..[0] = original[start] + ..[1] = original[start + 1] + ..[2] = original[start + 2] + ..[3] = original[start + 3]; + + return copiedUint8s; +} + +/// Returns a Uint8Lit that contains only 4 integers starting +/// from original[start]. Start defaults to 0 if not set. If +/// 4 integers cannot be copied from the list (e.g. list length +/// is less than 4 or start is too large to copy 4 integers), a +/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place +/// of any integer if that location is empty in the source list. +Uint8List u8LCopy4(Uint8List original, {int start = 0}) { + return _u8LCopy4(original, start: start); +} + +/// Returns a little Endian int32 interpreted from 4 Uint8s +/// retrieved from data (Uint8List) starting at an index of +/// start (int). The Uint8list length must be at least 4 and +/// start must be data.length - 4 or smaller. Returns -1 +/// if processing has failed. +int _processToInt32(Uint8List data, int start) { + // Checks to ensure that the Uint8List is longer than 4 + // and that the starting index is valid to parse out a + // 32-bit integer. + if (data.length < 4 || start > (data.length - 4)) { + return -1; + } + + // Copies the 4 uint8s we need to parse the int32. + Uint8List copiedUint8s = _u8LCopy4(data, start: start); + + // I don't know what this does. But it gives me access + // to the list and the ability to parse out a 32-bit int. + var blob = ByteData.sublistView(copiedUint8s); + + // Process out the 32-bit integer assuming little Endian + // (which is what the RCON protocol uses). + int processedInt32 = blob.getInt32(0, Endian.little); + + return processedInt32; +} + +/// Returns a little Endian int32 interpreted from 4 Uint8s +/// retrieved from data (Uint8List) starting at an index of +/// start (int). The Uint8list length must be at least 4 and +/// start must be data.length - 4 or smaller. Returns -1 +/// if processing has failed. +int processToInt32(Uint8List data, int start) { + return _processToInt32(data, start); +} + +/// Returns a List that contains the 3 header integers +/// processed from the Uint8List of data. +List _processHeaders(Uint8List data) { + // Processes out each of the 3 header integers. + int msgLength = _processToInt32(data, 0); + int respReqID = _processToInt32(data, 4); + int commandID = _processToInt32(data, 8); + + // Creates a list made of the header integers. + List msgHeader = [msgLength, respReqID, commandID]; + + return msgHeader; +} + +/// Returns a List that contains the 3 header integers +/// processed from the Uint8List of data. +// @visibleForTesting +// List processHeaders(Uint8List data) { +// return _processHeaders(data); +// } + +/// Returns a boolean that represents whether the response ID +/// represents a good packet. Good packet means response +/// ID == original request ID. Bad auth packet means response +/// ID == -1. +bool _processResponseID(int respID) { + if (respID == requestID) { + // If the response ID == original request ID, + // we received a good packet. + print("mc_rcon: Good packet received."); + return true; + } else if (respID == -1) { + // If the response ID == -1, we haven't authenticated + // properly, which can mean we sent the wrong password + // or we haven't authenticated yet. + print( + "mc_rcon: Bad authentication. Incorrect password or you haven't logged in yet."); + return false; + } else { + // Catch-all for all other response IDs. Should never trigger. + print("mc_rcon: Received unknown request ID."); + return false; + } +} + +/// Returns a boolean that represents whether the response ID +/// represents a good packet. Good packet means response +/// ID == original request ID. Bad auth packet means response +/// ID == -1. +// @visibleForTesting +// bool processResponseID(int respID) { +// return _processResponseID(respID); +// } + +/// Processes the server response (represented as a Uint8List) +/// and calls onData handler if we have received a good packet. +void _processServerResponse(Uint8List data, Function onData) { + // Parses out the message headers and payload. + List rconHeaders = _processHeaders(data); + String payload = String.fromCharCodes(data, 12); + + // Pulls out the messsage length to ensure the integrity + // of the message and the response ID to print. + int messageLen = rconHeaders[0]; + int responseID = rconHeaders[1]; + print("mc_rcon: Server response id: $responseID"); + + // Ensures that the data we recieved is the same + // as what the length of the message is supposed to be. + bool badMessage = (data.length == messageLen); + + // Sends the headers and payload to the user handler function + // if the response is good (we receive our own request ID). + if (!badMessage && _processResponseID(responseID)) { + onData(rconHeaders, payload); + } +} + +/// Processes the server response (represented as a Uint8List) +/// and calls onData handler if we have received a good packet. +void processServerResponse(Uint8List data, Function onData) { + return _processServerResponse(data, onData); +} + +/// Processes the server response (represented as a Uint8List) +/// and calls onData handler if we have received a good packet. +void pSR(Uint8List data, Function onData) { + return _processServerResponse(data, onData); +} + +/// Sets every int in dataList to the Uint32List using a ByteData buffer. +void _setUint32s(Uint32List int32List, List dataList) { + // Views the buffer of the Uint32list. + ByteData bd = ByteData.view(int32List.buffer); + + // Used to offset the bytes assigned to the ByteData. + // Otherwise we will overwrite the already written bytes. + int bdByteOffset = 0; + + // Loops through every int data and assigns it as a little + // Endian Uint32 to the Uint32list's ByteData buffer. + for (int data in dataList) { + bd.setUint32(bdByteOffset, data, Endian.little); + bdByteOffset += 4; + } +} + +/// Sets every int in dataList to the Uint32List using a ByteData buffer. +// @visibleForTesting +// void setUint32s(Uint32List int32List, List dataList) { +// return _setUint32s(int32List, dataList); +// } + +/// Returns a Uint8List that represents the header of +/// the RCON message. Assembles the header with the +/// specified length and message ID (i.e. type of message). +Uint8List _assembleHeader(int payloadLen, int msgID, [int? overrideReqID]) { + // Creates a new, length 3, Uint32List. + Uint32List headerAs32 = Uint32List(3); + + // Sets the three Uint32s to the proper header integers. + _setUint32s(headerAs32, [ + payloadLen, + overrideReqID != null ? overrideReqID : requestID, + msgID, + ]); + + // Transforms the header Uint32List into a Uint8List + // for transmission. + Uint8List header = headerAs32.buffer.asUint8List(); + + return header; +} + +/// Returns a Uint8List that represents the header of +/// the RCON message. Assembles the header with the +/// specified length and message ID (i.e. type of message). +// @visibleForTesting +// Uint8List assembleHeader(int payloadLen, int msgID) { +// return _assembleHeader(payloadLen, msgID); +// } + +/// Returns a Uint8List that represents the suffix of +/// the RCON message. The suffix is two NULLs in ASCII, +/// one to end the payload, and one to end the message. +Uint8List _assembleSuffix() { + // Creates a new, length 2, Uint8List. + Uint8List suffix = Uint8List(2); + + // Views the buffer of the list so we can add to the list. + ByteData suffixBD = ByteData.view(suffix.buffer); + + // Adds the two NULLs to the Uint8List. + suffixBD.setUint8(0, 0); + suffixBD.setUint8(1, 0); + + return suffix; +} + +/// Returns a Uint8List that represents the suffix of +/// the RCON message. The suffix is two NULLs in ASCII, +/// one to end the payload, and one to end the message. +// @visibleForTesting +// Uint8List assembleSuffix() { +// return _assembleSuffix(); +// } + +/// Assembles a list of Uint8Lists into one Uint8List. +Uint8List _assembleUint8Lists(List msgParts) { + // Creates a new BytesBuilder to assemble the ints. + BytesBuilder bBuilder = BytesBuilder(); + + // Adds every list of Uint8s to the BB. + // This automatically compiles the list of ints. + for (Uint8List part in msgParts) { + bBuilder.add(part); + } + + // Returns the BB's data as a Uint8list. + return bBuilder.toBytes(); +} + +/// Assembles a list of Uint8Lists into one Uint8List. +// @visibleForTesting +// Uint8List assembleUint8Lists(List msgParts) { +// return _assembleUint8Lists(msgParts); +// } + +/// Returns the whole RCON message as a Uint8List. Requires the +/// message length, message ID, and the payload. +Uint8List _createMessage(int msgLen, int msgID, String payload, + [int? overrideReqID]) { + // Example login data: (u__ refers to the type of unsigned integer) + // 0d00 0000 | dec0 ad0b | 0300 0000 | 3132 3300 00 + // len u32 | pID u32 | cmdID u32 | payload/password u8 + + // Creates the 3 parts of the message using their respective + // constructors (2 are self-written, one built into Uint8List). + Uint8List header = _assembleHeader(msgLen, msgID, overrideReqID); + Uint8List msgAsIntList = Uint8List.fromList(payload.codeUnits); + Uint8List suffix = _assembleSuffix(); + + // Assembles the 3 parts into 1 Uint8List to return; + Uint8List fullMsg = _assembleUint8Lists([header, msgAsIntList, suffix]); + + return fullMsg; +} + +/// Returns the whole RCON message as a Uint8List. Requires the +/// message length, message ID, and the payload. +Uint8List createMessage(int msgLen, int msgID, String payload, + [int? overrideReqID]) { + return _createMessage(msgLen, msgID, payload, overrideReqID); +} + +/// Returns the whole RCON message as a Uint8List. Requires the +/// message length, message ID, and the payload. +Uint8List cM(int msgLen, int msgID, String payload) { + return _createMessage(msgLen, msgID, payload); +} diff --git a/lib/utils/rcon/rcon_vars.dart b/lib/utils/rcon/rcon_vars.dart new file mode 100644 index 0000000..bcf86df --- /dev/null +++ b/lib/utils/rcon/rcon_vars.dart @@ -0,0 +1,12 @@ +import 'dart:io'; +import 'dart:math'; + +/// The socket that is used to communicate with the RCON server. +/// Generated when createSocket() is run. +Socket? rconSck; + +/// The randomly generated request ID that is sent with every +/// message to the RCON server. Used to ensure that the commands +/// sent to and received from the server are ours, and not another +/// user's. +int requestID = Random().nextInt(2147483647); From 86ae7ad4aa338a936165ef37f487f8ae5113a94e Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 14:40:47 -0700 Subject: [PATCH 35/50] Bump package version number --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index fdd6f7a..79e1837 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.20 +version: 1.0.21 homepage: "https://zontreck.com" From 0a5d99cd6f6ab3e5417585069af8f61466c9cd1c Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 15:43:47 -0700 Subject: [PATCH 36/50] File is deleted on write to ensure it does not continuously grow --- lib/nbt/SnbtIo.dart | 4 ++++ lib/nbt/Stream.dart | 2 ++ pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/nbt/SnbtIo.dart b/lib/nbt/SnbtIo.dart index b59f40f..9a74bea 100644 --- a/lib/nbt/SnbtIo.dart +++ b/lib/nbt/SnbtIo.dart @@ -7,6 +7,10 @@ import 'impl/CompoundTag.dart'; class SnbtIo { static void write(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()); diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index 2a64cea..82bf471 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -210,6 +210,8 @@ class ByteLayer { Future 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); } diff --git a/pubspec.yaml b/pubspec.yaml index 79e1837..91e8c0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.21 +version: 1.0.22 homepage: "https://zontreck.com" From 4de135284f41ade462f696fb425126ccbd788cc3 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 15:47:06 -0700 Subject: [PATCH 37/50] Do not completely crash, store error and stacktrace when failing to decode reply. --- lib/packets/packets.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index a60d1a5..6c52226 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -88,6 +88,9 @@ class PacketClient { } } + /// 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 send(IPacket packet) async { if (!connected) { return S2CResponse(); @@ -120,7 +123,13 @@ class PacketClient { await close(); await startConnect(lastIP); S2CResponse reply = S2CResponse(); - reply.decodeTag(ct.get("result")!.asCompoundTag()); + 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; } From 043010aad3a70dc5ecee8d94b561364c6919a694 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 24 May 2024 15:48:50 -0700 Subject: [PATCH 38/50] Adds a instruction to prevent automatically reconnecting the client to the server --- bin/client_test.dart | 4 ++-- lib/packets/packets.dart | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/client_test.dart b/bin/client_test.dart index 5d5aa92..2c24380 100644 --- a/bin/client_test.dart +++ b/bin/client_test.dart @@ -10,14 +10,14 @@ void main() async { PacketClient client = PacketClient(); await client.startConnect("127.0.0.1"); - S2CResponse response = await client.send(C2SPing()); + 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()); + await client.send(StopServerPacket(), false); if (client.connected) await client.close(); return; diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 6c52226..d6243f7 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -91,7 +91,7 @@ class PacketClient { /// 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 send(IPacket packet) async { + Future send(IPacket packet, bool shouldReconnect) async { if (!connected) { return S2CResponse(); } @@ -121,6 +121,7 @@ class PacketClient { await onCompletion.future; await close(); + await startConnect(lastIP); S2CResponse reply = S2CResponse(); try { From 8d93157ac0ea12e0e1edea874bf899aea810bad1 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 3 Jun 2024 15:19:28 -0700 Subject: [PATCH 39/50] Make it possible to change the port number of the PacketServer --- bin/client_test.dart | 2 +- bin/server_test.dart | 2 +- lib/packets/packets.dart | 12 +++++++----- pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bin/client_test.dart b/bin/client_test.dart index 2c24380..c215f8b 100644 --- a/bin/client_test.dart +++ b/bin/client_test.dart @@ -8,7 +8,7 @@ void main() async { reg.registerDefaults(); PacketClient client = PacketClient(); - await client.startConnect("127.0.0.1"); + await client.startConnect("127.0.0.1", 25306); S2CResponse response = await client.send(C2SPing(), true); CompoundTag tag = response.contents; diff --git a/bin/server_test.dart b/bin/server_test.dart index 85e65b7..e8a4eac 100644 --- a/bin/server_test.dart +++ b/bin/server_test.dart @@ -1,5 +1,5 @@ import 'package:libac_dart/packets/packets.dart'; void main() async { - await PacketServer.start(); + await PacketServer.start(25306); } diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index d6243f7..1f6b0eb 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -11,8 +11,8 @@ import '../nbt/impl/StringTag.dart'; class PacketServer { static ServerSocket? socket; static bool shouldRestart = true; - static Future start() async { - socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306); + static Future start(int port) async { + socket = await ServerSocket.bind(InternetAddress.anyIPv4, port); print("Server now listening on port 25306"); await for (var sock in socket!) { @@ -74,14 +74,16 @@ class PacketClient { Socket? socket; bool connected = false; String lastIP = ""; + int port = 25306; PacketClient(); - Future startConnect(String IPAddress) async { + Future startConnect(String IPAddress, int port) async { try { - socket = await Socket.connect(IPAddress, 25306); + socket = await Socket.connect(IPAddress, port); connected = true; lastIP = IPAddress; + this.port = port; } catch (E, stack) { connected = false; socket = null; @@ -122,7 +124,7 @@ class PacketClient { await onCompletion.future; await close(); - await startConnect(lastIP); + await startConnect(lastIP, port); S2CResponse reply = S2CResponse(); try { reply.decodeTag(ct.get("result")!.asCompoundTag()); diff --git a/pubspec.yaml b/pubspec.yaml index 91e8c0f..98123b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.22 +version: 1.0.23 homepage: "https://zontreck.com" From 868303b9daeb1195ed91d01f72b49c09caafe430 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 3 Jun 2024 15:19:46 -0700 Subject: [PATCH 40/50] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 98123b4..8bc30eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.23 +version: 1.0.24 homepage: "https://zontreck.com" From 0b3458a0b66c095b128e8a6680b99d3fb5ca3d50 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 3 Jun 2024 15:43:13 -0700 Subject: [PATCH 41/50] print port number --- lib/packets/packets.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 1f6b0eb..14e3ce0 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -13,7 +13,7 @@ class PacketServer { static bool shouldRestart = true; static Future start(int port) async { socket = await ServerSocket.bind(InternetAddress.anyIPv4, port); - print("Server now listening on port 25306"); + print("Server now listening on port ${port}"); await for (var sock in socket!) { print( diff --git a/pubspec.yaml b/pubspec.yaml index 8bc30eb..baf63f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.24 +version: 1.0.25 homepage: "https://zontreck.com" From 74fe37d7d83874ad47ca9ef5ba0bdbdb969f1572 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 3 Jun 2024 16:42:59 -0700 Subject: [PATCH 42/50] Adds tail functions --- lib/utils/IOTools.dart | 37 +++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/utils/IOTools.dart b/lib/utils/IOTools.dart index 3152655..4fdd578 100644 --- a/lib/utils/IOTools.dart +++ b/lib/utils/IOTools.dart @@ -1,4 +1,6 @@ +import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; class PathHelper { String pth = ""; @@ -73,3 +75,38 @@ class PathHelper { return pth; } } + +Stream> 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 _read() async* { + while (pos < len) { + final bytesRead = await randomAccess.readInto(buf); + pos += bytesRead; + + yield buf.sublist(0, bytesRead); + } + } + + // 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) { + len = await (randomAccess.length()); + yield* _read(); + } + } +} + +void tailAndPrint(File file) { + 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); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index baf63f6..8f7654b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.25 +version: 1.0.26 homepage: "https://zontreck.com" From 15aae5c3d7a295feca951b6a413dbfb4aed40966 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 4 Jun 2024 13:30:12 -0700 Subject: [PATCH 43/50] Roll my own rcon implementation due to problems with other implementation --- lib/utils/rcon/Rcon.dart | 95 ++++++++++ lib/utils/rcon/rcon_api.dart | 98 ---------- lib/utils/rcon/rcon_helpers.dart | 301 ------------------------------- lib/utils/rcon/rcon_vars.dart | 12 -- pubspec.yaml | 2 +- 5 files changed, 96 insertions(+), 412 deletions(-) create mode 100644 lib/utils/rcon/Rcon.dart delete mode 100644 lib/utils/rcon/rcon_api.dart delete mode 100644 lib/utils/rcon/rcon_helpers.dart delete mode 100644 lib/utils/rcon/rcon_vars.dart diff --git a/lib/utils/rcon/Rcon.dart b/lib/utils/rcon/Rcon.dart new file mode 100644 index 0000000..f67c508 --- /dev/null +++ b/lib/utils/rcon/Rcon.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; +import 'dart:io'; + +class RCONPacket { + int id; + int type; + String body; + + RCONPacket(this.id, this.type, this.body); + + List toBytes() { + List bodyBytes = utf8.encode(body); + int size = 10 + bodyBytes.length; + List packet = []; + packet.addAll(_intToBytes(size)); + packet.addAll(_intToBytes(id)); + packet.addAll(_intToBytes(type)); + packet.addAll(bodyBytes); + packet.addAll([0, 0]); + return packet; + } + + static RCONPacket fromBytes(List bytes) { + int size = _bytesToInt(bytes.sublist(0, 4)); + int id = _bytesToInt(bytes.sublist(4, 8)); + int type = _bytesToInt(bytes.sublist(8, 12)); + String body = + utf8.decode(bytes.sublist(12, size + 2).sublist(0, size - 10)); + return RCONPacket(id, type, body); + } + + static List _intToBytes(int value) { + return [ + value & 0xFF, + (value >> 8) & 0xFF, + (value >> 16) & 0xFF, + (value >> 24) & 0xFF + ]; + } + + static int _bytesToInt(List bytes) { + return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); + } +} + +class RCONClient { + String host; + int port; + String password; + late Socket _socket; + late int _requestId; + + RCONClient(this.host, this.port, this.password) { + _requestId = 0; + } + + Future connect() async { + _socket = await Socket.connect(host, port); + if (await _authenticate()) { + print('Authenticated successfully'); + } else { + print('Authentication failed'); + } + } + + Future _authenticate() async { + RCONPacket packet = RCONPacket(_requestId++, 3, password); + _socket.add(packet.toBytes()); + await _socket.flush(); + RCONPacket response = await _readPacket(); + return response.id == packet.id && response.type == 2; + } + + Future _readPacket() async { + List sizeBytes = await _socket.first; + int size = RCONPacket._bytesToInt(sizeBytes); + List dataBytes = []; + while (dataBytes.length < size) { + dataBytes.addAll(await _socket.first); + } + return RCONPacket.fromBytes(sizeBytes + dataBytes); + } + + Future sendCommand(String command) async { + RCONPacket packet = RCONPacket(_requestId++, 2, command); + _socket.add(packet.toBytes()); + await _socket.flush(); + RCONPacket response = await _readPacket(); + return response.body; + } + + void close() { + _socket.close(); + } +} diff --git a/lib/utils/rcon/rcon_api.dart b/lib/utils/rcon/rcon_api.dart deleted file mode 100644 index 97b5fb3..0000000 --- a/lib/utils/rcon/rcon_api.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:libac_dart/utils/rcon/rcon_helpers.dart'; -import 'package:libac_dart/utils/rcon/rcon_vars.dart'; - -/// Creates and stores a socket connected to the RCON server -/// with the given host (IP/FQDN) and port. Port defaults to -/// 25575 if no port is specified. -Future createSocket(String host, {int port = 25575}) async { - // Creates the socket by connecting the socket to the specified - // host and port. - rconSck = await Socket.connect(host, port); -} - -/// Closes the socket to the RCON server. Returns a bool that -/// specified whether the socket was successfully destroyed. -bool close() { - // Checks to ensure that the RCON socket exists. - if (rconSck == null) { - return false; - } - - // Destroys the socket, which also closes the connection. - rconSck!.destroy(); - - return true; -} - -/// Send a message with the given message ID and message payload. -/// Returns a boolean that specifies if the command was successful. -bool sendMsg(int msgID, String payload) { - // Ensures that the RCON socket exists. - if (rconSck == null) { - return false; - } - - // Message length is the payload length + 10 to account - // for the headers and suffix. - int msgLen = 10 + payload.length; - - // Creates the full RCON message. - Uint8List fullMsg = cM(msgLen, msgID, payload); - - // Add the RCON message to the socket stream. - rconSck!.add(fullMsg); - print("mc_rcon: sent payload ($fullMsg) on socket"); - - return true; -} - -/// Log in to the RCON server using the given socket and password. -/// Returns a boolean that specifies if the command was successful. -bool login(String password) { - // Sends an RCON message with request ID = 3 (authenticate) - // with the password as the payload. - return sendMsg(3, password); -} - -/// Send the provided command to the RCON server using the -/// Returns a boolean that specifies if the command was successful. -bool sendCommand(String command) { - // Sends an RCON message with request ID = 2 (command) - // with the String command as the payload. - return sendMsg(2, command); -} - -/// Starts listening on the socket for packets sent by the RCON -/// server. Returns a boolean that specifies if the socket has -/// started listening. Note: onData must accept a List and -/// a String as the only parameters. -bool listen(Function onData) { - // Checks to ensure that the RCON socket exists. - if (rconSck == null) { - return false; - } - - // Starts listening on the RCON socket. - // Calls the first handler if we receive data, calls onError - // if there is an error on the stream, calls onDone when the - // client or the server ends the connection. - rconSck!.listen( - (Uint8List data) { - pSR(data, onData); - }, - onError: (error) { - print('mc_rcon: Error with the connection to the server: $error'); - rconSck!.destroy(); - }, - onDone: () { - print('mc_rcon: The server has ended the connection.'); - rconSck!.destroy(); - }, - cancelOnError: false, - ); - - return true; -} diff --git a/lib/utils/rcon/rcon_helpers.dart b/lib/utils/rcon/rcon_helpers.dart deleted file mode 100644 index 3f31463..0000000 --- a/lib/utils/rcon/rcon_helpers.dart +++ /dev/null @@ -1,301 +0,0 @@ -import 'dart:typed_data'; - -import 'rcon_vars.dart'; - -/// Returns a Uint8Lit that contains only 4 integers starting -/// from original[start]. Start defaults to 0 if not set. If -/// 4 integers cannot be copied from the list (e.g. list length -/// is less than 4 or start is too large to copy 4 integers), a -/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place -/// of any integer if that location is empty in the source list. -Uint8List _u8LCopy4(Uint8List original, {int start = 0}) { - // Ensure that the Uint8List is at least len 4 and that - // start is list.length-4 or less - if (original.length < 4 || start + 3 > original.length - 1) { - // If not, return a list that is all -1. - Uint8List errorList = Uint8List(4) - ..[0] = -1 - ..[1] = -1 - ..[2] = -1 - ..[3] = -1; - - return errorList; - } - - // Creates a new length 4 Uint8List and sets each of the - // values to a value from the original list. - Uint8List copiedUint8s = Uint8List(4) - ..[0] = original[start] - ..[1] = original[start + 1] - ..[2] = original[start + 2] - ..[3] = original[start + 3]; - - return copiedUint8s; -} - -/// Returns a Uint8Lit that contains only 4 integers starting -/// from original[start]. Start defaults to 0 if not set. If -/// 4 integers cannot be copied from the list (e.g. list length -/// is less than 4 or start is too large to copy 4 integers), a -/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place -/// of any integer if that location is empty in the source list. -Uint8List u8LCopy4(Uint8List original, {int start = 0}) { - return _u8LCopy4(original, start: start); -} - -/// Returns a little Endian int32 interpreted from 4 Uint8s -/// retrieved from data (Uint8List) starting at an index of -/// start (int). The Uint8list length must be at least 4 and -/// start must be data.length - 4 or smaller. Returns -1 -/// if processing has failed. -int _processToInt32(Uint8List data, int start) { - // Checks to ensure that the Uint8List is longer than 4 - // and that the starting index is valid to parse out a - // 32-bit integer. - if (data.length < 4 || start > (data.length - 4)) { - return -1; - } - - // Copies the 4 uint8s we need to parse the int32. - Uint8List copiedUint8s = _u8LCopy4(data, start: start); - - // I don't know what this does. But it gives me access - // to the list and the ability to parse out a 32-bit int. - var blob = ByteData.sublistView(copiedUint8s); - - // Process out the 32-bit integer assuming little Endian - // (which is what the RCON protocol uses). - int processedInt32 = blob.getInt32(0, Endian.little); - - return processedInt32; -} - -/// Returns a little Endian int32 interpreted from 4 Uint8s -/// retrieved from data (Uint8List) starting at an index of -/// start (int). The Uint8list length must be at least 4 and -/// start must be data.length - 4 or smaller. Returns -1 -/// if processing has failed. -int processToInt32(Uint8List data, int start) { - return _processToInt32(data, start); -} - -/// Returns a List that contains the 3 header integers -/// processed from the Uint8List of data. -List _processHeaders(Uint8List data) { - // Processes out each of the 3 header integers. - int msgLength = _processToInt32(data, 0); - int respReqID = _processToInt32(data, 4); - int commandID = _processToInt32(data, 8); - - // Creates a list made of the header integers. - List msgHeader = [msgLength, respReqID, commandID]; - - return msgHeader; -} - -/// Returns a List that contains the 3 header integers -/// processed from the Uint8List of data. -// @visibleForTesting -// List processHeaders(Uint8List data) { -// return _processHeaders(data); -// } - -/// Returns a boolean that represents whether the response ID -/// represents a good packet. Good packet means response -/// ID == original request ID. Bad auth packet means response -/// ID == -1. -bool _processResponseID(int respID) { - if (respID == requestID) { - // If the response ID == original request ID, - // we received a good packet. - print("mc_rcon: Good packet received."); - return true; - } else if (respID == -1) { - // If the response ID == -1, we haven't authenticated - // properly, which can mean we sent the wrong password - // or we haven't authenticated yet. - print( - "mc_rcon: Bad authentication. Incorrect password or you haven't logged in yet."); - return false; - } else { - // Catch-all for all other response IDs. Should never trigger. - print("mc_rcon: Received unknown request ID."); - return false; - } -} - -/// Returns a boolean that represents whether the response ID -/// represents a good packet. Good packet means response -/// ID == original request ID. Bad auth packet means response -/// ID == -1. -// @visibleForTesting -// bool processResponseID(int respID) { -// return _processResponseID(respID); -// } - -/// Processes the server response (represented as a Uint8List) -/// and calls onData handler if we have received a good packet. -void _processServerResponse(Uint8List data, Function onData) { - // Parses out the message headers and payload. - List rconHeaders = _processHeaders(data); - String payload = String.fromCharCodes(data, 12); - - // Pulls out the messsage length to ensure the integrity - // of the message and the response ID to print. - int messageLen = rconHeaders[0]; - int responseID = rconHeaders[1]; - print("mc_rcon: Server response id: $responseID"); - - // Ensures that the data we recieved is the same - // as what the length of the message is supposed to be. - bool badMessage = (data.length == messageLen); - - // Sends the headers and payload to the user handler function - // if the response is good (we receive our own request ID). - if (!badMessage && _processResponseID(responseID)) { - onData(rconHeaders, payload); - } -} - -/// Processes the server response (represented as a Uint8List) -/// and calls onData handler if we have received a good packet. -void processServerResponse(Uint8List data, Function onData) { - return _processServerResponse(data, onData); -} - -/// Processes the server response (represented as a Uint8List) -/// and calls onData handler if we have received a good packet. -void pSR(Uint8List data, Function onData) { - return _processServerResponse(data, onData); -} - -/// Sets every int in dataList to the Uint32List using a ByteData buffer. -void _setUint32s(Uint32List int32List, List dataList) { - // Views the buffer of the Uint32list. - ByteData bd = ByteData.view(int32List.buffer); - - // Used to offset the bytes assigned to the ByteData. - // Otherwise we will overwrite the already written bytes. - int bdByteOffset = 0; - - // Loops through every int data and assigns it as a little - // Endian Uint32 to the Uint32list's ByteData buffer. - for (int data in dataList) { - bd.setUint32(bdByteOffset, data, Endian.little); - bdByteOffset += 4; - } -} - -/// Sets every int in dataList to the Uint32List using a ByteData buffer. -// @visibleForTesting -// void setUint32s(Uint32List int32List, List dataList) { -// return _setUint32s(int32List, dataList); -// } - -/// Returns a Uint8List that represents the header of -/// the RCON message. Assembles the header with the -/// specified length and message ID (i.e. type of message). -Uint8List _assembleHeader(int payloadLen, int msgID, [int? overrideReqID]) { - // Creates a new, length 3, Uint32List. - Uint32List headerAs32 = Uint32List(3); - - // Sets the three Uint32s to the proper header integers. - _setUint32s(headerAs32, [ - payloadLen, - overrideReqID != null ? overrideReqID : requestID, - msgID, - ]); - - // Transforms the header Uint32List into a Uint8List - // for transmission. - Uint8List header = headerAs32.buffer.asUint8List(); - - return header; -} - -/// Returns a Uint8List that represents the header of -/// the RCON message. Assembles the header with the -/// specified length and message ID (i.e. type of message). -// @visibleForTesting -// Uint8List assembleHeader(int payloadLen, int msgID) { -// return _assembleHeader(payloadLen, msgID); -// } - -/// Returns a Uint8List that represents the suffix of -/// the RCON message. The suffix is two NULLs in ASCII, -/// one to end the payload, and one to end the message. -Uint8List _assembleSuffix() { - // Creates a new, length 2, Uint8List. - Uint8List suffix = Uint8List(2); - - // Views the buffer of the list so we can add to the list. - ByteData suffixBD = ByteData.view(suffix.buffer); - - // Adds the two NULLs to the Uint8List. - suffixBD.setUint8(0, 0); - suffixBD.setUint8(1, 0); - - return suffix; -} - -/// Returns a Uint8List that represents the suffix of -/// the RCON message. The suffix is two NULLs in ASCII, -/// one to end the payload, and one to end the message. -// @visibleForTesting -// Uint8List assembleSuffix() { -// return _assembleSuffix(); -// } - -/// Assembles a list of Uint8Lists into one Uint8List. -Uint8List _assembleUint8Lists(List msgParts) { - // Creates a new BytesBuilder to assemble the ints. - BytesBuilder bBuilder = BytesBuilder(); - - // Adds every list of Uint8s to the BB. - // This automatically compiles the list of ints. - for (Uint8List part in msgParts) { - bBuilder.add(part); - } - - // Returns the BB's data as a Uint8list. - return bBuilder.toBytes(); -} - -/// Assembles a list of Uint8Lists into one Uint8List. -// @visibleForTesting -// Uint8List assembleUint8Lists(List msgParts) { -// return _assembleUint8Lists(msgParts); -// } - -/// Returns the whole RCON message as a Uint8List. Requires the -/// message length, message ID, and the payload. -Uint8List _createMessage(int msgLen, int msgID, String payload, - [int? overrideReqID]) { - // Example login data: (u__ refers to the type of unsigned integer) - // 0d00 0000 | dec0 ad0b | 0300 0000 | 3132 3300 00 - // len u32 | pID u32 | cmdID u32 | payload/password u8 - - // Creates the 3 parts of the message using their respective - // constructors (2 are self-written, one built into Uint8List). - Uint8List header = _assembleHeader(msgLen, msgID, overrideReqID); - Uint8List msgAsIntList = Uint8List.fromList(payload.codeUnits); - Uint8List suffix = _assembleSuffix(); - - // Assembles the 3 parts into 1 Uint8List to return; - Uint8List fullMsg = _assembleUint8Lists([header, msgAsIntList, suffix]); - - return fullMsg; -} - -/// Returns the whole RCON message as a Uint8List. Requires the -/// message length, message ID, and the payload. -Uint8List createMessage(int msgLen, int msgID, String payload, - [int? overrideReqID]) { - return _createMessage(msgLen, msgID, payload, overrideReqID); -} - -/// Returns the whole RCON message as a Uint8List. Requires the -/// message length, message ID, and the payload. -Uint8List cM(int msgLen, int msgID, String payload) { - return _createMessage(msgLen, msgID, payload); -} diff --git a/lib/utils/rcon/rcon_vars.dart b/lib/utils/rcon/rcon_vars.dart deleted file mode 100644 index bcf86df..0000000 --- a/lib/utils/rcon/rcon_vars.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:io'; -import 'dart:math'; - -/// The socket that is used to communicate with the RCON server. -/// Generated when createSocket() is run. -Socket? rconSck; - -/// The randomly generated request ID that is sent with every -/// message to the RCON server. Used to ensure that the commands -/// sent to and received from the server are ours, and not another -/// user's. -int requestID = Random().nextInt(2147483647); diff --git a/pubspec.yaml b/pubspec.yaml index 8f7654b..cfe41a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.26 +version: 1.0.27 homepage: "https://zontreck.com" From 167c27848a4fcf45a34f43d6d124399fc6dc47aa Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 4 Jun 2024 14:51:07 -0700 Subject: [PATCH 44/50] add decompress to try catch --- lib/nbt/NbtIo.dart | 16 ++++++++++------ pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/nbt/NbtIo.dart b/lib/nbt/NbtIo.dart index f0ab573..4338584 100644 --- a/lib/nbt/NbtIo.dart +++ b/lib/nbt/NbtIo.dart @@ -81,11 +81,15 @@ class NbtIo { static Future readFromStream(Uint8List list) async { _io = ByteLayer(); - _io.writeBytes(list); - _io.resetPosition(); - _io.decompress(); - _io.resetPosition(); - - return Tag.readNamedTag(_io) as CompoundTag; + try { + _io.writeBytes(list); + _io.resetPosition(); + _io.decompress(); + _io.resetPosition(); + } catch (E) { + print(E); + } finally { + return Tag.readNamedTag(_io) as CompoundTag; + } } } diff --git a/pubspec.yaml b/pubspec.yaml index cfe41a8..3288df8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.27 +version: 1.0.28 homepage: "https://zontreck.com" From 2a5774bd6d1eb6887a48a47b62947c605f18722d Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 4 Jun 2024 15:34:07 -0700 Subject: [PATCH 45/50] Rcon was not working --- lib/utils/rcon/Rcon.dart | 179 +++++++++++++++++++------------- lib/utils/rcon/RconDecoder.dart | 48 +++++++++ pubspec.yaml | 2 +- 3 files changed, 153 insertions(+), 76 deletions(-) create mode 100644 lib/utils/rcon/RconDecoder.dart diff --git a/lib/utils/rcon/Rcon.dart b/lib/utils/rcon/Rcon.dart index f67c508..094268d 100644 --- a/lib/utils/rcon/Rcon.dart +++ b/lib/utils/rcon/Rcon.dart @@ -1,95 +1,124 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; -class RCONPacket { - int id; - int type; - String body; +import 'RconDecoder.dart'; - RCONPacket(this.id, this.type, this.body); +abstract class RconClient { + void exit(); - List toBytes() { - List bodyBytes = utf8.encode(body); - int size = 10 + bodyBytes.length; - List packet = []; - packet.addAll(_intToBytes(size)); - packet.addAll(_intToBytes(id)); - packet.addAll(_intToBytes(type)); - packet.addAll(bodyBytes); - packet.addAll([0, 0]); - return packet; - } + void send(String cmd); - static RCONPacket fromBytes(List bytes) { - int size = _bytesToInt(bytes.sublist(0, 4)); - int id = _bytesToInt(bytes.sublist(4, 8)); - int type = _bytesToInt(bytes.sublist(8, 12)); - String body = - utf8.decode(bytes.sublist(12, size + 2).sublist(0, size - 10)); - return RCONPacket(id, type, body); - } + factory RconClient() => RconClientImpl(); - static List _intToBytes(int value) { - return [ - value & 0xFF, - (value >> 8) & 0xFF, - (value >> 16) & 0xFF, - (value >> 24) & 0xFF - ]; - } - - static int _bytesToInt(List bytes) { - return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); - } + Stream connect(String host, int port, String password); } -class RCONClient { - String host; - int port; - String password; +class RconType { + /// RCON Packet Type 0: Server response + static const int SERVERDATA_RESPONSE_VALUE = 0; + + /// RCON Packet Type 2: Server auth response + static const int SERVERDATA_AUTH_RESPONSE = 2; + + /// RCON Packet Type 2: Client execute command + static const int SERVERDATA_EXECCOMMAND = 2; + + /// RCON Packet Type 3: Client send authentication data + static const int SERVERDATA_AUTH = 3; +} + +/// Client for the rcon protocol +class RconClientImpl implements RconClient { + /// Socket used to write and read TCP packets. late Socket _socket; - late int _requestId; - RCONClient(this.host, this.port, this.password) { - _requestId = 0; + /// Disconnect from the a RCON server. + /// + /// Usage: + /// + /// ```dart + /// RconClient client = RconClient.connect(print, 'localhost', '25575', 'password'); + /// client.exit(); + /// ``` + @override + void exit() => _socket.destroy(); + + /// Send commands + @override + void send(String cmd) => _send(RconType.SERVERDATA_EXECCOMMAND, cmd); + + /// Authenticate to the server + void _authenticate(String password) => + _send(RconType.SERVERDATA_AUTH, password); + + /// Build and send a packet to the rcon server + /// + /// | Field | Type | Value | + /// |--------------|-------------------------------------|-------| + /// | Size | 32-bit little-endian Signed Integer | | + /// | ID | 32-bit little-endian Signed Integer | | + /// | Type | 32-bit little-endian Signed Integer | | + /// | Body | Null-terminated ASCII String | | + /// | Empty String | Null-terminated ASCII String | 0x00 | + void _send(int type, String payload) { + final Uint8List outSize = Uint8List(4); + final Uint8List outId = Uint8List(4); // Empty: 00 00 00 00 + final Uint8List outType = Uint8List(4); + + // Write type in the Type field + final ByteData typeData = ByteData.view(outType.buffer); + typeData.setInt32(0, type, Endian.little); + + // Build the body + final List outBody = utf8.encode(payload); + + // Build ID + Type + Body + Empty String + final Uint8List outPacketBody = + Uint8List.fromList(outId + outType + outBody + Uint8List(2)); + + // Calculate the Size field + final ByteData sizeData = ByteData.view(outSize.buffer); + sizeData.setInt32(0, outPacketBody.length, Endian.little); + + // Build the packet + final Uint8List packet = Uint8List.fromList(outSize + outPacketBody); + + // View packet + // print(packet); + // print(packet.map((int i) => i.toRadixString(16)).toList()); + // print(utf8.decode(packet)); + + // Send the packet + _socket.add(packet); } - Future connect() async { + /// Connect to the a RCON server. + /// + /// Usage: + /// + /// ```dart + /// final RconClient client = RconClient(); + /// final Stream response = client.connect( + /// 'localhost', + /// 25575, + /// 'password', + /// ); + /// ``` + @override + Stream connect(String host, int port, String password) async* { + print("Trying to connect..."); + _socket = await Socket.connect(host, port); - if (await _authenticate()) { - print('Authenticated successfully'); - } else { - print('Authentication failed'); - } - } - Future _authenticate() async { - RCONPacket packet = RCONPacket(_requestId++, 3, password); - _socket.add(packet.toBytes()); - await _socket.flush(); - RCONPacket response = await _readPacket(); - return response.id == packet.id && response.type == 2; - } + print("Connected."); - Future _readPacket() async { - List sizeBytes = await _socket.first; - int size = RCONPacket._bytesToInt(sizeBytes); - List dataBytes = []; - while (dataBytes.length < size) { - dataBytes.addAll(await _socket.first); - } - return RCONPacket.fromBytes(sizeBytes + dataBytes); - } + final Stream stream = _socket.transform(RconDecoder()); - Future sendCommand(String command) async { - RCONPacket packet = RCONPacket(_requestId++, 2, command); - _socket.add(packet.toBytes()); - await _socket.flush(); - RCONPacket response = await _readPacket(); - return response.body; - } + print("Trying to authenticate"); - void close() { - _socket.close(); + _authenticate(password); + + yield* stream; } } diff --git a/lib/utils/rcon/RconDecoder.dart b/lib/utils/rcon/RconDecoder.dart new file mode 100644 index 0000000..1e81811 --- /dev/null +++ b/lib/utils/rcon/RconDecoder.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'Rcon.dart'; + +class RconDecoder extends Converter { + /// Transform packets into a String. + /// + /// | Field | Type | Value | + /// |--------------|-------------------------------------|-------| + /// | Size | 32-bit little-endian Signed Integer | | + /// | ID | 32-bit little-endian Signed Integer | | + /// | Type | 32-bit little-endian Signed Integer | | + /// | Body | Null-terminated ASCII String | | + /// | Empty String | Null-terminated ASCII String | 0x00 | + @override + String convert(Uint8List data) { + final byteData = ByteData.sublistView(data); + // final inLength = byteData.getInt32(0, Endian.little); + final inId = byteData.getInt32(4, Endian.little); + final inType = byteData.getInt32(8, Endian.little); + final inBody = data.sublist(12); + + if (inType == RconType.SERVERDATA_AUTH_RESPONSE) { + if (inId == -1) { + throw const SocketException('Bad login.'); + } else if (inId == 0) { + return 'Authentication successful. You can now write commands.'; + } + } + + return utf8.decode(inBody); + } + + @override + Sink startChunkedConversion(Sink sink) { + return ChunkedConversionSink.withCallback((chunk) { + for (final data in chunk) { + sink.add(convert(data)); + } + }); + } + + // Override the base class's bind, to provide a better type. + @override + Stream bind(Stream stream) => super.bind(stream); +} diff --git a/pubspec.yaml b/pubspec.yaml index 3288df8..83f5f11 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.28 +version: 1.0.29 homepage: "https://zontreck.com" From d8854856844a4f7447b541faf3ae6cb062f79106 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 5 Jun 2024 22:01:01 -0700 Subject: [PATCH 46/50] TryCatch tail functions --- lib/packets/packets.dart | 13 +--- lib/utils/IOTools.dart | 27 ++++--- lib/utils/rcon/Rcon.dart | 124 -------------------------------- lib/utils/rcon/RconDecoder.dart | 48 ------------- pubspec.yaml | 2 +- 5 files changed, 22 insertions(+), 192 deletions(-) delete mode 100644 lib/utils/rcon/Rcon.dart delete mode 100644 lib/utils/rcon/RconDecoder.dart diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index 14e3ce0..b259924 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -16,12 +16,12 @@ class PacketServer { 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 { - S2CResponse response = S2CResponse(); try { CompoundTag tag = await NbtIo.readFromStream(data); StringBuilder builder = StringBuilder(); @@ -33,8 +33,8 @@ class PacketServer { request.decodeTag(tag); PacketResponse reply = await request.handleServerPacket(); - // Server uses NBT to communicate + // Server uses NBT to communicate builder = StringBuilder(); Tag.writeStringifiedNamedTag(reply.replyDataTag, builder, 0); @@ -45,17 +45,10 @@ class PacketServer { .put("error", StringTag.valueOf("Malformed request packet")); print( - "Something went wrong, sending malformed request response\n${stack}\n\n${E}\n\n"); - - sock.add( - await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); + "Something went wrong. Malformed request? \n\n${E}\n\n${stack}\n\n\n\n"); } finally { await sock.flush(); sock.close(); - - if (!shouldRestart) { - await socket!.close(); - } } }, onDone: () { sock.close(); diff --git a/lib/utils/IOTools.dart b/lib/utils/IOTools.dart index 4fdd578..0fef4f4 100644 --- a/lib/utils/IOTools.dart +++ b/lib/utils/IOTools.dart @@ -85,10 +85,12 @@ Stream> tail(final File file) async* { Stream _read() async* { while (pos < len) { - final bytesRead = await randomAccess.readInto(buf); - pos += bytesRead; + try { + final bytesRead = await randomAccess.readInto(buf); + pos += bytesRead; - yield buf.sublist(0, bytesRead); + yield buf.sublist(0, bytesRead); + } catch (E) {} } } @@ -98,15 +100,22 @@ Stream> tail(final File file) async* { // 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) { - len = await (randomAccess.length()); - yield* _read(); + try { + len = await (randomAccess.length()); + yield* _read(); + } catch (E) {} } } } void tailAndPrint(File file) { - 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); - }); + 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) {} } diff --git a/lib/utils/rcon/Rcon.dart b/lib/utils/rcon/Rcon.dart deleted file mode 100644 index 094268d..0000000 --- a/lib/utils/rcon/Rcon.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'RconDecoder.dart'; - -abstract class RconClient { - void exit(); - - void send(String cmd); - - factory RconClient() => RconClientImpl(); - - Stream connect(String host, int port, String password); -} - -class RconType { - /// RCON Packet Type 0: Server response - static const int SERVERDATA_RESPONSE_VALUE = 0; - - /// RCON Packet Type 2: Server auth response - static const int SERVERDATA_AUTH_RESPONSE = 2; - - /// RCON Packet Type 2: Client execute command - static const int SERVERDATA_EXECCOMMAND = 2; - - /// RCON Packet Type 3: Client send authentication data - static const int SERVERDATA_AUTH = 3; -} - -/// Client for the rcon protocol -class RconClientImpl implements RconClient { - /// Socket used to write and read TCP packets. - late Socket _socket; - - /// Disconnect from the a RCON server. - /// - /// Usage: - /// - /// ```dart - /// RconClient client = RconClient.connect(print, 'localhost', '25575', 'password'); - /// client.exit(); - /// ``` - @override - void exit() => _socket.destroy(); - - /// Send commands - @override - void send(String cmd) => _send(RconType.SERVERDATA_EXECCOMMAND, cmd); - - /// Authenticate to the server - void _authenticate(String password) => - _send(RconType.SERVERDATA_AUTH, password); - - /// Build and send a packet to the rcon server - /// - /// | Field | Type | Value | - /// |--------------|-------------------------------------|-------| - /// | Size | 32-bit little-endian Signed Integer | | - /// | ID | 32-bit little-endian Signed Integer | | - /// | Type | 32-bit little-endian Signed Integer | | - /// | Body | Null-terminated ASCII String | | - /// | Empty String | Null-terminated ASCII String | 0x00 | - void _send(int type, String payload) { - final Uint8List outSize = Uint8List(4); - final Uint8List outId = Uint8List(4); // Empty: 00 00 00 00 - final Uint8List outType = Uint8List(4); - - // Write type in the Type field - final ByteData typeData = ByteData.view(outType.buffer); - typeData.setInt32(0, type, Endian.little); - - // Build the body - final List outBody = utf8.encode(payload); - - // Build ID + Type + Body + Empty String - final Uint8List outPacketBody = - Uint8List.fromList(outId + outType + outBody + Uint8List(2)); - - // Calculate the Size field - final ByteData sizeData = ByteData.view(outSize.buffer); - sizeData.setInt32(0, outPacketBody.length, Endian.little); - - // Build the packet - final Uint8List packet = Uint8List.fromList(outSize + outPacketBody); - - // View packet - // print(packet); - // print(packet.map((int i) => i.toRadixString(16)).toList()); - // print(utf8.decode(packet)); - - // Send the packet - _socket.add(packet); - } - - /// Connect to the a RCON server. - /// - /// Usage: - /// - /// ```dart - /// final RconClient client = RconClient(); - /// final Stream response = client.connect( - /// 'localhost', - /// 25575, - /// 'password', - /// ); - /// ``` - @override - Stream connect(String host, int port, String password) async* { - print("Trying to connect..."); - - _socket = await Socket.connect(host, port); - - print("Connected."); - - final Stream stream = _socket.transform(RconDecoder()); - - print("Trying to authenticate"); - - _authenticate(password); - - yield* stream; - } -} diff --git a/lib/utils/rcon/RconDecoder.dart b/lib/utils/rcon/RconDecoder.dart deleted file mode 100644 index 1e81811..0000000 --- a/lib/utils/rcon/RconDecoder.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; - -import 'Rcon.dart'; - -class RconDecoder extends Converter { - /// Transform packets into a String. - /// - /// | Field | Type | Value | - /// |--------------|-------------------------------------|-------| - /// | Size | 32-bit little-endian Signed Integer | | - /// | ID | 32-bit little-endian Signed Integer | | - /// | Type | 32-bit little-endian Signed Integer | | - /// | Body | Null-terminated ASCII String | | - /// | Empty String | Null-terminated ASCII String | 0x00 | - @override - String convert(Uint8List data) { - final byteData = ByteData.sublistView(data); - // final inLength = byteData.getInt32(0, Endian.little); - final inId = byteData.getInt32(4, Endian.little); - final inType = byteData.getInt32(8, Endian.little); - final inBody = data.sublist(12); - - if (inType == RconType.SERVERDATA_AUTH_RESPONSE) { - if (inId == -1) { - throw const SocketException('Bad login.'); - } else if (inId == 0) { - return 'Authentication successful. You can now write commands.'; - } - } - - return utf8.decode(inBody); - } - - @override - Sink startChunkedConversion(Sink sink) { - return ChunkedConversionSink.withCallback((chunk) { - for (final data in chunk) { - sink.add(convert(data)); - } - }); - } - - // Override the base class's bind, to provide a better type. - @override - Stream bind(Stream stream) => super.bind(stream); -} diff --git a/pubspec.yaml b/pubspec.yaml index 83f5f11..44ba16f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.29 +version: 1.0.31 homepage: "https://zontreck.com" From 396c66011365deac38409f9377b9b9ba569d2474 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 6 Jun 2024 10:35:26 -0700 Subject: [PATCH 47/50] Allows root tag to be serialized without a name --- lib/nbt/Tag.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index ca0b4a6..d6b4023 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -86,10 +86,14 @@ abstract class Tag { if (tag.getType() != 0) { if (!builder.isEmpty) { // Write name - if (tag.shouldQuoteName()) { - builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": "); - } else - builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: "); + 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); From a0f372693b96f62940aa4326da367444385a60b3 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 6 Jun 2024 14:14:13 -0700 Subject: [PATCH 48/50] Implement basic SNBT reader and testsuite --- lib/nbt/SnbtIo.dart | 20 +++++- lib/nbt/Stream.dart | 121 +++++++++++++++++++++++++++++++++ lib/nbt/Tag.dart | 91 +++++++++++++++++++++++++ lib/nbt/impl/ByteArrayTag.dart | 12 ++++ lib/nbt/impl/ByteTag.dart | 8 +++ lib/nbt/impl/CompoundTag.dart | 12 ++++ lib/nbt/impl/DoubleTag.dart | 8 +++ lib/nbt/impl/EndTag.dart | 3 + lib/nbt/impl/FloatTag.dart | 8 +++ lib/nbt/impl/IntArrayTag.dart | 11 +++ lib/nbt/impl/IntTag.dart | 6 ++ lib/nbt/impl/ListTag.dart | 11 +++ lib/nbt/impl/LongArrayTag.dart | 12 ++++ lib/nbt/impl/LongTag.dart | 8 +++ lib/nbt/impl/ShortTag.dart | 8 +++ lib/nbt/impl/StringTag.dart | 6 ++ test/nbt_test.dart | 9 ++- 17 files changed, 352 insertions(+), 2 deletions(-) diff --git a/lib/nbt/SnbtIo.dart b/lib/nbt/SnbtIo.dart index 9a74bea..cb823a8 100644 --- a/lib/nbt/SnbtIo.dart +++ b/lib/nbt/SnbtIo.dart @@ -5,7 +5,7 @@ import 'Tag.dart'; import 'impl/CompoundTag.dart'; class SnbtIo { - static void write(String file, CompoundTag tag) { + static void writeToFile(String file, CompoundTag tag) { File handle = File(file); if (handle.existsSync()) @@ -15,4 +15,22 @@ class SnbtIo { Tag.writeStringifiedNamedTag(tag, builder, 0); handle.writeAsString(builder.toString()); } + + static Future 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 readFromString(String data) async { + return Tag.readStringifiedNamedTag(StringReader(data)); + } } diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index 82bf471..be7fde6 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -408,3 +408,124 @@ class StringBuilder { 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; + + // 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; + } +} diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index d6b4023..4a73e97 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -40,6 +40,79 @@ enum TagType { return TagType.End; } + + static TagType getStringifiedTagType(StringReader reader) { + reader.startSeek(); + TagType ret = TagType.End; + + // 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 "L": + { + ret = TagType.Long; + break; + } + case "S": + { + ret = TagType.Short; + break; + } + } + } + + reader.endSeek(); + return ret; + } } abstract class Tag { @@ -100,6 +173,23 @@ abstract class Tag { } } + 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; @@ -116,6 +206,7 @@ abstract class Tag { } void writeStringifiedValue(StringBuilder builder, int indent, bool isList); + void readStringifiedValue(StringReader reader); bool equals(dynamic object) { if (object == null || object is! Tag) return false; diff --git a/lib/nbt/impl/ByteArrayTag.dart b/lib/nbt/impl/ByteArrayTag.dart index c80ef47..818dcc5 100644 --- a/lib/nbt/impl/ByteArrayTag.dart +++ b/lib/nbt/impl/ByteArrayTag.dart @@ -52,4 +52,16 @@ class ByteArrayTag extends Tag { builder.append( "${isList ? "".padLeft(indent, '\t') : ""}[B; ${value.join("B, ")}B]"); } + + @override + void readStringifiedValue(StringReader reader) { + reader.expect("["); + while (reader.peek() != "]") { + value.add(int.parse(reader.readNumber())); + reader.expect("b"); + + if (reader.peek() == ",") reader.next(); + } + reader.expect("]"); + } } diff --git a/lib/nbt/impl/ByteTag.dart b/lib/nbt/impl/ByteTag.dart index a52738b..cd56ec9 100644 --- a/lib/nbt/impl/ByteTag.dart +++ b/lib/nbt/impl/ByteTag.dart @@ -39,4 +39,12 @@ class ByteTag extends Tag { 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"); + } } diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index 88bcdac..a0f9d1e 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -190,4 +190,16 @@ class CompoundTag extends Tag implements Map { @override Iterable get values => value.values; + + @override + void readStringifiedValue(StringReader reader) { + reader.expect("{"); + + while (reader.peek() != "}") { + Tag tag = Tag.readStringifiedNamedTag(reader); + put(tag.getKey(), tag); + } + + reader.expect("}"); + } } diff --git a/lib/nbt/impl/DoubleTag.dart b/lib/nbt/impl/DoubleTag.dart index a2ec956..d8b284b 100644 --- a/lib/nbt/impl/DoubleTag.dart +++ b/lib/nbt/impl/DoubleTag.dart @@ -39,4 +39,12 @@ class DoubleTag extends Tag { 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"); + } } diff --git a/lib/nbt/impl/EndTag.dart b/lib/nbt/impl/EndTag.dart index 1e7d0e3..0b87918 100644 --- a/lib/nbt/impl/EndTag.dart +++ b/lib/nbt/impl/EndTag.dart @@ -22,4 +22,7 @@ class EndTag extends Tag { @override void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {} + + @override + void readStringifiedValue(StringReader reader) {} } diff --git a/lib/nbt/impl/FloatTag.dart b/lib/nbt/impl/FloatTag.dart index f037d01..717a048 100644 --- a/lib/nbt/impl/FloatTag.dart +++ b/lib/nbt/impl/FloatTag.dart @@ -38,4 +38,12 @@ class FloatTag extends Tag { 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"); + } } diff --git a/lib/nbt/impl/IntArrayTag.dart b/lib/nbt/impl/IntArrayTag.dart index 009a02e..9840ab7 100644 --- a/lib/nbt/impl/IntArrayTag.dart +++ b/lib/nbt/impl/IntArrayTag.dart @@ -51,4 +51,15 @@ class IntArrayTag extends Tag { builder.append( "${isList ? "".padLeft(indent, '\t') : ""}[I; ${value.join('I, ')}I]"); } + + @override + void readStringifiedValue(StringReader reader) { + reader.expect("["); + while (reader.peek() != "]") { + value.add(int.parse(reader.readNumber())); + + if (reader.peek() == ",") reader.next(); + } + reader.expect("]"); + } } diff --git a/lib/nbt/impl/IntTag.dart b/lib/nbt/impl/IntTag.dart index 429d9a6..d4a533c 100644 --- a/lib/nbt/impl/IntTag.dart +++ b/lib/nbt/impl/IntTag.dart @@ -38,4 +38,10 @@ class IntTag extends Tag { 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); + } } diff --git a/lib/nbt/impl/ListTag.dart b/lib/nbt/impl/ListTag.dart index ee8d5f4..46ac985 100644 --- a/lib/nbt/impl/ListTag.dart +++ b/lib/nbt/impl/ListTag.dart @@ -103,4 +103,15 @@ class ListTag extends Tag { } builder.append("\n${"".padLeft(indent - 1, '\t')}]"); } + + @override + void readStringifiedValue(StringReader reader) { + reader.expect("["); + while (reader.peek() != "]") { + value.add(Tag.readStringifiedNamedTag(reader)); + + if (reader.peek() == ",") reader.next(); + } + reader.expect("]"); + } } diff --git a/lib/nbt/impl/LongArrayTag.dart b/lib/nbt/impl/LongArrayTag.dart index d517438..fa275db 100644 --- a/lib/nbt/impl/LongArrayTag.dart +++ b/lib/nbt/impl/LongArrayTag.dart @@ -55,4 +55,16 @@ class LongArrayTag extends Tag { builder.append( "${isList ? "".padLeft(indent, '\t') : ""}[L; ${value.join('L, ')}L]"); } + + @override + void readStringifiedValue(StringReader reader) { + reader.expect("["); + while (reader.peek() != "]") { + value.add(int.parse(reader.readNumber())); + reader.expect("l"); + + if (reader.peek() == ",") reader.next(); + } + reader.expect("]"); + } } diff --git a/lib/nbt/impl/LongTag.dart b/lib/nbt/impl/LongTag.dart index 89ac809..81f3b58 100644 --- a/lib/nbt/impl/LongTag.dart +++ b/lib/nbt/impl/LongTag.dart @@ -38,4 +38,12 @@ class LongTag extends Tag { 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"); + } } diff --git a/lib/nbt/impl/ShortTag.dart b/lib/nbt/impl/ShortTag.dart index 13ed6aa..a30cb12 100644 --- a/lib/nbt/impl/ShortTag.dart +++ b/lib/nbt/impl/ShortTag.dart @@ -38,4 +38,12 @@ class ShortTag extends Tag { 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"); + } } diff --git a/lib/nbt/impl/StringTag.dart b/lib/nbt/impl/StringTag.dart index 4829e31..2978e2c 100644 --- a/lib/nbt/impl/StringTag.dart +++ b/lib/nbt/impl/StringTag.dart @@ -38,4 +38,10 @@ class StringTag extends Tag { 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; + } } diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 69273f7..1636ab8 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -82,12 +82,19 @@ void main() { await NbtIo.read("${Directory.current.path}/test/bigtest.nbt"); String output = "${Directory.current.path}/build/bigtest.snbt"; File file = File(output); - SnbtIo.write(output, ct); + SnbtIo.writeToFile(output, ct); // Expect that the file exists expect(file.existsSync(), 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); + }); + test("Write NULL UUID to NBT", () async { CompoundTag tag = CompoundTag(); NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(UUID.ZERO)); From dfd536ae1c64bac7f0ffa1b71376d9eadd65a3c7 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 6 Jun 2024 18:14:41 -0700 Subject: [PATCH 49/50] Finish implementing - and testing SNBT --- lib/nbt/Stream.dart | 3 +++ lib/nbt/Tag.dart | 26 ++++++++++++++++++++++++++ lib/nbt/impl/ByteArrayTag.dart | 2 ++ lib/nbt/impl/CompoundTag.dart | 2 ++ lib/nbt/impl/IntArrayTag.dart | 2 ++ lib/nbt/impl/IntTag.dart | 6 ++++++ lib/nbt/impl/ListTag.dart | 5 ++++- lib/nbt/impl/LongArrayTag.dart | 2 ++ lib/utils/IOTools.dart | 5 ++++- test/nbt_test.dart | 7 ++++++- 10 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/nbt/Stream.dart b/lib/nbt/Stream.dart index be7fde6..d315c1a 100644 --- a/lib/nbt/Stream.dart +++ b/lib/nbt/Stream.dart @@ -419,6 +419,9 @@ class StringReader { // 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) { diff --git a/lib/nbt/Tag.dart b/lib/nbt/Tag.dart index 4a73e97..718a4e4 100644 --- a/lib/nbt/Tag.dart +++ b/lib/nbt/Tag.dart @@ -44,6 +44,7 @@ enum TagType { 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) { @@ -97,6 +98,11 @@ enum TagType { ret = TagType.Float; break; } + case "I": + { + ret = TagType.Int; + break; + } case "L": { ret = TagType.Long; @@ -107,6 +113,26 @@ enum TagType { 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; + } } } diff --git a/lib/nbt/impl/ByteArrayTag.dart b/lib/nbt/impl/ByteArrayTag.dart index 818dcc5..f94a63d 100644 --- a/lib/nbt/impl/ByteArrayTag.dart +++ b/lib/nbt/impl/ByteArrayTag.dart @@ -56,6 +56,8 @@ class ByteArrayTag extends Tag { @override void readStringifiedValue(StringReader reader) { reader.expect("["); + reader.expect("B"); + reader.expect(";"); while (reader.peek() != "]") { value.add(int.parse(reader.readNumber())); reader.expect("b"); diff --git a/lib/nbt/impl/CompoundTag.dart b/lib/nbt/impl/CompoundTag.dart index a0f9d1e..1a5c8ea 100644 --- a/lib/nbt/impl/CompoundTag.dart +++ b/lib/nbt/impl/CompoundTag.dart @@ -198,6 +198,8 @@ class CompoundTag extends Tag implements Map { while (reader.peek() != "}") { Tag tag = Tag.readStringifiedNamedTag(reader); put(tag.getKey(), tag); + + if (reader.peek() == ",") reader.next(); } reader.expect("}"); diff --git a/lib/nbt/impl/IntArrayTag.dart b/lib/nbt/impl/IntArrayTag.dart index 9840ab7..651ac33 100644 --- a/lib/nbt/impl/IntArrayTag.dart +++ b/lib/nbt/impl/IntArrayTag.dart @@ -55,6 +55,8 @@ class IntArrayTag extends Tag { @override void readStringifiedValue(StringReader reader) { reader.expect("["); + reader.expect("I"); + reader.expect(";"); while (reader.peek() != "]") { value.add(int.parse(reader.readNumber())); diff --git a/lib/nbt/impl/IntTag.dart b/lib/nbt/impl/IntTag.dart index d4a533c..65d16c3 100644 --- a/lib/nbt/impl/IntTag.dart +++ b/lib/nbt/impl/IntTag.dart @@ -43,5 +43,11 @@ class IntTag extends Tag { 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"); } } diff --git a/lib/nbt/impl/ListTag.dart b/lib/nbt/impl/ListTag.dart index 46ac985..60ca16c 100644 --- a/lib/nbt/impl/ListTag.dart +++ b/lib/nbt/impl/ListTag.dart @@ -108,7 +108,10 @@ class ListTag extends Tag { void readStringifiedValue(StringReader reader) { reader.expect("["); while (reader.peek() != "]") { - value.add(Tag.readStringifiedNamedTag(reader)); + TagType type = TagType.getStringifiedTagType(reader); + Tag newTag = Tag.makeTagOfType(type); + newTag.readStringifiedValue(reader); + add(newTag); if (reader.peek() == ",") reader.next(); } diff --git a/lib/nbt/impl/LongArrayTag.dart b/lib/nbt/impl/LongArrayTag.dart index fa275db..af10d69 100644 --- a/lib/nbt/impl/LongArrayTag.dart +++ b/lib/nbt/impl/LongArrayTag.dart @@ -59,6 +59,8 @@ class LongArrayTag extends Tag { @override void readStringifiedValue(StringReader reader) { reader.expect("["); + reader.expect("L"); + reader.expect(";"); while (reader.peek() != "]") { value.add(int.parse(reader.readNumber())); reader.expect("l"); diff --git a/lib/utils/IOTools.dart b/lib/utils/IOTools.dart index 0fef4f4..c704c16 100644 --- a/lib/utils/IOTools.dart +++ b/lib/utils/IOTools.dart @@ -15,7 +15,10 @@ class PathHelper { } bool exists() { - return File(build()).existsSync(); + File fi = File(build()); + Directory dir = Directory(build()); + + return fi.existsSync() || dir.existsSync(); } PathHelper clone() { diff --git a/test/nbt_test.dart b/test/nbt_test.dart index 1636ab8..4e9c23a 100644 --- a/test/nbt_test.dart +++ b/test/nbt_test.dart @@ -7,6 +7,7 @@ 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'; @@ -85,7 +86,10 @@ void main() { SnbtIo.writeToFile(output, ct); // Expect that the file exists - expect(file.existsSync(), true); + PathHelper ph = PathHelper.builder(Directory.current.path) + .resolve("build") + .resolve("bigtest.snbt"); + expect(ph.exists(), true); }); test("Read BigTest from SNBT file", () async { @@ -93,6 +97,7 @@ void main() { "${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 { From bb67d9f335806dae98da76d8a9928822456f50f5 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 6 Jun 2024 18:15:02 -0700 Subject: [PATCH 50/50] Increment Version number --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 44ba16f..6bc5c9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.0.31 +version: 1.0.32 homepage: "https://zontreck.com"