From 04c12f8409d8778d7405a20bf746f959e38429be Mon Sep 17 00:00:00 2001 From: Bixilon Date: Fri, 25 Dec 2020 18:13:11 +0100 Subject: [PATCH] cssc: more parsers (nbt and chat component), fixes --- .../minosoft/data/commands/CommandNode.java | 1 - .../data/commands/CommandStringReader.java | 159 +++++++++++++++++- .../data/commands/parser/BlockStateParser.kt | 19 ++- .../data/commands/parser/CommandParsers.kt | 12 +- .../data/commands/parser/ComponentParser.kt | 37 ++++ .../data/commands/parser/CoordinateParser.kt | 2 +- .../data/commands/parser/EntityParser.java | 8 +- .../data/commands/parser/ItemStackParser.kt | 10 +- .../data/commands/parser/NBTParser.kt | 47 ++++++ .../data/commands/parser/ObjectiveParser.kt | 7 +- ...alidBlockPredicateCommandParseException.kt | 25 +++ .../InvalidComponentCommandParseException.kt | 25 +++ ...validItemPredicateCommandParseException.kt | 25 +++ .../InvalidJSONCommandParseException.kt | 25 +++ .../UnknownCommandParseException.java | 2 +- ...mpoundTagBadFormatCommandParseException.kt | 22 +++ ...pectedPrimitiveTagCommandParseException.kt | 22 +++ .../ListTagBadFormatCommandParseException.kt | 22 +++ .../minosoft/data/mappings/ModIdentifier.java | 5 +- .../main/dialogs/OfflineLoginController.java | 4 +- .../play/PacketDeclareCommands.java | 21 --- .../java/de/bixilon/minosoft/util/Util.java | 29 +++- .../minosoft/util/nbt/tag/ListTag.java | 51 ++++-- 23 files changed, 498 insertions(+), 82 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/ComponentParser.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/NBTParser.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidBlockPredicateCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidComponentCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidItemPredicateCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidJSONCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/CompoundTagBadFormatCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ExpectedPrimitiveTagCommandParseException.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ListTagBadFormatCommandParseException.kt diff --git a/src/main/java/de/bixilon/minosoft/data/commands/CommandNode.java b/src/main/java/de/bixilon/minosoft/data/commands/CommandNode.java index b833e6ad9..b98402a32 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/CommandNode.java +++ b/src/main/java/de/bixilon/minosoft/data/commands/CommandNode.java @@ -86,7 +86,6 @@ public abstract class CommandNode { } String nextArgument = stringReader.readUnquotedString(); if (this.literalChildren.containsKey(nextArgument)) { - stringReader.skip(nextArgument.length()); this.literalChildren.get(nextArgument).isSyntaxCorrect(connection, stringReader); return; } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/CommandStringReader.java b/src/main/java/de/bixilon/minosoft/data/commands/CommandStringReader.java index 185020222..0015f63b6 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/CommandStringReader.java +++ b/src/main/java/de/bixilon/minosoft/data/commands/CommandStringReader.java @@ -13,9 +13,17 @@ package de.bixilon.minosoft.data.commands; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.stream.JsonReader; import de.bixilon.minosoft.data.commands.parser.exceptions.BooleanCommandParseException; +import de.bixilon.minosoft.data.commands.parser.exceptions.InvalidJSONCommandParseException; import de.bixilon.minosoft.data.commands.parser.exceptions.StringCommandParseException; import de.bixilon.minosoft.data.commands.parser.exceptions.identifier.InvalidIdentifierCommandParseException; +import de.bixilon.minosoft.data.commands.parser.exceptions.nbt.CompoundTagBadFormatCommandParseException; +import de.bixilon.minosoft.data.commands.parser.exceptions.nbt.ListTagBadFormatCommandParseException; import de.bixilon.minosoft.data.commands.parser.exceptions.number.DoubleCommandParseException; import de.bixilon.minosoft.data.commands.parser.exceptions.number.FloatCommandParseException; import de.bixilon.minosoft.data.commands.parser.exceptions.number.IntegerCommandParseException; @@ -24,13 +32,25 @@ import de.bixilon.minosoft.data.commands.parser.exceptions.properties.BadPropert import de.bixilon.minosoft.data.commands.parser.exceptions.properties.DuplicatedPropertyKeyCommandParseException; import de.bixilon.minosoft.data.mappings.ModIdentifier; import de.bixilon.minosoft.util.Pair; -import de.bixilon.minosoft.util.nbt.tag.CompoundTag; +import de.bixilon.minosoft.util.Util; +import de.bixilon.minosoft.util.nbt.tag.*; import org.jetbrains.annotations.NotNull; +import java.io.StringReader; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.regex.Pattern; public class CommandStringReader { + private static final Pattern NBT_PATTERN_INT = Pattern.compile("[-+]?(0|[1-9][0-9]*)"); + private static final Pattern NBT_PATTERN_BYTE = Pattern.compile(NBT_PATTERN_INT.pattern() + "b"); + private static final Pattern NBT_PATTERN_SHORT = Pattern.compile(NBT_PATTERN_INT.pattern() + "s"); + private static final Pattern NBT_PATTERN_LONG = Pattern.compile(NBT_PATTERN_INT.pattern() + "l"); + private static final Pattern NBT_PATTERN_FLOAT = Pattern.compile(NBT_PATTERN_INT.pattern() + "([.][0-9]*)?f"); + private static final Pattern NBT_PATTERN_DOUBLE = Pattern.compile(NBT_PATTERN_INT.pattern() + "([.][0-9]*)?d?"); + private static final Gson GSON = new Gson(); + private final String string; private int cursor; @@ -88,11 +108,15 @@ public class CommandStringReader { } public void skip(int length) { - this.cursor += length; + int nextLength = this.cursor + length; + if (nextLength > this.string.length() || nextLength < 0) { + throw new IllegalStateException("Nothing to skip!"); + } + this.cursor = nextLength; } public void skip() { - this.cursor++; + skip(1); } /** @@ -148,7 +172,6 @@ public class CommandStringReader { @NotNull public Pair readProperty() throws StringCommandParseException, BadPropertyMapCommandParseException { skipWhitespaces(); - StringBuilder builder = new StringBuilder(); String key = readString(); skipWhitespaces(); if (read() != '=') { @@ -162,11 +185,12 @@ public class CommandStringReader { @NotNull public Map readProperties() throws StringCommandParseException, DuplicatedPropertyKeyCommandParseException, BadPropertyMapCommandParseException { - Map ret = new HashMap<>(); if (peek() != '[') { throw new BadPropertyMapCommandParseException(this, String.valueOf(peek()), "Not a property map!"); } + Map ret = new HashMap<>(); skip(); + skipWhitespaces(); if (peek() == ']') { return ret; } @@ -338,8 +362,124 @@ public class CommandStringReader { return readStringUntil(true, terminator); } - public CompoundTag readNBTCompoundTag() { - throw new IllegalArgumentException("TODO"); + public ListTag readNBTListTag() throws ListTagBadFormatCommandParseException, StringCommandParseException, CompoundTagBadFormatCommandParseException { + skipWhitespaces(); + if (peek() != '[') { + throw new ListTagBadFormatCommandParseException(this, String.valueOf(peek()), "Not a list tag!"); + } + skip(); + ListTag listTag = new ListTag(); + skipWhitespaces(); + if (peek() == ']') { + skip(); + return listTag; + } + while (canRead()) { + int start = getCursor(); + try { + listTag.addTag(readNBTTag()); + } catch (IllegalArgumentException exception) { + throw new ListTagBadFormatCommandParseException(this, this.string.substring(start, getCursor()), exception); + } + skipWhitespaces(); + if (peek() == ',') { + skip(); + skipWhitespaces(); + } + if (peek() == ']') { + skip(); + return listTag; + } + } + throw new ListTagBadFormatCommandParseException(this, String.valueOf(peek()), "No closing tag!"); + } + + public NBTTag readNBTTag() throws StringCommandParseException, ListTagBadFormatCommandParseException, CompoundTagBadFormatCommandParseException { + // ToDo: Array tags + skipWhitespaces(); + if (peek() == '[') { + return readNBTListTag(); + } + if (peek() == '{') { + return readNBTCompoundTag(); + } + String data = readString().toLowerCase(Locale.ROOT); + if (data.equals("true")) { + return new ByteTag(true); + } + if (data.equals("false")) { + return new ByteTag(false); + } + try { + if (NBT_PATTERN_BYTE.matcher(data).matches()) { + return new ByteTag(Byte.parseByte(data.substring(0, data.length() - 1))); + } + if (NBT_PATTERN_SHORT.matcher(data).matches()) { + return new ShortTag(Short.parseShort(data.substring(0, data.length() - 1))); + } + if (NBT_PATTERN_LONG.matcher(data).matches()) { + return new LongTag(Long.parseLong(data.substring(0, data.length() - 1))); + } + if (NBT_PATTERN_INT.matcher(data).matches()) { + return new IntTag(Integer.parseInt(data)); + } + if (NBT_PATTERN_FLOAT.matcher(data).matches()) { + return new FloatTag(Float.parseFloat(data.substring(0, data.length() - 1))); + } + if (NBT_PATTERN_DOUBLE.matcher(data).matches()) { + return new DoubleTag(Double.parseDouble(data.endsWith("d") ? data.substring(0, data.length() - 1) : data)); + } + } catch (NumberFormatException ignored) { + } + + return new StringTag(data); + } + + public CompoundTag readNBTCompoundTag() throws CompoundTagBadFormatCommandParseException, StringCommandParseException, ListTagBadFormatCommandParseException { + if (peek() != '{') { + throw new CompoundTagBadFormatCommandParseException(this, String.valueOf(peek()), "Not a compound tag!"); + } + CompoundTag compoundTag = new CompoundTag(); + skip(); + skipWhitespaces(); + if (peek() == '}') { + return compoundTag; + } + while (canRead()) { + String key = readString(); + skipWhitespaces(); + if (peek() != ':') { + throw new CompoundTagBadFormatCommandParseException(this, String.valueOf(peek()), "Invalid map char!"); + } + skip(); + NBTTag value = readNBTTag(); + compoundTag.writeTag(key, value); + skipWhitespaces(); + char nextChar = read(); + if (nextChar == '}') { + return compoundTag; + } else { + skipWhitespaces(); + if (nextChar != ',') { + throw new CompoundTagBadFormatCommandParseException(this, String.valueOf(nextChar), "Invalid map char!"); + } + } + } + throw new CompoundTagBadFormatCommandParseException(this, String.valueOf(peek()), "No closing tag!"); + } + + + public JsonElement readJson() throws InvalidJSONCommandParseException { + try { + JsonReader jsonReader = new JsonReader(new StringReader(getRemaining())); + jsonReader.setLenient(false); + JsonObject json = GSON.fromJson(jsonReader, JsonObject.class); + + skip(Util.getJsonReaderPosition(jsonReader) - 1); + return json; + } catch (JsonParseException exception) { + throw new InvalidJSONCommandParseException(this, String.valueOf(peek()), exception); + } } public boolean readExpected(char... expected) { @@ -367,6 +507,9 @@ public class CommandStringReader { @Override public String toString() { - return String.format("position=%d/%d: \"%s\"", this.cursor, this.string.length(), peekRemaining()); + if (canRead()) { + return String.format("position=%d/%d: \"%s\"", this.cursor, this.string.length(), peekRemaining()); + } + return String.format("position=%d/%d", this.cursor, this.string.length()); } } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/BlockStateParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/BlockStateParser.kt index 02cfe2715..850786a94 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/BlockStateParser.kt +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/BlockStateParser.kt @@ -13,10 +13,7 @@ package de.bixilon.minosoft.data.commands.parser import de.bixilon.minosoft.data.commands.CommandStringReader -import de.bixilon.minosoft.data.commands.parser.exceptions.BlockNotFoundCommandParseException -import de.bixilon.minosoft.data.commands.parser.exceptions.BlockPropertyDuplicatedCommandParseException -import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException -import de.bixilon.minosoft.data.commands.parser.exceptions.UnknownBlockPropertyCommandParseException +import de.bixilon.minosoft.data.commands.parser.exceptions.* import de.bixilon.minosoft.data.commands.parser.properties.ParserProperties import de.bixilon.minosoft.data.mappings.blocks.BlockProperties import de.bixilon.minosoft.data.mappings.blocks.BlockRotations @@ -26,7 +23,13 @@ class BlockStateParser : CommandParser() { @Throws(CommandParseException::class) override fun isParsable(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader) { - val identifier = stringReader.readModIdentifier() + if (this == BLOCK_PREDICATE_PARSER) { + if (stringReader.peek() != '#') { + throw InvalidBlockPredicateCommandParseException(stringReader, stringReader.read().toString()) + } + stringReader.skip() + } + val identifier = stringReader.readModIdentifier() // ToDo: check tags if (!connection.mapping.doesBlockExist(identifier.value)) { throw BlockNotFoundCommandParseException(stringReader, identifier.key) } @@ -51,11 +54,17 @@ class BlockStateParser : CommandParser() { val blockProperty = blockPropertyKey[pair.value] ?: throw UnknownBlockPropertyCommandParseException(stringReader, pair.value) allProperties.add(blockProperty) } + } + if (this == BLOCK_PREDICATE_PARSER) { + if (stringReader.canRead() && stringReader.peek() == '{') { + stringReader.readNBTCompoundTag() + } } } companion object { val BLOCK_STACK_PARSER = BlockStateParser() + val BLOCK_PREDICATE_PARSER = BlockStateParser() } } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/CommandParsers.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/CommandParsers.kt index bcfc8847c..e84ef5463 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/CommandParsers.kt +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/CommandParsers.kt @@ -33,13 +33,13 @@ object CommandParsers { ModIdentifier("vec3") to Vec3Parser.VEC3_PARSER, ModIdentifier("vec2") to Vec2Parser.VEC2_PARSER, ModIdentifier("block_state") to BlockStateParser.BLOCK_STACK_PARSER, - // block_predicate + ModIdentifier("block_predicate") to BlockStateParser.BLOCK_PREDICATE_PARSER, ModIdentifier("item_stack") to ItemStackParser.ITEM_STACK_PARSER, - // item_predicate + ModIdentifier("item_predicate") to ItemStackParser.ITEM_PREDICATE_PARSER, ModIdentifier("color") to ColorParser.COLOR_PARSER, - // chat component + ModIdentifier("component") to ComponentParser.COMPONENT_PARSER, ModIdentifier("message") to MessageParser.MESSAGE_PARSER, - // nbt + ModIdentifier("nbt") to NBTParser.NBT_PARSER, // nbt_path ModIdentifier("objective") to ObjectiveParser.OBJECTIVE_PARSER, // objective_criteria @@ -62,8 +62,8 @@ object CommandParsers { ModIdentifier("entity_summon") to IdentifierListParser.SUMMONABLE_ENTITY_PARSER, ModIdentifier("dimension") to IdentifierListParser.DIMENSION_EFFECT_PARSER, ModIdentifier("uuid") to UUIDParser.UUID_PARSER, - // nbt_tag - // nbt_compound_tag + ModIdentifier("nbt_tag") to NBTParser.NBT_TAG_PARSER, + ModIdentifier("nbt_compound_tag") to NBTParser.NBT_COMPOUND_PARSER, ModIdentifier("time") to TimeParser.TIME_PARSER ) ) diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/ComponentParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/ComponentParser.kt new file mode 100644 index 000000000..c5ec13816 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/ComponentParser.kt @@ -0,0 +1,37 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser + +import de.bixilon.minosoft.data.commands.CommandStringReader +import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException +import de.bixilon.minosoft.data.commands.parser.exceptions.InvalidComponentCommandParseException +import de.bixilon.minosoft.data.commands.parser.properties.ParserProperties +import de.bixilon.minosoft.data.text.BaseComponent +import de.bixilon.minosoft.protocol.network.Connection + +class ComponentParser : CommandParser() { + + @Throws(CommandParseException::class) + override fun isParsable(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader) { + try { + BaseComponent(stringReader.readJson().asJsonObject) + } catch (exception: Exception) { + stringReader.skip(-1) + throw InvalidComponentCommandParseException(stringReader, stringReader.read().toString(), exception) + } + } + + companion object { + val COMPONENT_PARSER = ComponentParser() + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/CoordinateParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/CoordinateParser.kt index be1fce385..65784099f 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/CoordinateParser.kt +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/CoordinateParser.kt @@ -32,7 +32,7 @@ abstract class CoordinateParser : CommandParser() { if (notation != CoordinateNotations.NONE) { stringReader.skip() } - if (!stringReader.canRead() || stringReader.peek() != ' ') { + if (!stringReader.canRead() || stringReader.peek() == ' ') { return notation } if (allowDecimal) { diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/EntityParser.java b/src/main/java/de/bixilon/minosoft/data/commands/parser/EntityParser.java index 1acb97aa5..1fa903f6c 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/EntityParser.java +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/EntityParser.java @@ -92,12 +92,10 @@ public class EntityParser extends CommandParser { } } String value = stringReader.readUnquotedString(); - try { - Util.doesStringEqualsRegex(value, ProtocolDefinition.MINECRAFT_NAME_VALIDATOR); - return; - } catch (IllegalArgumentException ignored) { - } + if (ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(value).matches()) { + return; + } if (entityParserProperties.isOnlyPlayers()) { throw new PlayerOnlyEntityCommandParseException(stringReader, value); } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/ItemStackParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/ItemStackParser.kt index caeb13d19..81cbc10af 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/ItemStackParser.kt +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/ItemStackParser.kt @@ -14,6 +14,7 @@ package de.bixilon.minosoft.data.commands.parser import de.bixilon.minosoft.data.commands.CommandStringReader import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException +import de.bixilon.minosoft.data.commands.parser.exceptions.InvalidItemPredicateCommandParseException import de.bixilon.minosoft.data.commands.parser.exceptions.identifier.ItemNotFoundCommandParseException import de.bixilon.minosoft.data.commands.parser.properties.ParserProperties import de.bixilon.minosoft.protocol.network.Connection @@ -22,7 +23,13 @@ class ItemStackParser : CommandParser() { @Throws(CommandParseException::class) override fun isParsable(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader) { - val argument = stringReader.readModIdentifier() + if (this == ITEM_PREDICATE_PARSER) { + if (stringReader.peek() != '#') { + throw InvalidItemPredicateCommandParseException(stringReader, stringReader.read().toString()) + } + stringReader.skip() + } + val argument = stringReader.readModIdentifier() // ToDo: Check predicates if (!connection.mapping.doesItemExist(argument.value)) { throw ItemNotFoundCommandParseException(stringReader, argument.key) } @@ -33,5 +40,6 @@ class ItemStackParser : CommandParser() { companion object { val ITEM_STACK_PARSER = ItemStackParser() + val ITEM_PREDICATE_PARSER = ItemStackParser() } } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/NBTParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/NBTParser.kt new file mode 100644 index 000000000..eae6f86ca --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/NBTParser.kt @@ -0,0 +1,47 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser + +import de.bixilon.minosoft.data.commands.CommandStringReader +import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException +import de.bixilon.minosoft.data.commands.parser.exceptions.nbt.ExpectedPrimitiveTagCommandParseException +import de.bixilon.minosoft.data.commands.parser.properties.ParserProperties +import de.bixilon.minosoft.protocol.network.Connection +import de.bixilon.minosoft.util.nbt.tag.CompoundTag + +class NBTParser : CommandParser() { + + @Throws(CommandParseException::class) + override fun isParsable(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader) { + when (this) { + NBT_PARSER -> { + stringReader.readNBTTag() + } + NBT_TAG_PARSER -> { + val startPos = stringReader.cursor + val tag = stringReader.readNBTTag() + if (tag!! is CompoundTag) { + throw ExpectedPrimitiveTagCommandParseException(stringReader, stringReader.string.substring(startPos, stringReader.cursor), "Compound Tag is invalid here!") + } + } + NBT_COMPOUND_PARSER -> stringReader.readNBTCompoundTag() + } + + } + + companion object { + val NBT_PARSER = NBTParser() + val NBT_TAG_PARSER = NBTParser() + val NBT_COMPOUND_PARSER = NBTParser() + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/ObjectiveParser.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/ObjectiveParser.kt index 2dbe4aea6..7281381a0 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/ObjectiveParser.kt +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/ObjectiveParser.kt @@ -18,17 +18,14 @@ import de.bixilon.minosoft.data.commands.parser.exceptions.identifier.InvalidIde import de.bixilon.minosoft.data.commands.parser.properties.ParserProperties import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -import de.bixilon.minosoft.util.Util class ObjectiveParser : CommandParser() { @Throws(CommandParseException::class) override fun isParsable(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader) { val argument = stringReader.readUnquotedString() - try { - Util.doesStringEqualsRegex(argument, ProtocolDefinition.SCOREBOARD_OBJECTIVE_PATTERN) - } catch (exception: IllegalArgumentException) { - throw InvalidIdentifierCommandParseException(stringReader, argument, exception) + if (!ProtocolDefinition.SCOREBOARD_OBJECTIVE_PATTERN.matcher(argument).matches()) { + throw InvalidIdentifierCommandParseException(stringReader, argument) } } diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidBlockPredicateCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidBlockPredicateCommandParseException.kt new file mode 100644 index 000000000..a1f735f1b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidBlockPredicateCommandParseException.kt @@ -0,0 +1,25 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions + +import de.bixilon.minosoft.data.commands.CommandStringReader + +class InvalidBlockPredicateCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String) : super(ERROR_MESSAGE, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable) : super(ERROR_MESSAGE, command, currentArgument, cause) + + companion object { + private const val ERROR_MESSAGE = "Invalid block predicate!" + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidComponentCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidComponentCommandParseException.kt new file mode 100644 index 000000000..f10710787 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidComponentCommandParseException.kt @@ -0,0 +1,25 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions + +import de.bixilon.minosoft.data.commands.CommandStringReader + +class InvalidComponentCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String) : super(ERROR_MESSAGE, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable) : super(ERROR_MESSAGE, command, currentArgument, cause) + + companion object { + private const val ERROR_MESSAGE = "Malformed component!" + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidItemPredicateCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidItemPredicateCommandParseException.kt new file mode 100644 index 000000000..d8e4f2505 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidItemPredicateCommandParseException.kt @@ -0,0 +1,25 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions + +import de.bixilon.minosoft.data.commands.CommandStringReader + +class InvalidItemPredicateCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String) : super(ERROR_MESSAGE, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable) : super(ERROR_MESSAGE, command, currentArgument, cause) + + companion object { + private const val ERROR_MESSAGE = "Invalid item predicate!" + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidJSONCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidJSONCommandParseException.kt new file mode 100644 index 000000000..20994934d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/InvalidJSONCommandParseException.kt @@ -0,0 +1,25 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions + +import de.bixilon.minosoft.data.commands.CommandStringReader + +class InvalidJSONCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String) : super(ERROR_MESSAGE, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable) : super(ERROR_MESSAGE, command, currentArgument, cause) + + companion object { + private const val ERROR_MESSAGE = "Malformed json!" + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/UnknownCommandParseException.java b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/UnknownCommandParseException.java index 9b1b4fcc1..46b5feab6 100644 --- a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/UnknownCommandParseException.java +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/UnknownCommandParseException.java @@ -17,7 +17,7 @@ import de.bixilon.minosoft.data.commands.CommandStringReader; public class UnknownCommandParseException extends CommandParseException { - private static final String ERROR_MESSAGE = "Unknown command!"; + private static final String ERROR_MESSAGE = "Unknown command!" + 'a'; public UnknownCommandParseException(CommandStringReader command, String currentArgument) { super(ERROR_MESSAGE, command, currentArgument); diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/CompoundTagBadFormatCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/CompoundTagBadFormatCommandParseException.kt new file mode 100644 index 000000000..567db5a5b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/CompoundTagBadFormatCommandParseException.kt @@ -0,0 +1,22 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions.nbt + +import de.bixilon.minosoft.data.commands.CommandStringReader +import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException + +class CompoundTagBadFormatCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String, message: String) : super(message, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable, message: String) : super(message, command, currentArgument, cause) +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ExpectedPrimitiveTagCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ExpectedPrimitiveTagCommandParseException.kt new file mode 100644 index 000000000..eb33c6d89 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ExpectedPrimitiveTagCommandParseException.kt @@ -0,0 +1,22 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions.nbt + +import de.bixilon.minosoft.data.commands.CommandStringReader +import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException + +class ExpectedPrimitiveTagCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String, message: String) : super(message, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable, message: String) : super(message, command, currentArgument, cause) +} diff --git a/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ListTagBadFormatCommandParseException.kt b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ListTagBadFormatCommandParseException.kt new file mode 100644 index 000000000..bae123107 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/commands/parser/exceptions/nbt/ListTagBadFormatCommandParseException.kt @@ -0,0 +1,22 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program.If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.commands.parser.exceptions.nbt + +import de.bixilon.minosoft.data.commands.CommandStringReader +import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException + +class ListTagBadFormatCommandParseException : CommandParseException { + constructor(command: CommandStringReader, currentArgument: String, message: String) : super(message, command, currentArgument) + + constructor(command: CommandStringReader, currentArgument: String, cause: Throwable) : super(cause.message, command, currentArgument, cause) +} diff --git a/src/main/java/de/bixilon/minosoft/data/mappings/ModIdentifier.java b/src/main/java/de/bixilon/minosoft/data/mappings/ModIdentifier.java index 9fc5b05ed..38db9db1b 100644 --- a/src/main/java/de/bixilon/minosoft/data/mappings/ModIdentifier.java +++ b/src/main/java/de/bixilon/minosoft/data/mappings/ModIdentifier.java @@ -14,7 +14,6 @@ package de.bixilon.minosoft.data.mappings; import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; -import de.bixilon.minosoft.util.Util; import java.util.Objects; @@ -44,9 +43,7 @@ public class ModIdentifier { } public static ModIdentifier getIdentifier(String identifier) throws IllegalArgumentException { - try { - Util.doesStringEqualsRegex(identifier, ProtocolDefinition.IDENTIFIER_PATTERN); - } catch (IllegalArgumentException e) { + if (!ProtocolDefinition.IDENTIFIER_PATTERN.matcher(identifier).matches()) { throw new IllegalArgumentException(String.format("%s in not a valid identifier!", identifier)); } diff --git a/src/main/java/de/bixilon/minosoft/gui/main/dialogs/OfflineLoginController.java b/src/main/java/de/bixilon/minosoft/gui/main/dialogs/OfflineLoginController.java index e7d1630c4..a47d9bb70 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/dialogs/OfflineLoginController.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/dialogs/OfflineLoginController.java @@ -95,9 +95,7 @@ public class OfflineLoginController implements Initializable { } private void checkData(ObservableValue observableValue, String oldValue, String newValue) { - try { - Util.doesStringEqualsRegex(this.username.getText(), ProtocolDefinition.MINECRAFT_NAME_VALIDATOR); - } catch (IllegalArgumentException e) { + if (!ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(this.username.getText()).matches()) { this.addButton.setDisable(true); return; } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketDeclareCommands.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketDeclareCommands.java index fda4704ba..807f2924d 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketDeclareCommands.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketDeclareCommands.java @@ -15,7 +15,6 @@ package de.bixilon.minosoft.protocol.packets.clientbound.play; import de.bixilon.minosoft.data.commands.CommandNode; import de.bixilon.minosoft.data.commands.CommandRootNode; -import de.bixilon.minosoft.data.commands.parser.exceptions.CommandParseException; import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.protocol.network.Connection; import de.bixilon.minosoft.protocol.packets.ClientboundPacket; @@ -38,26 +37,6 @@ public class PacketDeclareCommands extends ClientboundPacket { @Override public void handle(Connection connection) { connection.setCommandRootNode(getRootNode()); - // ToDo: Remove these dummy commands - String[] commands = { - "setblock ~3 ^3 3 minecraft:anvil", - "setblock ~3 3 3 minecraft:anvila", - "setblock ~3 3 3 minecraft:anvil[facing=east]", - "setblock ~3 3 3 minecraft:anvil[facing=\"east\"]", - "setblock ~3 3 3 minecraft:anvil[facing=east\"]", - "setblock ~3 3 3 minecraft:anvil[facing=aeast]", - }; - for (String command : commands) { - try { - getRootNode().isSyntaxCorrect(connection, command); - Log.game("Command \"%s\" is valid", command); - } catch (CommandParseException e) { - Log.game("Command \"%s\" is invalid, %s: %s", command, e.getClass().getSimpleName(), e.getErrorMessage()); - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } } @Override diff --git a/src/main/java/de/bixilon/minosoft/util/Util.java b/src/main/java/de/bixilon/minosoft/util/Util.java index d74e8f19e..fc6b50967 100644 --- a/src/main/java/de/bixilon/minosoft/util/Util.java +++ b/src/main/java/de/bixilon/minosoft/util/Util.java @@ -16,6 +16,7 @@ package de.bixilon.minosoft.util; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import de.bixilon.minosoft.protocol.network.Connection; import de.bixilon.minosoft.protocol.protocol.InByteBuffer; import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition; @@ -23,6 +24,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import java.io.*; +import java.lang.reflect.Field; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -32,7 +34,6 @@ import java.util.Random; import java.util.UUID; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadLocalRandom; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.*; @@ -42,6 +43,23 @@ public final class Util { public static final String LINE_SEPARATOR = System.getProperty("line.separator"); private static final Random THREAD_LOCAL_RANDOM = ThreadLocalRandom.current(); + private static final Field JSON_READER_POS_FIELD; + private static final Field JSON_READER_LINE_START_FIELD; + + static { + new JsonReader(new StringReader("")); + Class jsonReadClass = JsonReader.class; + try { + JSON_READER_POS_FIELD = jsonReadClass.getDeclaredField("pos"); + JSON_READER_POS_FIELD.setAccessible(true); + JSON_READER_LINE_START_FIELD = jsonReadClass.getDeclaredField("lineStart"); + JSON_READER_LINE_START_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + public static UUID getUUIDFromString(String uuid) { uuid = uuid.trim(); if (uuid.length() == 36) { @@ -292,10 +310,11 @@ public final class Util { return !string.toLowerCase().equals(string); } - public static void doesStringEqualsRegex(String input, Pattern pattern) throws IllegalArgumentException { - Matcher matcher = pattern.matcher(input); - if (!matcher.find() || !matcher.group().equals(input)) { - throw new IllegalArgumentException(); + public static int getJsonReaderPosition(JsonReader jsonReader) { + try { + return JSON_READER_POS_FIELD.getInt(jsonReader) - JSON_READER_LINE_START_FIELD.getInt(jsonReader) + 1; + } catch (IllegalAccessException e) { + throw new IllegalStateException(); } } } diff --git a/src/main/java/de/bixilon/minosoft/util/nbt/tag/ListTag.java b/src/main/java/de/bixilon/minosoft/util/nbt/tag/ListTag.java index 30ce34879..10d73b2d8 100644 --- a/src/main/java/de/bixilon/minosoft/util/nbt/tag/ListTag.java +++ b/src/main/java/de/bixilon/minosoft/util/nbt/tag/ListTag.java @@ -20,36 +20,43 @@ import java.util.ArrayList; import java.util.Arrays; public class ListTag extends NBTTag { - private final TagTypes type; private final ArrayList list; + private TagTypes type; public ListTag(TagTypes type, ArrayList list) { this.type = type; this.list = list; } - public ListTag(TagTypes type, NBTTag[] list) { + public ListTag(TagTypes type, NBTTag... list) { this.type = type; this.list = new ArrayList<>(Arrays.asList(list)); } + + public ListTag() { + this.type = TagTypes.BYTE; // idk, default value? + this.list = new ArrayList<>(); + } + public ListTag(InByteBuffer buffer) { this.type = TagTypes.byId(new ByteTag(buffer).getValue()); int length = new IntTag(buffer).getValue(); this.list = new ArrayList<>(); for (int i = 0; i < length; i++) { - switch (this.type) { - case BYTE -> this.list.add(new ByteTag(buffer)); - case SHORT -> this.list.add(new ShortTag(buffer)); - case INT -> this.list.add(new IntTag(buffer)); - case LONG -> this.list.add(new LongTag(buffer)); - case FLOAT -> this.list.add(new FloatTag(buffer)); - case DOUBLE -> this.list.add(new DoubleTag(buffer)); - case BYTE_ARRAY -> this.list.add(new ByteArrayTag(buffer)); - case STRING -> this.list.add(new StringTag(buffer)); - case LIST -> this.list.add(new ListTag(buffer)); - case COMPOUND -> this.list.add(new CompoundTag(true, buffer)); - } + this.list.add(switch (this.type) { + case BYTE -> new ByteTag(buffer); + case SHORT -> new ShortTag(buffer); + case INT -> new IntTag(buffer); + case LONG -> new LongTag(buffer); + case FLOAT -> new FloatTag(buffer); + case DOUBLE -> new DoubleTag(buffer); + case BYTE_ARRAY -> new ByteArrayTag(buffer); + case STRING -> new StringTag(buffer); + case LIST -> new ListTag(buffer); + case COMPOUND -> new CompoundTag(true, buffer); + default -> throw new IllegalStateException("Unexpected value: " + this.type); + }); } } @@ -64,11 +71,23 @@ public class ListTag extends NBTTag { new IntTag(this.list.size()).writeBytes(buffer); - for (NBTTag NBTTag : this.list) { - NBTTag.writeBytes(buffer); + for (NBTTag tag : this.list) { + tag.writeBytes(buffer); } } + public ListTag addTag(NBTTag tag) { + if (this.type == null) { + this.type = tag.getType(); + } else { + if (this.type != tag.getType()) { + throw new IllegalArgumentException("Can not mix types!"); + } + } + this.list.add(tag); + return this; + } + @SuppressWarnings("unchecked") public ArrayList getValue() { return (ArrayList) this.list;