import 'dart:core'; 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 { End(0), Byte(1), Short(2), Int(3), Long(4), Float(5), Double(6), ByteArray(7), String(8), List(9), Compound(10), IntArray(11), LongArray(12); final int byte; const TagType(this.byte); static TagType get(int byte) { for (TagType type in values) { if (type.byte == byte) { return type; } } return TagType.End; } static const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static TagType getStringifiedTagType(StringReader reader) { reader.startSeek(); // Enter fake read mode TagType ret = TagType.End; bool isQuoted = false; bool isNumeric = true; // Assume true until proven otherwise bool isAlpha = true; // Assume true until proven otherwise bool hasDecimalPoint = false; StringBuffer buffer = StringBuffer(); while (reader.canRead) { var val = reader.peek(); // Peek at the next character without consuming it if (val == ',' || val == '\n' || val == "]" || val == "}") { break; // Stop at comma or newline } if (val == '"') { reader.next(); // Consume the quote character isQuoted = true; // Toggle quoted state break; } if (val == "'") { reader.next(); isQuoted = true; break; } if (val == "-" || val == "+") { reader.next(); } if (val == '{') { ret = TagType.Compound; // Detected a CompoundTag reader.next(); // Consume '{' reader.endSeek(); // Restore the original stream position return ret; } if (val == '[') { reader.next(); // Consume '[' // Peek ahead to differentiate between List and Array var prefix = reader.readUntil(";", 2).toUpperCase(); switch (prefix) { case "B": ret = TagType.ByteArray; break; case "I": ret = TagType.IntArray; break; case "L": ret = TagType.LongArray; break; default: ret = TagType.List; // No type prefix, it's a List break; } reader.endSeek(); // Restore the original stream position return ret; } // Adjusting numeric and alphabetic checks var current = reader.next(); // Consume the character buffer.write(current); // Updated check to allow digits, decimal points, and numeric suffixes if (!RegExp(r'^[0-9]$').hasMatch(current)) { if (current == '.' && !hasDecimalPoint) { hasDecimalPoint = true; // Allow only one decimal point } else if (!RegExp(r'^[sSbBiIlLfFdD]$').hasMatch(current)) { isNumeric = false; // Not purely numeric or allowed decimal/suffix } } // Check if current character is purely alphabetical if (!RegExp(r'^[A-Za-z]$').hasMatch(current)) { isAlpha = false; // Not purely alphabetical } } var input = buffer.toString().trim(); reader.endSeek(); // Restore the original stream position if (input.isEmpty) { return TagType.String; // No input detected } if (isQuoted) { return TagType.String; // Quoted string } if (isNumeric) { // Check the last character for type indicator (only for numeric input) var lastChar = input.substring(input.length - 1).toUpperCase(); switch (lastChar) { case 'S': return TagType.Short; case 'B': return TagType.Byte; case 'I': return TagType.Int; case 'F': return TagType.Float; case 'D': return TagType.Double; case 'L': return TagType.Long; default: return TagType .Int; // Default to Int if purely numeric with no specific suffix } } else if (isAlpha && !input.contains(' ')) { return TagType.String; // Unquoted purely alphabetical string } else { return TagType.String; // Unquoted string with mixed content or spaces } } } abstract class Tag { int getType() { return getTagType().byte; } TagType getTagType(); TagType _parentTagType = TagType.End; Tag? _parentTag; bool get hasParent => _parentTag != null; void updateParent(Tag? tag) { if (tag == null) { _parentTag = null; setParentTagType(TagType.End); } else { _parentTag = tag; setParentTagType(tag.getTagType()); } } Tag? get getParent => _parentTag; TagType get parentTagType => _parentTagType; void setParentTagType(TagType type) { _parentTagType = type; } void writeValue(ByteLayer data); void readValue(ByteLayer data); String? _key; String getKey() { return (_key == null ? "" : _key!); } void setKey(String key) { _key = key; } dynamic getValue(); void setValue(dynamic val); static Tag readNamedTag(ByteLayer data) { var type = data.readByte(); if (type == 0) { return EndTag(); } else { Tag tag = makeTagOfType(TagType.get(type)); tag._key = data.readString(); tag.readValue(data); return tag; } } static void writeNamedTag(Tag tag, ByteLayer data) { data.writeByte(tag.getType()); if (tag.getType() != 0) { data.writeString(tag.getKey()); tag.writeValue(data); } } 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; try { tag.readStringifiedValue(string); } catch (E, stack) { print(E); print(string.getSnapshot()); print(stack); } return tag; } bool shouldQuoteName() { if (getKey() == "") { return false; } else { String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < getKey().length; i++) { String digit = getKey().substring(i, i + 1); if (letters.indexOf(digit) == -1) { return true; } } return false; } } bool shouldQuote(String value) { if (value == "") { return true; } else { String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < value.length; i++) { String digit = value.substring(i, i + 1); if (letters.indexOf(digit) == -1) { return true; } } return false; } } bool shouldUseSingleQuotes(String value) { return value.contains("\""); } bool shouldEscapeSingleQuotes(String value) { return value.contains("'"); } void writeStringifiedValue(StringBuilder builder, int indent, bool isList); void readStringifiedValue(StringReader reader); bool equals(dynamic object) { if (object == null || object is! Tag) return false; Tag tag = object; if (tag.getType() != getType()) return false; if (!(getKey() == tag.getKey())) return false; return true; } static Tag makeTagOfType(TagType type) { switch (type) { case TagType.Byte: { return ByteTag(); } case TagType.ByteArray: { return ByteArrayTag(); } case TagType.Compound: { return CompoundTag(); } case TagType.Double: { return DoubleTag(); } case TagType.End: { return EndTag(); } case TagType.Short: { return ShortTag(); } case TagType.Int: { return IntTag(); } case TagType.Long: { return LongTag(); } case TagType.Float: { return FloatTag(); } case TagType.IntArray: { return IntArrayTag(); } case TagType.LongArray: { return LongArrayTag(); } case TagType.List: { return ListTag(); } case TagType.String: { return StringTag(); } } } int asByte() { if (this is ByteTag) { return (this as ByteTag).value; } else { return 0; } } List asByteArray() { if (this is ByteArrayTag) { return (this as ByteArrayTag).value; } else { return []; } } double asDouble() { if (this is DoubleTag) { return (this as DoubleTag).value; } else { return 0.0; } } double asFloat() { if (this is FloatTag) { return (this as FloatTag).value; } else { return 0.0; } } List asIntArray() { if (this is IntArrayTag) { return (this as IntArrayTag).value; } else { return []; } } int asInt() { if (this is IntTag) { return (this as IntTag).value; } else { return 0; } } List asLongArray() { if (this is LongArrayTag) { return (this as LongArrayTag).value; } else { return []; } } int asLong() { if (this is LongTag) { return (this as LongTag).value; } else { return 0; } } int asShort() { if (this is ShortTag) { return (this as ShortTag).value; } else { return 0; } } String asString() { if (this is StringTag) { return (this as StringTag).value; } else { return ""; } } CompoundTag asCompoundTag() { if (this is CompoundTag) { return this as CompoundTag; } else return CompoundTag(); } void prettyPrint(int indent, bool recurse); static String getCanonicalName(TagType type) { switch (type) { case TagType.String: return "TAG_String"; case TagType.List: return "TAG_List"; case TagType.LongArray: return "TAG_LongArray"; case TagType.Long: return "TAG_Long"; case TagType.IntArray: return "TAG_IntArray"; case TagType.Int: return "TAG_Int"; case TagType.Compound: return "TAG_Compound"; case TagType.ByteArray: return "TAG_ByteArray"; case TagType.Byte: return "TAG_Byte"; case TagType.Double: return "TAG_Double"; case TagType.Float: return "TAG_Float"; case TagType.Short: return "TAG_Short"; case TagType.End: return "TAG_End"; } } } class NBTAccountant { static int _prettyIndex = 0; static void printRead(Tag tag) { tag.prettyPrint(_prettyIndex, false); } static void visitTag() { _prettyIndex++; } static void leaveTag(Tag tag) { if (tag is CompoundTag || tag is ListTag) { if (tag is CompoundTag) { CompoundTag ct = tag; ct.endPrettyPrint(_prettyIndex); } else if (tag is ListTag) { ListTag lt = tag; lt.endPrettyPrint(_prettyIndex); } } _prettyIndex--; } }