Reviewed-on: #9
This commit is contained in:
commit
2954a1924e
34 changed files with 1785 additions and 157 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -29,4 +29,6 @@ doc/api/
|
|||
|
||||
|
||||
.idea
|
||||
*.iml
|
||||
*.iml
|
||||
|
||||
out
|
24
bin/client_test.dart
Normal file
24
bin/client_test.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:libac_dart/nbt/Stream.dart';
|
||||
import 'package:libac_dart/nbt/Tag.dart';
|
||||
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
|
||||
import 'package:libac_dart/packets/packets.dart';
|
||||
|
||||
void main() async {
|
||||
PacketRegistry reg = PacketRegistry();
|
||||
reg.registerDefaults();
|
||||
|
||||
PacketClient client = PacketClient();
|
||||
await client.startConnect("127.0.0.1", 25306);
|
||||
|
||||
S2CResponse response = await client.send(C2SPing(), true);
|
||||
CompoundTag tag = response.contents;
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
|
||||
print("Response from server: \n${builder}");
|
||||
|
||||
await client.send(StopServerPacket(), false);
|
||||
|
||||
if (client.connected) await client.close();
|
||||
return;
|
||||
}
|
5
bin/server_test.dart
Normal file
5
bin/server_test.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:libac_dart/packets/packets.dart';
|
||||
|
||||
void main() async {
|
||||
await PacketServer.start(25306);
|
||||
}
|
7
compile.sh
Executable file
7
compile.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
mkdir out
|
||||
rm -rf out/*
|
||||
flutter pub publish -f --skip-validation
|
||||
dart compile exe -o out/server_test bin/server_test.dart
|
||||
dart compile exe -o out/client_test bin/client_test.dart
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../utils/Converter.dart';
|
||||
import 'Stream.dart';
|
||||
import 'Tag.dart';
|
||||
import 'impl/CompoundTag.dart';
|
||||
|
||||
class NbtIo {
|
||||
static ByteLayer _io = ByteLayer();
|
||||
|
@ -49,4 +52,44 @@ class NbtIo {
|
|||
static ByteLayer getStream() {
|
||||
return _io;
|
||||
}
|
||||
|
||||
static Future<String> writeBase64String(CompoundTag tag) async {
|
||||
_io = ByteLayer();
|
||||
Tag.writeNamedTag(tag, _io);
|
||||
_io.compress();
|
||||
return base64Encoder.encode(_io.bytes);
|
||||
}
|
||||
|
||||
static Future<CompoundTag> readBase64String(String encoded) async {
|
||||
_io = ByteLayer();
|
||||
List<int> bytes = base64Encoder.decode(encoded);
|
||||
for (int byte in bytes) {
|
||||
_io.writeByte(byte);
|
||||
}
|
||||
_io.decompress();
|
||||
_io.resetPosition();
|
||||
return Tag.readNamedTag(_io) as CompoundTag;
|
||||
}
|
||||
|
||||
static Future<Uint8List> writeToStream(CompoundTag tag) async {
|
||||
_io = ByteLayer();
|
||||
Tag.writeNamedTag(tag, _io);
|
||||
_io.compress();
|
||||
|
||||
return _io.bytes;
|
||||
}
|
||||
|
||||
static Future<CompoundTag> readFromStream(Uint8List list) async {
|
||||
_io = ByteLayer();
|
||||
try {
|
||||
_io.writeBytes(list);
|
||||
_io.resetPosition();
|
||||
_io.decompress();
|
||||
_io.resetPosition();
|
||||
} catch (E) {
|
||||
print(E);
|
||||
} finally {
|
||||
return Tag.readNamedTag(_io) as CompoundTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:libac_flutter/nbt/impl/ByteTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/DoubleTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/IntArrayTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/IntTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/ListTag.dart';
|
||||
import 'package:libac_flutter/utils/Vector3.dart';
|
||||
|
||||
import '../utils/Vector2.dart';
|
||||
import '../utils/uuid/UUID.dart';
|
||||
import '../utils/Vector3.dart';
|
||||
import '../utils/uuid/NbtUUID.dart';
|
||||
import 'impl/ByteTag.dart';
|
||||
import 'impl/CompoundTag.dart';
|
||||
import 'impl/DoubleTag.dart';
|
||||
import 'impl/IntArrayTag.dart';
|
||||
import 'impl/IntTag.dart';
|
||||
import 'impl/ListTag.dart';
|
||||
|
||||
class NbtUtils {
|
||||
static void writeBoolean(CompoundTag tag, String name, bool b) {
|
||||
|
@ -15,7 +14,7 @@ class NbtUtils {
|
|||
}
|
||||
|
||||
static bool readBoolean(CompoundTag tag, String name) {
|
||||
if (tag.contains(name)) {
|
||||
if (tag.containsKey(name)) {
|
||||
return tag.get(name)!.asByte() == 1 ? true : false;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -30,7 +29,7 @@ class NbtUtils {
|
|||
}
|
||||
|
||||
static Vector2d readVector2d(CompoundTag tag, String name) {
|
||||
if (tag.contains(name)) {
|
||||
if (tag.containsKey(name)) {
|
||||
ListTag lst = tag.get(name)! as ListTag;
|
||||
return Vector2d(X: lst.get(0).asDouble(), Z: lst.get(1).asDouble());
|
||||
} else {
|
||||
|
@ -46,7 +45,7 @@ class NbtUtils {
|
|||
}
|
||||
|
||||
static Vector2i readVector2i(CompoundTag tag, String name) {
|
||||
if (tag.contains(name)) {
|
||||
if (tag.containsKey(name)) {
|
||||
ListTag lst = tag.get(name)! as ListTag;
|
||||
return Vector2i(X: lst.get(0).asInt(), Z: lst.get(1).asInt());
|
||||
} else {
|
||||
|
@ -63,7 +62,7 @@ class NbtUtils {
|
|||
}
|
||||
|
||||
static Vector3d readVector3d(CompoundTag tag, String name) {
|
||||
if (tag.contains(name)) {
|
||||
if (tag.containsKey(name)) {
|
||||
ListTag lst = tag.get(name)! as ListTag;
|
||||
return Vector3d(
|
||||
X: lst.get(0).asDouble(),
|
||||
|
@ -83,7 +82,7 @@ class NbtUtils {
|
|||
}
|
||||
|
||||
static Vector3i readVector3i(CompoundTag tag, String name) {
|
||||
if (tag.contains(name)) {
|
||||
if (tag.containsKey(name)) {
|
||||
ListTag lst = tag.get(name)! as ListTag;
|
||||
return Vector3i(
|
||||
X: lst.get(0).asInt(), Y: lst.get(1).asInt(), Z: lst.get(2).asInt());
|
||||
|
@ -96,24 +95,25 @@ class NbtUtils {
|
|||
return [msb >> 32, msb, lsb >> 32, lsb];
|
||||
}
|
||||
|
||||
static List<int> _uuidToIntArray(UUID ID) {
|
||||
static List<int> _uuidToIntArray(NbtUUID ID) {
|
||||
return _msbLsbToIntArray(
|
||||
ID.getMostSignificantBits(), ID.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
static UUID _uuidFromIntArray(List<int> values) {
|
||||
return UUID((values[0] << 32 | values[1] & 4294967295),
|
||||
static NbtUUID _uuidFromIntArray(List<int> values) {
|
||||
return NbtUUID((values[0] << 32 | values[1] & 4294967295),
|
||||
(values[2] << 32 | values[3] & 4294967295));
|
||||
}
|
||||
|
||||
static void writeUUID(CompoundTag tag, String name, UUID ID) {
|
||||
static void writeUUID(CompoundTag tag, String name, NbtUUID ID) {
|
||||
tag.put(name, IntArrayTag.valueOf(_uuidToIntArray(ID)));
|
||||
}
|
||||
|
||||
static UUID readUUID(CompoundTag tag, String name) {
|
||||
if (!tag.contains(name))
|
||||
return UUID.ZERO;
|
||||
else
|
||||
static NbtUUID readUUID(CompoundTag tag, String name) {
|
||||
if (!tag.containsKey(name)) {
|
||||
return NbtUUID.ZERO;
|
||||
} else {
|
||||
return _uuidFromIntArray(tag.get(name)!.asIntArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
lib/nbt/SnbtIo.dart
Normal file
36
lib/nbt/SnbtIo.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'Stream.dart';
|
||||
import 'Tag.dart';
|
||||
import 'impl/CompoundTag.dart';
|
||||
|
||||
class SnbtIo {
|
||||
static void writeToFile(String file, CompoundTag tag) {
|
||||
File handle = File(file);
|
||||
|
||||
if (handle.existsSync())
|
||||
handle.deleteSync(); // Ensure we flush the file to 0 bytes
|
||||
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
handle.writeAsString(builder.toString());
|
||||
}
|
||||
|
||||
static Future<Tag> readFromFile(String file) async {
|
||||
File fi = File(file);
|
||||
String data = await fi.readAsString();
|
||||
StringReader reader = StringReader(data);
|
||||
|
||||
return Tag.readStringifiedNamedTag(reader);
|
||||
}
|
||||
|
||||
static String writeToString(CompoundTag tag) {
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static Future<Tag> readFromString(String data) async {
|
||||
return Tag.readStringifiedNamedTag(StringReader(data));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ByteLayer {
|
||||
|
@ -209,6 +210,8 @@ class ByteLayer {
|
|||
|
||||
Future<void> writeToFile(String filePath) async {
|
||||
final file = File(filePath);
|
||||
if (file.existsSync())
|
||||
file.deleteSync(); // Ensure we flush the file to 0 bytes
|
||||
await file.writeAsBytes(bytes);
|
||||
}
|
||||
|
||||
|
@ -351,8 +354,9 @@ class ByteLayer {
|
|||
seek(position);
|
||||
int current = readUnsignedByte();
|
||||
return (current & mask) == mask;
|
||||
} else
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int getBit(int position) {
|
||||
|
@ -375,4 +379,156 @@ class ByteLayer {
|
|||
clearBit(position, maskToClear);
|
||||
setBit(position, maskToSet);
|
||||
}
|
||||
|
||||
void insertRandomBytes(int count) {
|
||||
Random rng = Random();
|
||||
for (int a = 0; a < count; a++) {
|
||||
writeByte(rng.nextInt(255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StringBuilder {
|
||||
String _buffer = "";
|
||||
|
||||
StringBuilder();
|
||||
|
||||
bool get isEmpty => _buffer.isEmpty;
|
||||
|
||||
void append(String value) {
|
||||
_buffer += value;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_buffer = "";
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "${_buffer}";
|
||||
}
|
||||
}
|
||||
|
||||
class StringReader {
|
||||
final String _buffer;
|
||||
int _position = 0;
|
||||
int _lastPostion = 0;
|
||||
|
||||
StringReader(this._buffer);
|
||||
|
||||
// Check if there's more to read
|
||||
bool get canRead => _position < _buffer.length;
|
||||
|
||||
// Get the number of chars seeked
|
||||
int get getSeeked => _lastPostion - _position;
|
||||
|
||||
// Read the next character
|
||||
String next() {
|
||||
if (canRead) {
|
||||
skipWhitespace();
|
||||
return _buffer[_position++];
|
||||
} else {
|
||||
throw Exception("End of buffer reached");
|
||||
}
|
||||
}
|
||||
|
||||
// Peek the next character without advancing the position
|
||||
String peek() {
|
||||
skipWhitespace();
|
||||
if (canRead) {
|
||||
return _buffer[_position];
|
||||
} else {
|
||||
throw Exception("End of buffer reached");
|
||||
}
|
||||
}
|
||||
|
||||
// Skip any whitespace characters
|
||||
void skipWhitespace() {
|
||||
while (canRead && isWhitespace(_buffer[_position])) {
|
||||
_position++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a character is a whitespace
|
||||
bool isWhitespace(String char) {
|
||||
return char.trim().isEmpty;
|
||||
}
|
||||
|
||||
// Read until a specific character is found
|
||||
String readUntil(String stopChar) {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead && peek() != stopChar) {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Read a string enclosed in double quotes
|
||||
String readQuotedString() {
|
||||
if (next() != '"') {
|
||||
throw Exception('Expected double quotes at the start of a string');
|
||||
}
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead) {
|
||||
String char = next();
|
||||
if (char == '"') {
|
||||
break;
|
||||
}
|
||||
result.write(char);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Read a number (int or double)
|
||||
String readNumber() {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead && (isDigit(peek()) || peek() == '.' || peek() == '-')) {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Check if a character is a digit
|
||||
bool isDigit(String char) {
|
||||
return int.tryParse(char) != null;
|
||||
}
|
||||
|
||||
// Read an unquoted string (used for keys in SNBT)
|
||||
String readUnquotedString() {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead &&
|
||||
!isWhitespace(peek()) &&
|
||||
peek() != ':' &&
|
||||
peek() != ',' &&
|
||||
peek() != '{' &&
|
||||
peek() != '}' &&
|
||||
peek() != '[' &&
|
||||
peek() != ']') {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
String readString() {
|
||||
if (peek() == "\"") {
|
||||
return readQuotedString();
|
||||
} else
|
||||
return readUnquotedString();
|
||||
}
|
||||
|
||||
// Read a specific character and throw an exception if it's not found
|
||||
void expect(String expectedChar) {
|
||||
if (next().toLowerCase() != expectedChar.toLowerCase()) {
|
||||
throw Exception('Expected $expectedChar');
|
||||
}
|
||||
}
|
||||
|
||||
void startSeek() {
|
||||
_lastPostion = _position;
|
||||
}
|
||||
|
||||
void endSeek() {
|
||||
_position = _lastPostion;
|
||||
_lastPostion = 0;
|
||||
}
|
||||
}
|
||||
|
|
179
lib/nbt/Tag.dart
179
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 {
|
||||
|
@ -41,6 +40,105 @@ enum TagType {
|
|||
|
||||
return TagType.End;
|
||||
}
|
||||
|
||||
static TagType getStringifiedTagType(StringReader reader) {
|
||||
reader.startSeek();
|
||||
TagType ret = TagType.End;
|
||||
bool isNumber = true;
|
||||
|
||||
// Start to determine the next tag type
|
||||
while (reader.canRead && ret == TagType.End) {
|
||||
var val = reader.next().toUpperCase();
|
||||
switch (val) {
|
||||
case "{":
|
||||
{
|
||||
ret = TagType.Compound;
|
||||
break;
|
||||
}
|
||||
case "[":
|
||||
{
|
||||
// Check for a type Prefix
|
||||
var X = reader.readUntil(";");
|
||||
switch (X.toUpperCase()) {
|
||||
case "B":
|
||||
{
|
||||
ret = TagType.ByteArray;
|
||||
break;
|
||||
}
|
||||
case "I":
|
||||
{
|
||||
ret = TagType.IntArray;
|
||||
break;
|
||||
}
|
||||
case "L":
|
||||
{
|
||||
ret = TagType.LongArray;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ret = TagType.List;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "B":
|
||||
{
|
||||
ret = TagType.Byte;
|
||||
break;
|
||||
}
|
||||
case "D":
|
||||
{
|
||||
ret = TagType.Double;
|
||||
break;
|
||||
}
|
||||
case "F":
|
||||
{
|
||||
ret = TagType.Float;
|
||||
break;
|
||||
}
|
||||
case "I":
|
||||
{
|
||||
ret = TagType.Int;
|
||||
break;
|
||||
}
|
||||
case "L":
|
||||
{
|
||||
ret = TagType.Long;
|
||||
break;
|
||||
}
|
||||
case "S":
|
||||
{
|
||||
ret = TagType.Short;
|
||||
break;
|
||||
}
|
||||
case "\"":
|
||||
{
|
||||
ret = TagType.String;
|
||||
break;
|
||||
}
|
||||
case ",":
|
||||
case "\n":
|
||||
{
|
||||
if (reader.getSeeked >= 1) ret = TagType.String;
|
||||
if (isNumber) ret = TagType.Int;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!reader.isDigit(val)) {
|
||||
if (isNumber) isNumber = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.endSeek();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Tag {
|
||||
|
@ -82,6 +180,60 @@ abstract class Tag {
|
|||
}
|
||||
}
|
||||
|
||||
static void writeStringifiedNamedTag(
|
||||
Tag tag, StringBuilder builder, int indents) {
|
||||
if (tag.getType() != 0) {
|
||||
if (!builder.isEmpty) {
|
||||
// Write name
|
||||
if (tag._key == "") {
|
||||
builder.append("${"".padLeft(indents, '\t')}");
|
||||
} else {
|
||||
if (tag.shouldQuoteName()) {
|
||||
builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": ");
|
||||
} else
|
||||
builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: ");
|
||||
}
|
||||
}
|
||||
|
||||
tag.writeStringifiedValue(builder, indents + 1, false);
|
||||
}
|
||||
}
|
||||
|
||||
static Tag readStringifiedNamedTag(StringReader string) {
|
||||
String name = "";
|
||||
if (string.peek() == "{" || string.peek() == "[") {
|
||||
// No name
|
||||
name = "";
|
||||
} else {
|
||||
name = string.readString();
|
||||
string.expect(":");
|
||||
}
|
||||
TagType type = TagType.getStringifiedTagType(string);
|
||||
Tag tag = Tag.makeTagOfType(type);
|
||||
tag._key = name;
|
||||
tag.readStringifiedValue(string);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool shouldQuoteName() {
|
||||
if (getKey() == "") {
|
||||
return false;
|
||||
} else {
|
||||
String letters = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (int i = 0; i < getKey().length; i++) {
|
||||
String digit = getKey().substring(i, i + 1);
|
||||
if (letters.indexOf(digit) == -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList);
|
||||
void readStringifiedValue(StringReader reader);
|
||||
|
||||
bool equals(dynamic object) {
|
||||
if (object == null || object is! Tag) return false;
|
||||
|
||||
|
@ -232,6 +384,13 @@ abstract class Tag {
|
|||
}
|
||||
}
|
||||
|
||||
CompoundTag asCompoundTag() {
|
||||
if (this is CompoundTag) {
|
||||
return this as CompoundTag;
|
||||
} else
|
||||
return CompoundTag();
|
||||
}
|
||||
|
||||
void prettyPrint(int indent, bool recurse);
|
||||
|
||||
static String getCanonicalName(TagType type) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class ByteArrayTag extends Tag {
|
||||
final List<int> value = [];
|
||||
|
@ -46,4 +46,24 @@ class ByteArrayTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[B; ${value.join("B, ")}B]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
reader.expect("B");
|
||||
reader.expect(";");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
reader.expect("b");
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class ByteTag extends Tag {
|
||||
int value = 0;
|
||||
|
@ -34,4 +34,17 @@ class ByteTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${value}b");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String val = reader.readNumber();
|
||||
value = int.parse(val);
|
||||
|
||||
reader.expect("b");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class CompoundTag extends Tag {
|
||||
class CompoundTag extends Tag implements Map<String, Tag> {
|
||||
late final Map<String, Tag> value = {};
|
||||
|
||||
CompoundTag();
|
||||
|
@ -48,12 +48,8 @@ class CompoundTag extends Tag {
|
|||
tag.setKey(name);
|
||||
}
|
||||
|
||||
bool contains(String name) {
|
||||
return value.containsKey(name);
|
||||
}
|
||||
|
||||
Tag? get(String name) {
|
||||
if (contains(name)) {
|
||||
if (containsKey(name)) {
|
||||
return value[name] as Tag;
|
||||
} else {
|
||||
// Does not exist!
|
||||
|
@ -61,10 +57,6 @@ class CompoundTag extends Tag {
|
|||
}
|
||||
}
|
||||
|
||||
void remove(String name) {
|
||||
value.remove(name);
|
||||
}
|
||||
|
||||
@override
|
||||
TagType getTagType() {
|
||||
return TagType.Compound;
|
||||
|
@ -85,4 +77,131 @@ class CompoundTag extends Tag {
|
|||
void endPrettyPrint(int indent) {
|
||||
print("${"".padLeft(indent, '\t')}}");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
Iterator<Tag> it = value.values.iterator;
|
||||
builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}{\n");
|
||||
|
||||
bool firstEntry = true;
|
||||
while (it.moveNext()) {
|
||||
Tag t = it.current;
|
||||
if (firstEntry) {
|
||||
firstEntry = false;
|
||||
} else {
|
||||
builder.append(",\n");
|
||||
}
|
||||
Tag.writeStringifiedNamedTag(t, builder, indent);
|
||||
}
|
||||
builder.append("\n${"".padLeft(indent - 1, '\t')}}");
|
||||
}
|
||||
|
||||
@override
|
||||
Tag? operator [](Object? key) {
|
||||
return value[key];
|
||||
}
|
||||
|
||||
@override
|
||||
void operator []=(String key, Tag value) {
|
||||
this.value[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
void addAll(Map<String, Tag> other) {
|
||||
value.addAll(other);
|
||||
}
|
||||
|
||||
@override
|
||||
void addEntries(Iterable<MapEntry<String, Tag>> newEntries) {
|
||||
value.addEntries(newEntries);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<RK, RV> cast<RK, RV>() {
|
||||
return value.cast();
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
value.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
bool containsKey(Object? key) {
|
||||
return value.containsKey(key);
|
||||
}
|
||||
|
||||
@override
|
||||
bool containsValue(Object? value) {
|
||||
return this.value.containsValue(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<MapEntry<String, Tag>> get entries => value.entries;
|
||||
|
||||
@override
|
||||
void forEach(void Function(String key, Tag value) action) {
|
||||
value.forEach(action);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isEmpty => value.isEmpty;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => value.isNotEmpty;
|
||||
|
||||
@override
|
||||
Iterable<String> get keys => value.keys;
|
||||
|
||||
@override
|
||||
int get length => value.length;
|
||||
|
||||
@override
|
||||
Map<K2, V2> map<K2, V2>(
|
||||
MapEntry<K2, V2> Function(String key, Tag value) convert) {
|
||||
return value.map(convert);
|
||||
}
|
||||
|
||||
@override
|
||||
Tag putIfAbsent(String key, Tag Function() ifAbsent) {
|
||||
return this.value.putIfAbsent(key, ifAbsent);
|
||||
}
|
||||
|
||||
@override
|
||||
Tag? remove(Object? key) {
|
||||
return value.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeWhere(bool Function(String key, Tag value) test) {
|
||||
value.removeWhere(test);
|
||||
}
|
||||
|
||||
@override
|
||||
Tag update(String key, Tag Function(Tag value) update,
|
||||
{Tag Function()? ifAbsent}) {
|
||||
return value.update(key, update);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateAll(Tag Function(String key, Tag value) update) {
|
||||
value.updateAll(update);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<Tag> get values => value.values;
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("{");
|
||||
|
||||
while (reader.peek() != "}") {
|
||||
Tag tag = Tag.readStringifiedNamedTag(reader);
|
||||
put(tag.getKey(), tag);
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
|
||||
reader.expect("}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class DoubleTag extends Tag {
|
||||
double value = 0.0;
|
||||
|
@ -34,4 +34,17 @@ class DoubleTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}d");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
double val = double.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("d");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class EndTag extends Tag {
|
||||
EndTag();
|
||||
|
@ -19,4 +19,10 @@ class EndTag extends Tag {
|
|||
void prettyPrint(int indent, bool recurse) {
|
||||
print("${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class FloatTag extends Tag {
|
||||
double value = 0.0;
|
||||
|
@ -33,4 +33,17 @@ class FloatTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}f");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
double val = double.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("f");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class IntArrayTag extends Tag {
|
||||
final List<int> value = [];
|
||||
|
@ -45,4 +45,23 @@ class IntArrayTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[I; ${value.join('I, ')}I]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
reader.expect("I");
|
||||
reader.expect(";");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class IntTag extends Tag {
|
||||
int value = 0;
|
||||
|
@ -33,4 +33,21 @@ class IntTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}i");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String val = reader.readNumber();
|
||||
value = int.parse(val);
|
||||
|
||||
// Since a type indicator is optional for a int, check for a comma
|
||||
if (reader.peek() == ",")
|
||||
return;
|
||||
else
|
||||
reader.expect("i");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
import 'EndTag.dart';
|
||||
|
||||
class ListTag extends Tag {
|
||||
|
@ -86,4 +85,36 @@ class ListTag extends Tag {
|
|||
void endPrettyPrint(int indent) {
|
||||
print("${"".padLeft(indent, '\t')}]");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent - 1, '\t') : ""}[\n");
|
||||
Iterator<Tag> it = value.iterator;
|
||||
bool firstTag = true;
|
||||
while (it.moveNext()) {
|
||||
Tag tag = it.current;
|
||||
if (firstTag)
|
||||
firstTag = !firstTag;
|
||||
else {
|
||||
builder.append(",\n");
|
||||
}
|
||||
|
||||
tag.writeStringifiedValue(builder, indent + 1, true);
|
||||
}
|
||||
builder.append("\n${"".padLeft(indent - 1, '\t')}]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
while (reader.peek() != "]") {
|
||||
TagType type = TagType.getStringifiedTagType(reader);
|
||||
Tag newTag = Tag.makeTagOfType(type);
|
||||
newTag.readStringifiedValue(reader);
|
||||
add(newTag);
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class LongArrayTag extends Tag {
|
||||
final List<int> value = [];
|
||||
|
@ -49,4 +49,24 @@ class LongArrayTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: [$array]");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[L; ${value.join('L, ')}L]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
reader.expect("L");
|
||||
reader.expect(";");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
reader.expect("l");
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class LongTag extends Tag {
|
||||
int value = 0;
|
||||
|
@ -33,4 +33,17 @@ class LongTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}L");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
int val = int.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("l");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class ShortTag extends Tag {
|
||||
int value = 0;
|
||||
|
@ -33,4 +33,17 @@ class ShortTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}S");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
int val = int.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("s");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/nbt/Tag.dart';
|
||||
import '../Stream.dart';
|
||||
import '../Tag.dart';
|
||||
|
||||
class StringTag extends Tag {
|
||||
String value = "";
|
||||
|
@ -33,4 +33,15 @@ class StringTag extends Tag {
|
|||
print(
|
||||
"${"".padLeft(indent, '\t')}${Tag.getCanonicalName(getTagType())}: $value");
|
||||
}
|
||||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}\"${value}\"");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String str = reader.readString();
|
||||
value = str;
|
||||
}
|
||||
}
|
||||
|
|
451
lib/packets/packets.dart
Normal file
451
lib/packets/packets.dart
Normal file
|
@ -0,0 +1,451 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import '../nbt/NbtIo.dart';
|
||||
import '../nbt/Stream.dart';
|
||||
import '../nbt/Tag.dart';
|
||||
import '../nbt/impl/CompoundTag.dart';
|
||||
import '../nbt/impl/StringTag.dart';
|
||||
|
||||
class PacketServer {
|
||||
static ServerSocket? socket;
|
||||
static bool shouldRestart = true;
|
||||
static Future<void> start(int port) async {
|
||||
socket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
|
||||
print("Server now listening on port ${port}");
|
||||
|
||||
await for (var sock in socket!) {
|
||||
S2CResponse response = S2CResponse();
|
||||
print(
|
||||
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
|
||||
|
||||
try {
|
||||
sock.listen((data) async {
|
||||
try {
|
||||
CompoundTag tag = await NbtIo.readFromStream(data);
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
|
||||
print("Request from client: \n${builder}");
|
||||
|
||||
C2SRequestPacket request = C2SRequestPacket();
|
||||
request.decodeTag(tag);
|
||||
|
||||
PacketResponse reply = await request.handleServerPacket();
|
||||
|
||||
// Server uses NBT to communicate
|
||||
builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(reply.replyDataTag, builder, 0);
|
||||
|
||||
print("Response to client: \n${builder}");
|
||||
sock.add(await NbtIo.writeToStream(reply.replyDataTag));
|
||||
} catch (E, stack) {
|
||||
response.contents
|
||||
.put("error", StringTag.valueOf("Malformed request packet"));
|
||||
|
||||
print(
|
||||
"Something went wrong. Malformed request? \n\n${E}\n\n${stack}\n\n\n\n");
|
||||
} finally {
|
||||
await sock.flush();
|
||||
sock.close();
|
||||
}
|
||||
}, onDone: () {
|
||||
sock.close();
|
||||
}, onError: (E) {
|
||||
print("ERROR: ${E}");
|
||||
sock.close();
|
||||
});
|
||||
} catch (E) {
|
||||
sock.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PacketClient {
|
||||
Socket? socket;
|
||||
bool connected = false;
|
||||
String lastIP = "";
|
||||
int port = 25306;
|
||||
|
||||
PacketClient();
|
||||
|
||||
Future<void> startConnect(String IPAddress, int port) async {
|
||||
try {
|
||||
socket = await Socket.connect(IPAddress, port);
|
||||
connected = true;
|
||||
lastIP = IPAddress;
|
||||
this.port = port;
|
||||
} catch (E, stack) {
|
||||
connected = false;
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to send a packet to the connected server
|
||||
///
|
||||
/// On success, returns either, the decoded [S2CResponse], or on error a S2CResponse containing an error and a stacktrace as [StringTag]
|
||||
Future<S2CResponse> send(IPacket packet, bool shouldReconnect) async {
|
||||
if (!connected) {
|
||||
return S2CResponse();
|
||||
}
|
||||
C2SRequestPacket request = C2SRequestPacket();
|
||||
request.payload = packet;
|
||||
request.cap = packet.getChannelID();
|
||||
|
||||
socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag()));
|
||||
CompoundTag ct = CompoundTag();
|
||||
Completer<void> onCompletion = Completer();
|
||||
socket!.listen((data) async {
|
||||
CompoundTag result = await NbtIo.readFromStream(data);
|
||||
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(result, builder, 0);
|
||||
|
||||
print("Response from server: \n${builder}");
|
||||
ct.put("result", result);
|
||||
|
||||
onCompletion.complete();
|
||||
}, onError: (E, stack) {
|
||||
print("ERROR: ${E}\n${stack}");
|
||||
if (!onCompletion.isCompleted) onCompletion.complete();
|
||||
}, onDone: () {
|
||||
print("Request completed");
|
||||
});
|
||||
|
||||
await onCompletion.future;
|
||||
await close();
|
||||
|
||||
await startConnect(lastIP, port);
|
||||
S2CResponse reply = S2CResponse();
|
||||
try {
|
||||
reply.decodeTag(ct.get("result")!.asCompoundTag());
|
||||
} catch (E, stack) {
|
||||
reply.contents = CompoundTag(); // This is essentially a null response
|
||||
reply.contents.put("error", StringTag.valueOf(E.toString()));
|
||||
reply.contents.put("stacktrace", StringTag.valueOf(stack.toString()));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
await socket!.close();
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class IPacket with NbtEncodable, JsonEncodable {
|
||||
String getChannelID();
|
||||
|
||||
// This function handles the packet
|
||||
Future<PacketResponse> handleServerPacket();
|
||||
Future<void> handleClientPacket();
|
||||
NetworkDirection direction();
|
||||
}
|
||||
|
||||
class StopServerPacket extends IPacket {
|
||||
@override
|
||||
void decodeJson(String params) {}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return json.encode({});
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
return CompoundTag();
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "StopServer";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
// We're now on the server. Handle the packet with a response to the client
|
||||
PacketServer.shouldRestart = false;
|
||||
|
||||
S2CResponse response = S2CResponse();
|
||||
|
||||
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class PacketResponse {
|
||||
static final nil = PacketResponse(replyDataTag: CompoundTag());
|
||||
|
||||
PacketResponse({required this.replyDataTag});
|
||||
|
||||
CompoundTag replyDataTag = CompoundTag();
|
||||
}
|
||||
|
||||
class PacketRegistry {
|
||||
Map<String, IPacket Function()> _registry = {};
|
||||
static PacketRegistry _inst = PacketRegistry._();
|
||||
|
||||
PacketRegistry._() {
|
||||
registerDefaults();
|
||||
}
|
||||
|
||||
factory PacketRegistry() {
|
||||
return _inst;
|
||||
}
|
||||
|
||||
int get count => _registry.length;
|
||||
|
||||
void register(IPacket packet, IPacket Function() packetResolver) {
|
||||
_registry[packet.getChannelID()] = packetResolver;
|
||||
}
|
||||
|
||||
IPacket getPacket(String channel) {
|
||||
if (_registry.containsKey(channel)) {
|
||||
IPacket Function() callback = _registry[channel]!;
|
||||
return callback();
|
||||
} else
|
||||
throw Exception("No such channel has been registered");
|
||||
}
|
||||
|
||||
void registerDefaults() {
|
||||
register(S2CResponse(), () {
|
||||
return S2CResponse();
|
||||
});
|
||||
register(C2SRequestPacket(), () {
|
||||
return C2SRequestPacket();
|
||||
});
|
||||
register(StopServerPacket(), () {
|
||||
return StopServerPacket();
|
||||
});
|
||||
register(C2SPing(), () {
|
||||
return C2SPing();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum NetworkDirection { ClientToServer, ServerToClient }
|
||||
|
||||
enum PacketOperation { Encode, Decode }
|
||||
|
||||
class S2CResponse implements IPacket {
|
||||
CompoundTag contents = CompoundTag();
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ServerToClient;
|
||||
}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "Response";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
// We can't predict handling for this type, it is a data packet response with no pre-defined structure.
|
||||
return PacketResponse.nil;
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeJson(String encoded) {
|
||||
fromJson(json.decode(encoded));
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag encoded) {
|
||||
CompoundTag ct = encoded as CompoundTag;
|
||||
contents = ct.get("contents")!.asCompoundTag();
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return json.encode(toJson());
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag tag = CompoundTag();
|
||||
tag.put("contents", contents);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> params) {}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {}; // Operation is not supported at this time.
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() async {
|
||||
// We haven't got anything to process. This is structured data
|
||||
}
|
||||
}
|
||||
|
||||
class C2SRequestPacket implements IPacket {
|
||||
String cap = ""; // Packet channel
|
||||
late IPacket payload;
|
||||
|
||||
@override
|
||||
void decodeJson(String encoded) {
|
||||
fromJson(json.decode(encoded));
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag encoded) {
|
||||
CompoundTag tag = encoded.asCompoundTag();
|
||||
String cap = tag.get("cap")!.asString();
|
||||
payload = PacketRegistry().getPacket(cap);
|
||||
payload.decodeTag(tag.get("payload")!.asCompoundTag());
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return json.encode(toJson());
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag tag = CompoundTag();
|
||||
tag.put("cap", StringTag.valueOf(payload.getChannelID()));
|
||||
tag.put("payload", payload.encodeTag());
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> params) {
|
||||
String cap = params['cap'] as String;
|
||||
payload = PacketRegistry().getPacket(cap);
|
||||
payload.fromJson(params['payload']);
|
||||
}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "C2SRequest";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
// This has no internal handling
|
||||
return payload.handleServerPacket();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {"cap": payload.getChannelID(), "payload": payload.toJson()};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class C2SPing implements IPacket {
|
||||
String clientVersion = "";
|
||||
|
||||
@override
|
||||
void decodeJson(String params) {
|
||||
fromJson(json.decode(params));
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeTag(Tag tag) {
|
||||
clientVersion = tag.asCompoundTag().get("version")!.asString();
|
||||
}
|
||||
|
||||
@override
|
||||
NetworkDirection direction() {
|
||||
return NetworkDirection.ClientToServer;
|
||||
}
|
||||
|
||||
@override
|
||||
String encodeJson() {
|
||||
return json.encode(toJson());
|
||||
}
|
||||
|
||||
@override
|
||||
Tag encodeTag() {
|
||||
CompoundTag tag = CompoundTag();
|
||||
tag.put("version", StringTag.valueOf(clientVersion));
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@override
|
||||
void fromJson(Map<String, dynamic> js) {
|
||||
clientVersion = js['version'] as String;
|
||||
}
|
||||
|
||||
@override
|
||||
String getChannelID() {
|
||||
return "Ping";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PacketResponse> handleServerPacket() async {
|
||||
CompoundTag tag = CompoundTag();
|
||||
tag.put("pong", StringTag.valueOf(Platform.version));
|
||||
|
||||
S2CResponse response = S2CResponse();
|
||||
response.contents = tag;
|
||||
|
||||
PacketResponse reply =
|
||||
PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
|
||||
return reply;
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {"version": clientVersion};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleClientPacket() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
mixin JsonEncodable {
|
||||
String encodeJson();
|
||||
void decodeJson(String params);
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
void fromJson(Map<String, dynamic> js);
|
||||
}
|
||||
|
||||
mixin NbtEncodable {
|
||||
Tag encodeTag();
|
||||
void decodeTag(Tag tag);
|
||||
}
|
12
lib/utils/Converter.dart
Normal file
12
lib/utils/Converter.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class base64Encoder {
|
||||
static String encode(List<int> value) {
|
||||
return base64.encode(value);
|
||||
}
|
||||
|
||||
static Uint8List decode(String value) {
|
||||
return base64.decode(value);
|
||||
}
|
||||
}
|
35
lib/utils/Hashing.dart
Normal file
35
lib/utils/Hashing.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
class Hashing {
|
||||
static String md5Hash(String input) {
|
||||
var hash = md5.convert(utf8.encode(input)).bytes;
|
||||
String x = "";
|
||||
for (int byte in hash) {
|
||||
x += byte.toRadixString(16).padLeft(2, '0');
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static String sha1Hash(String input) {
|
||||
var hash = sha1.convert(utf8.encode(input)).bytes;
|
||||
String x = "";
|
||||
for (int byte in hash) {
|
||||
x += byte.toRadixString(16).padLeft(2, '0');
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static String sha256Hash(String input) {
|
||||
var hash = sha256.convert(utf8.encode(input)).bytes;
|
||||
String x = "";
|
||||
for (int byte in hash) {
|
||||
x += byte.toRadixString(16).padLeft(2, '0');
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
}
|
124
lib/utils/IOTools.dart
Normal file
124
lib/utils/IOTools.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class PathHelper {
|
||||
String pth = "";
|
||||
PathHelper({required this.pth});
|
||||
|
||||
static String combine(String path1, String path2) {
|
||||
return path1 + Platform.pathSeparator + path2;
|
||||
}
|
||||
|
||||
static PathHelper builder(String startPath) {
|
||||
return PathHelper(pth: startPath);
|
||||
}
|
||||
|
||||
bool exists() {
|
||||
File fi = File(build());
|
||||
Directory dir = Directory(build());
|
||||
|
||||
return fi.existsSync() || dir.existsSync();
|
||||
}
|
||||
|
||||
PathHelper clone() {
|
||||
return PathHelper.builder(build());
|
||||
}
|
||||
|
||||
PathHelper resolve(String path2) {
|
||||
pth += Platform.pathSeparator + path2;
|
||||
return this;
|
||||
}
|
||||
|
||||
PathHelper conditionalResolve(bool flag, String path) {
|
||||
if (flag) pth += Platform.pathSeparator + path;
|
||||
return this;
|
||||
}
|
||||
|
||||
bool deleteDirectory({bool recursive = false}) {
|
||||
Directory dir = new Directory(build());
|
||||
try {
|
||||
dir.deleteSync(recursive: recursive);
|
||||
|
||||
return true;
|
||||
} catch (E) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool deleteFile() {
|
||||
File file = new File(build());
|
||||
try {
|
||||
file.deleteSync(recursive: true);
|
||||
|
||||
return true;
|
||||
} catch (E) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PathHelper removeDir() {
|
||||
deleteDirectory(recursive: true);
|
||||
return this;
|
||||
}
|
||||
|
||||
PathHelper removeFile() {
|
||||
deleteFile();
|
||||
return this;
|
||||
}
|
||||
|
||||
PathHelper mkdir() {
|
||||
Directory dir = new Directory(build());
|
||||
dir.createSync(recursive: true);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
String build() {
|
||||
return pth;
|
||||
}
|
||||
}
|
||||
|
||||
Stream<List<int>> tail(final File file) async* {
|
||||
final randomAccess = await file.open(mode: FileMode.read);
|
||||
var pos = await randomAccess.position();
|
||||
var len = await randomAccess.length();
|
||||
// Increase/decrease buffer size as needed.
|
||||
var buf = Uint8List(8192);
|
||||
|
||||
Stream<Uint8List> _read() async* {
|
||||
while (pos < len) {
|
||||
try {
|
||||
final bytesRead = await randomAccess.readInto(buf);
|
||||
pos += bytesRead;
|
||||
|
||||
yield buf.sublist(0, bytesRead);
|
||||
} catch (E) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: read whole file
|
||||
yield* _read();
|
||||
|
||||
// Step 2: wait for modify events and read more bytes from file
|
||||
await for (final event in file.watch(events: FileSystemEvent.modify)) {
|
||||
if ((event as FileSystemModifyEvent).contentChanged) {
|
||||
try {
|
||||
len = await (randomAccess.length());
|
||||
yield* _read();
|
||||
} catch (E) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tailAndPrint(File file) {
|
||||
try {
|
||||
tail(file)
|
||||
.transform(utf8.decoder)
|
||||
.transform(LineSplitter())
|
||||
.forEach((line) {
|
||||
// Do something with the line that has been read, e.g. print it out...
|
||||
print(line);
|
||||
});
|
||||
} catch (E) {}
|
||||
}
|
146
lib/utils/TimeUtils.dart
Normal file
146
lib/utils/TimeUtils.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
import '../nbt/Stream.dart';
|
||||
|
||||
class Time {
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
|
||||
Time({required this.hours, required this.minutes, required this.seconds}) {
|
||||
autofix();
|
||||
}
|
||||
|
||||
int getTotalSeconds() {
|
||||
int current = 0;
|
||||
current += seconds;
|
||||
|
||||
current += (minutes * 60); // 60 seconds in a minute
|
||||
|
||||
current += (hours * 60 * 60); // 60 seconds, 60 minutes in an hour
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void add(Time time) {
|
||||
seconds += time.getTotalSeconds();
|
||||
autofix();
|
||||
}
|
||||
|
||||
void subtract(Time time) {
|
||||
int sec = getTotalSeconds();
|
||||
sec -= time.getTotalSeconds();
|
||||
|
||||
apply(sec);
|
||||
}
|
||||
|
||||
void tickDown() {
|
||||
int sec = getTotalSeconds();
|
||||
sec--;
|
||||
|
||||
apply(sec);
|
||||
}
|
||||
|
||||
void tickUp() {
|
||||
int sec = getTotalSeconds();
|
||||
sec++;
|
||||
|
||||
apply(sec);
|
||||
}
|
||||
|
||||
void apply(int seconds) {
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
this.seconds = seconds;
|
||||
if (this.seconds < 0) this.seconds = 0;
|
||||
|
||||
autofix();
|
||||
}
|
||||
|
||||
void autofix() {
|
||||
int totalSeconds = getTotalSeconds();
|
||||
if (totalSeconds < 0) totalSeconds = 0;
|
||||
|
||||
int one_hour = (1 * 60 * 60);
|
||||
int one_minute = (1 * 60);
|
||||
|
||||
int hours = (totalSeconds / 60 / 60).round();
|
||||
totalSeconds -= (hours * one_hour);
|
||||
|
||||
int minutes = (totalSeconds / 60).round();
|
||||
totalSeconds -= (minutes * one_minute);
|
||||
|
||||
int seconds = totalSeconds;
|
||||
|
||||
this.hours = hours;
|
||||
this.minutes = minutes;
|
||||
this.seconds = seconds;
|
||||
}
|
||||
|
||||
Time copy() {
|
||||
return Time(hours: hours, minutes: minutes, seconds: seconds);
|
||||
}
|
||||
|
||||
factory Time.copy(Time other) {
|
||||
return Time(
|
||||
hours: other.hours, minutes: other.minutes, seconds: other.seconds);
|
||||
}
|
||||
|
||||
factory Time.fromNotation(String notation) {
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
int seconds = 0;
|
||||
|
||||
List<String> current = [];
|
||||
String val = notation;
|
||||
if (val.indexOf('h') == -1) {
|
||||
hours = 0;
|
||||
} else {
|
||||
current = val.split('h');
|
||||
hours = int.parse(current[0]);
|
||||
|
||||
if (current.length == 2)
|
||||
val = current[1];
|
||||
else
|
||||
val = "";
|
||||
}
|
||||
|
||||
if (val.indexOf('m') == -1) {
|
||||
minutes = 0;
|
||||
} else {
|
||||
current = val.split('m');
|
||||
minutes = int.parse(current[0]);
|
||||
|
||||
if (current.length == 2)
|
||||
val = current[1];
|
||||
else
|
||||
val = "";
|
||||
}
|
||||
|
||||
if (val.indexOf('s') == -1) {
|
||||
seconds = 0;
|
||||
} else {
|
||||
current = val.split('s');
|
||||
seconds = int.parse(current[0]);
|
||||
|
||||
if (current.length == 2)
|
||||
val = current[1];
|
||||
else
|
||||
val = "";
|
||||
}
|
||||
|
||||
if (val != "") {
|
||||
seconds += int.parse(val);
|
||||
}
|
||||
|
||||
return Time(hours: hours, minutes: minutes, seconds: seconds);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
StringBuilder builder = StringBuilder();
|
||||
if (hours > 0) builder.append("${hours}h");
|
||||
if (minutes > 0) builder.append("${minutes}m");
|
||||
if (seconds > 0) builder.append("${seconds}s");
|
||||
|
||||
return "${builder}";
|
||||
}
|
||||
}
|
42
lib/utils/uuid/NbtUUID.dart
Normal file
42
lib/utils/uuid/NbtUUID.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import '../../nbt/Stream.dart';
|
||||
import 'UUID.dart';
|
||||
|
||||
class NbtUUID {
|
||||
final int MSB;
|
||||
final int LSB;
|
||||
|
||||
const NbtUUID(this.MSB, this.LSB);
|
||||
|
||||
static final NbtUUID ZERO = NbtUUID.fromUUID(UUID.ZERO);
|
||||
|
||||
factory NbtUUID.fromUUID(UUID id) {
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeBytes(id.getBytes());
|
||||
layer.resetPosition();
|
||||
|
||||
int MSB = layer.readLong();
|
||||
int LSB = layer.readLong();
|
||||
|
||||
return NbtUUID(MSB, LSB);
|
||||
}
|
||||
|
||||
/// Returns the Most Significant Bits (MSB) long value of the UUID.
|
||||
int getMostSignificantBits() => MSB;
|
||||
|
||||
/// Returns the Least Significant Bits (LSB) long value of the UUID.
|
||||
int getLeastSignificantBits() => LSB;
|
||||
|
||||
UUID toUUID() {
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeLong(MSB);
|
||||
layer.writeLong(LSB);
|
||||
layer.resetPosition();
|
||||
|
||||
return UUID(layer.readBytes(16));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toUUID().toString();
|
||||
}
|
||||
}
|
|
@ -2,23 +2,14 @@ import 'dart:convert';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
|
||||
import '../../nbt/Stream.dart';
|
||||
|
||||
class UUID {
|
||||
late final int LSB;
|
||||
late final int MSB;
|
||||
late final List<int> _bytes;
|
||||
|
||||
UUID(int msb, int lsb) {
|
||||
MSB = msb;
|
||||
LSB = lsb;
|
||||
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeLong(MSB);
|
||||
layer.writeLong(LSB);
|
||||
layer.resetPosition();
|
||||
|
||||
_bytes = layer.readBytes(16);
|
||||
UUID(List<int> bytes) {
|
||||
_bytes = bytes;
|
||||
}
|
||||
|
||||
static final UUID ZERO = UUID.generate(0);
|
||||
|
@ -27,8 +18,9 @@ class UUID {
|
|||
static bool validate(String uuid) {
|
||||
if (uuid.length == ((16 * 2) + 4)) {
|
||||
return true; // Likely is true. This is just a surface level check
|
||||
} else
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the given [uuid] string and returns a UUID object.
|
||||
|
@ -42,30 +34,26 @@ class UUID {
|
|||
final msbString = segments.sublist(0, 3).join('');
|
||||
final lsbString = segments.sublist(3, 5).join('');
|
||||
|
||||
int msb = 0;
|
||||
int lsb = 0;
|
||||
|
||||
int i = 0;
|
||||
ByteLayer layer = ByteLayer();
|
||||
for (i = 0; i < msbString.length; i += 2) {
|
||||
String hex = "${msbString.substring(i, i + 2)}";
|
||||
String hex = msbString.substring(i, i + 2);
|
||||
int byte = int.parse(hex, radix: 16);
|
||||
layer.writeByte(byte);
|
||||
}
|
||||
|
||||
for (i = 0; i < lsbString.length; i += 2) {
|
||||
String hex = "${lsbString.substring(i, i + 2)}";
|
||||
String hex = lsbString.substring(i, i + 2);
|
||||
int byte = int.parse(hex, radix: 16);
|
||||
layer.writeByte(byte);
|
||||
}
|
||||
|
||||
layer.resetPosition();
|
||||
msb = layer.readLong();
|
||||
lsb = layer.readLong();
|
||||
|
||||
return UUID(msb, lsb);
|
||||
} else
|
||||
return UUID(layer.readBytes(16));
|
||||
} else {
|
||||
return UUID.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -77,29 +65,33 @@ class UUID {
|
|||
return '${hexBuilder.substring(0, 8)}-${hexBuilder.substring(8, 12)}-${hexBuilder.substring(12, 16)}-${hexBuilder.substring(16, 20)}-${hexBuilder.substring(20, 32)}';
|
||||
}
|
||||
|
||||
/// Returns the Most Significant Bits (MSB) long value of the UUID.
|
||||
int getMostSignificantBits() => MSB;
|
||||
|
||||
/// Returns the Least Significant Bits (LSB) long value of the UUID.
|
||||
int getLeastSignificantBits() => LSB;
|
||||
|
||||
/// Factory method to generate UUID of the specific version.
|
||||
factory UUID.generate(int version, {List<Object>? parameters}) {
|
||||
List<Object> params = [];
|
||||
if (parameters == null) {
|
||||
if (version != 4) {
|
||||
if (version != 4 && version != 0) {
|
||||
return UUID.generate(4);
|
||||
}
|
||||
} else
|
||||
params = parameters!;
|
||||
} else {
|
||||
params = parameters;
|
||||
}
|
||||
switch (version) {
|
||||
case 0:
|
||||
return UUID(0, 0);
|
||||
{
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeLong(0);
|
||||
layer.writeLong(0);
|
||||
|
||||
layer.resetPosition();
|
||||
|
||||
return UUID(layer.readBytes(16));
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (params.length != 2)
|
||||
if (params.length != 2) {
|
||||
throw Exception(
|
||||
"UUID v3 requires two parameters, [namespace,name]");
|
||||
}
|
||||
String namespace = params[0] as String;
|
||||
String name = params[1] as String;
|
||||
|
||||
|
@ -118,45 +110,38 @@ class UUID {
|
|||
layer.unsetSetBit(8, 0xC0, 0x80);
|
||||
|
||||
layer.resetPosition();
|
||||
|
||||
var msb = layer.readUnsignedLong();
|
||||
var lsb = layer.readUnsignedLong();
|
||||
|
||||
layer.resetPosition();
|
||||
return UUID(msb, lsb);
|
||||
return UUID(layer.readBytes(16));
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
ByteLayer layer = ByteLayer();
|
||||
final random = Random.secure();
|
||||
|
||||
layer.writeLong(
|
||||
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
|
||||
layer.writeLong(
|
||||
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
|
||||
layer.insertRandomBytes(16);
|
||||
|
||||
layer.unsetSetBit(6, 0xF0, 0x40);
|
||||
layer.unsetSetBit(8, 0xC0, 0x80);
|
||||
|
||||
layer.resetPosition();
|
||||
|
||||
return UUID(layer.readUnsignedLong(), layer.readUnsignedLong());
|
||||
return UUID(layer.readBytes(16));
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
ByteLayer layer = ByteLayer();
|
||||
if (params.length != 2)
|
||||
if (params.length != 2) {
|
||||
throw Exception(
|
||||
"UUID v5 requires two parameters, [namespace,name]");
|
||||
}
|
||||
String namespace = params[0] as String;
|
||||
String name = params[1] as String;
|
||||
|
||||
if (!namespace.isEmpty) {
|
||||
if (namespace.isNotEmpty) {
|
||||
final namespaceBytes = utf8.encode(namespace);
|
||||
layer.writeBytes(namespaceBytes);
|
||||
}
|
||||
|
||||
if (!name.isEmpty) {
|
||||
if (name.isNotEmpty) {
|
||||
final nameBytes = utf8.encode(name);
|
||||
layer.writeBytes(nameBytes);
|
||||
}
|
||||
|
@ -170,10 +155,14 @@ class UUID {
|
|||
|
||||
layer.resetPosition();
|
||||
|
||||
return UUID(layer.readUnsignedLong(), layer.readUnsignedLong());
|
||||
return UUID(layer.readBytes(16));
|
||||
}
|
||||
default:
|
||||
throw ArgumentError('Unsupported UUID version: $version');
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<int> getBytes() {
|
||||
return _bytes.take(16);
|
||||
}
|
||||
}
|
||||
|
|
25
pubspec.yaml
25
pubspec.yaml
|
@ -1,28 +1,23 @@
|
|||
name: libac_flutter
|
||||
name: libac_dart
|
||||
description: "Aria's Creations code library"
|
||||
version: 1.0.0
|
||||
homepage:
|
||||
version: 1.0.32
|
||||
homepage: "https://zontreck.com"
|
||||
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.4 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
sdk: ^3.4.0
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
crypto: ^3.0.3
|
||||
flutter:
|
||||
sdk: flutter
|
||||
# path: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
lints: ^3.0.0
|
||||
test: ^1.24.0
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
|
|
9
test/hash_test.dart
Normal file
9
test/hash_test.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:libac_dart/utils/Hashing.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
void main() {
|
||||
test("Test md5", () {
|
||||
expect(Hashing.md5Hash("Hello World"), "b10a8db164e0754105b7a99be72e3fe5");
|
||||
});
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:libac_flutter/nbt/NbtIo.dart';
|
||||
import 'package:libac_flutter/nbt/NbtUtils.dart';
|
||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||
import 'package:libac_flutter/nbt/impl/StringTag.dart';
|
||||
import 'package:libac_flutter/utils/uuid/UUID.dart';
|
||||
import 'package:libac_dart/nbt/NbtIo.dart';
|
||||
import 'package:libac_dart/nbt/NbtUtils.dart';
|
||||
import 'package:libac_dart/nbt/SnbtIo.dart';
|
||||
import 'package:libac_dart/nbt/Stream.dart';
|
||||
import 'package:libac_dart/nbt/Tag.dart';
|
||||
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
|
||||
import 'package:libac_dart/nbt/impl/StringTag.dart';
|
||||
import 'package:libac_dart/utils/IOTools.dart';
|
||||
import 'package:libac_dart/utils/uuid/NbtUUID.dart';
|
||||
import 'package:libac_dart/utils/uuid/UUID.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
void main() {
|
||||
test('read non-compressed helloworld NBT', () async {
|
||||
|
@ -13,7 +19,7 @@ void main() {
|
|||
CompoundTag tag =
|
||||
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
|
||||
expect(tag.getKey(), "hello world");
|
||||
expect(tag.contains("name"), true);
|
||||
expect(tag.containsKey("name"), true);
|
||||
|
||||
expect(tag.get("name")!.asString(), "Bananrama");
|
||||
});
|
||||
|
@ -33,7 +39,7 @@ void main() {
|
|||
CompoundTag tag =
|
||||
await NbtIo.read("${Directory.current.path}/build/hello_world.nbt");
|
||||
expect(tag.getKey(), "hello world");
|
||||
expect(tag.contains("name"), true);
|
||||
expect(tag.containsKey("name"), true);
|
||||
|
||||
expect(tag.get("name")!.asString(), "Bananrama");
|
||||
});
|
||||
|
@ -50,9 +56,56 @@ void main() {
|
|||
test("Generate a UUID v4, save to NBT, and read it back again", () async {
|
||||
var id = UUID.generate(4);
|
||||
CompoundTag tag = CompoundTag();
|
||||
NbtUtils.writeUUID(tag, "test", id);
|
||||
NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(id));
|
||||
|
||||
var newID = NbtUtils.readUUID(tag, "test");
|
||||
expect(id.toString(), newID.toString());
|
||||
});
|
||||
|
||||
test("Read HelloWorld, Output to SNBT", () async {
|
||||
CompoundTag ct =
|
||||
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
|
||||
StringBuilder sb = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(ct, sb, 0);
|
||||
print(sb.toString());
|
||||
});
|
||||
|
||||
test("Read BigTest, Output to SNBT", () async {
|
||||
CompoundTag ct =
|
||||
await NbtIo.read("${Directory.current.path}/test/bigtest.nbt");
|
||||
StringBuilder sb = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(ct, sb, 0);
|
||||
print(sb.toString());
|
||||
});
|
||||
|
||||
test("Write BigTest to SNBT file", () async {
|
||||
CompoundTag ct =
|
||||
await NbtIo.read("${Directory.current.path}/test/bigtest.nbt");
|
||||
String output = "${Directory.current.path}/build/bigtest.snbt";
|
||||
File file = File(output);
|
||||
SnbtIo.writeToFile(output, ct);
|
||||
|
||||
// Expect that the file exists
|
||||
PathHelper ph = PathHelper.builder(Directory.current.path)
|
||||
.resolve("build")
|
||||
.resolve("bigtest.snbt");
|
||||
expect(ph.exists(), true);
|
||||
});
|
||||
|
||||
test("Read BigTest from SNBT file", () async {
|
||||
CompoundTag tag = await SnbtIo.readFromFile(
|
||||
"${Directory.current.path}/build/bigtest.snbt") as CompoundTag;
|
||||
|
||||
expect(tag.containsKey("stringTest"), true);
|
||||
expect(tag.get("doubleTest")!.asDouble(), 0.4931287132182315);
|
||||
});
|
||||
|
||||
test("Write NULL UUID to NBT", () async {
|
||||
CompoundTag tag = CompoundTag();
|
||||
NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(UUID.ZERO));
|
||||
NbtUUID ID = NbtUtils.readUUID(tag, "test");
|
||||
|
||||
expect(ID.MSB, 0);
|
||||
expect(ID.LSB, 0);
|
||||
});
|
||||
}
|
||||
|
|
24
test/time_test.dart
Normal file
24
test/time_test.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:libac_dart/utils/TimeUtils.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
void main() {
|
||||
test("Parse time notation", () {
|
||||
Time time = Time.fromNotation("4h9s");
|
||||
|
||||
expect(time.toString(), "4h9s");
|
||||
expect(time.hours, 4);
|
||||
expect(time.minutes, 0);
|
||||
expect(time.seconds, 9);
|
||||
});
|
||||
|
||||
test("Add time", () {
|
||||
// Depends on first test working!
|
||||
Time time = Time.fromNotation("4h9s");
|
||||
time.add(Time(hours: 0, minutes: 80, seconds: 1));
|
||||
|
||||
expect(time.hours, 5);
|
||||
expect(time.minutes, 20);
|
||||
expect(time.seconds, 10);
|
||||
});
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:libac_flutter/nbt/Stream.dart';
|
||||
import 'package:libac_flutter/utils/uuid/UUID.dart';
|
||||
import 'package:libac_dart/nbt/Stream.dart';
|
||||
import 'package:libac_dart/utils/uuid/UUID.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test/scaffolding.dart';
|
||||
|
||||
void main() {
|
||||
test("Store a 64bit value in int", () {
|
||||
|
@ -20,15 +21,15 @@ void main() {
|
|||
}
|
||||
|
||||
for (UUID sID in ID) {
|
||||
print("ID : ${sID}}");
|
||||
print("ID : $sID}");
|
||||
}
|
||||
});
|
||||
|
||||
test("Check UUIDv4 for validity", () {
|
||||
var ID = UUID.generate(4);
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeLong(ID.getMostSignificantBits().toInt());
|
||||
layer.writeLong(ID.getLeastSignificantBits().toInt());
|
||||
//layer.writeLong(ID.getMostSignificantBits().toInt());
|
||||
//layer.writeLong(ID.getLeastSignificantBits().toInt());
|
||||
|
||||
print(
|
||||
"Checking version bit: ${layer.checkBit(6, 0x40)} - ${layer.getBit(6)}");
|
||||
|
@ -38,8 +39,8 @@ void main() {
|
|||
test("Generate and check a UUIDv3", () {
|
||||
var ID3 = UUID.generate(3, parameters: ["Test", "Test2"]);
|
||||
ByteLayer layer = ByteLayer();
|
||||
layer.writeLong(ID3.getMostSignificantBits().toInt());
|
||||
layer.writeLong(ID3.getLeastSignificantBits().toInt());
|
||||
//layer.writeLong(ID3.getMostSignificantBits().toInt());
|
||||
//layer.writeLong(ID3.getLeastSignificantBits().toInt());
|
||||
|
||||
print(
|
||||
"Checking version bit: ${layer.checkBit(6, 0x30)} - ${layer.getBit(6)}");
|
||||
|
@ -70,9 +71,16 @@ void main() {
|
|||
var ID3 = UUID.parse(asString);
|
||||
var ID3X = UUID.generate(3, parameters: ["OfflinePlayer:zontreck", ""]);
|
||||
|
||||
expect(ID3.MSB, ID3X.MSB);
|
||||
expect(ID3.LSB, ID3X.LSB);
|
||||
//expect(ID3.MSB, ID3X.MSB);
|
||||
//expect(ID3.LSB, ID3X.LSB);
|
||||
|
||||
expect(ID3.toString(), asString);
|
||||
});
|
||||
|
||||
test("Null UUID", () {
|
||||
var expected = "00000000-0000-0000-0000-000000000000";
|
||||
var actual = UUID.ZERO.toString();
|
||||
|
||||
expect(actual, expected);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue