Mirror von
https://github.com/ViaVersion/ViaVersion.git
synchronisiert 2024-11-03 14:50:30 +01:00
Ursprung
c1c542cc5a
Commit
e386f7cf45
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* See https://github.com/KyoriPowered/adventure.
|
||||
*/
|
||||
public final class BinaryTagIO {
|
||||
private BinaryTagIO() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a compound tag from a {@link String}.
|
||||
*
|
||||
* @param input the string
|
||||
* @return the compound tag
|
||||
* @throws IOException if an exception was encountered while reading a compound tag
|
||||
*/
|
||||
public static @NotNull
|
||||
CompoundTag readString(final @NotNull String input) throws IOException {
|
||||
try {
|
||||
final CharBuffer buffer = new CharBuffer(input);
|
||||
final TagStringReader parser = new TagStringReader(buffer);
|
||||
final CompoundTag tag = parser.compound();
|
||||
if (buffer.skipWhitespace().hasMore()) {
|
||||
throw new IOException("Document had trailing content after first CompoundTag");
|
||||
}
|
||||
return tag;
|
||||
} catch (final StringTagParseException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a compound tag to a {@link String}.
|
||||
*
|
||||
* @param tag the compound tag
|
||||
* @return the string
|
||||
* @throws IOException if an exception was encountered while writing the compound tag
|
||||
*/
|
||||
public static @NotNull
|
||||
String writeString(final @NotNull CompoundTag tag) throws IOException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try (final TagStringWriter emit = new TagStringWriter(sb)) {
|
||||
emit.writeTag(tag);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
/* package */ final class CharBuffer {
|
||||
private final CharSequence sequence;
|
||||
private int index;
|
||||
|
||||
CharBuffer(final CharSequence sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character at the current position
|
||||
*
|
||||
* @return The current character
|
||||
*/
|
||||
public char peek() {
|
||||
return this.sequence.charAt(this.index);
|
||||
}
|
||||
|
||||
public char peek(final int offset) {
|
||||
return this.sequence.charAt(this.index + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current character and advance
|
||||
*
|
||||
* @return current character
|
||||
*/
|
||||
public char take() {
|
||||
return this.sequence.charAt(this.index++);
|
||||
}
|
||||
|
||||
public boolean advance() {
|
||||
this.index++;
|
||||
return this.hasMore();
|
||||
}
|
||||
|
||||
public boolean hasMore() {
|
||||
return this.index < this.sequence.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the provided token, and advance the reader index past the {@code until} character.
|
||||
*
|
||||
* @param until Case-insensitive token
|
||||
* @return the string starting at the current position (inclusive) and going until the location of {@code until}, exclusive
|
||||
*/
|
||||
public CharSequence takeUntil(char until) throws StringTagParseException {
|
||||
until = Character.toLowerCase(until);
|
||||
int endIdx = -1;
|
||||
for (int idx = this.index; idx < this.sequence.length(); ++idx) {
|
||||
if (this.sequence.charAt(idx) == Tokens.ESCAPE_MARKER) {
|
||||
idx++;
|
||||
} else if (Character.toLowerCase(this.sequence.charAt(idx)) == until) {
|
||||
endIdx = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endIdx == -1) {
|
||||
throw this.makeError("No occurrence of " + until + " was found");
|
||||
}
|
||||
|
||||
final CharSequence result = this.sequence.subSequence(this.index, endIdx);
|
||||
this.index = endIdx + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the next non-whitespace character is the provided parameter.
|
||||
*
|
||||
* <p>If the assertion is successful, the token will be consumed.</p>
|
||||
*
|
||||
* @param expectedChar expected character
|
||||
* @return this
|
||||
* @throws StringTagParseException if EOF or non-matching value is found
|
||||
*/
|
||||
public CharBuffer expect(final char expectedChar) throws StringTagParseException {
|
||||
this.skipWhitespace();
|
||||
if (!this.hasMore()) {
|
||||
throw this.makeError("Expected character '" + expectedChar + "' but got EOF");
|
||||
}
|
||||
if (this.peek() != expectedChar) {
|
||||
throw this.makeError("Expected character '" + expectedChar + "' but got '" + this.peek() + "'");
|
||||
}
|
||||
this.take();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CharBuffer skipWhitespace() {
|
||||
while (this.hasMore() && Character.isWhitespace(this.peek())) this.advance();
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringTagParseException makeError(final String message) {
|
||||
return new StringTagParseException(message, this.sequence, this.index);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception thrown when parsing a string tag
|
||||
*/
|
||||
/* package */ class StringTagParseException extends IOException {
|
||||
private static final long serialVersionUID = -3001637554903912905L;
|
||||
private final CharSequence buffer;
|
||||
private final int position;
|
||||
|
||||
public StringTagParseException(final String message, final CharSequence buffer, final int position) {
|
||||
super(message);
|
||||
this.buffer = buffer;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
// TODO: Provide more specific position information
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return super.getMessage() + "(at position " + this.position + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* See https://github.com/KyoriPowered/adventure.
|
||||
*/
|
||||
/* package */ final class TagStringReader {
|
||||
private static final Field NAME_FIELD = getNameField();
|
||||
private final CharBuffer buffer;
|
||||
|
||||
private static Field getNameField() {
|
||||
try {
|
||||
return Tag.class.getDeclaredField("name");
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public TagStringReader(final CharBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public CompoundTag compound() throws StringTagParseException {
|
||||
this.buffer.expect(Tokens.COMPOUND_BEGIN);
|
||||
final CompoundTag compoundTag = new CompoundTag("");
|
||||
while (this.buffer.hasMore()) {
|
||||
final String key = this.key();
|
||||
final Tag tag = this.tag();
|
||||
// Doesn't get around this with the steveice lib :/
|
||||
try {
|
||||
if (!NAME_FIELD.isAccessible()) {
|
||||
NAME_FIELD.setAccessible(true);
|
||||
}
|
||||
NAME_FIELD.set(tag, key);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
compoundTag.put(tag);
|
||||
if (this.separatorOrCompleteWith(Tokens.COMPOUND_END)) {
|
||||
return compoundTag;
|
||||
}
|
||||
}
|
||||
throw this.buffer.makeError("Unterminated compound tag!");
|
||||
}
|
||||
|
||||
public ListTag list() throws StringTagParseException {
|
||||
final ListTag listTag = new ListTag("");
|
||||
this.buffer.expect(Tokens.ARRAY_BEGIN);
|
||||
while (this.buffer.hasMore()) {
|
||||
final Tag next = this.tag();
|
||||
// TODO: validate type
|
||||
listTag.add(next);
|
||||
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
|
||||
return listTag;
|
||||
}
|
||||
}
|
||||
throw this.buffer.makeError("Reached end of file without end of list tag!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to a list tag in syntax, but returning a single array tag rather than a list of tags.
|
||||
*
|
||||
* @return array-typed tag
|
||||
*/
|
||||
public Tag array(final char elementType) throws StringTagParseException {
|
||||
this.buffer.expect(Tokens.ARRAY_BEGIN)
|
||||
.expect(elementType)
|
||||
.expect(Tokens.ARRAY_SIGNATURE_SEPARATOR);
|
||||
|
||||
if (elementType == Tokens.TYPE_BYTE) {
|
||||
return new ByteArrayTag("", this.byteArray());
|
||||
} else if (elementType == Tokens.TYPE_INT) {
|
||||
return new IntArrayTag("", this.intArray());
|
||||
} else if (elementType == Tokens.TYPE_LONG) {
|
||||
return new LongArrayTag("", this.longArray());
|
||||
} else {
|
||||
throw this.buffer.makeError("Type " + elementType + " is not a valid element type in an array!");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] byteArray() throws StringTagParseException {
|
||||
final List<Byte> bytes = new ArrayList<>();
|
||||
while (this.buffer.hasMore()) {
|
||||
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_BYTE);
|
||||
try {
|
||||
bytes.add(Byte.valueOf(value.toString()));
|
||||
} catch (final NumberFormatException ex) {
|
||||
throw this.buffer.makeError("All elements of a byte array must be bytes!");
|
||||
}
|
||||
|
||||
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
|
||||
final byte[] result = new byte[bytes.size()];
|
||||
for (int i = 0; i < bytes.size(); ++i) { // todo yikes, let's do less boxing
|
||||
result[i] = bytes.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw this.buffer.makeError("Reached end of document without array close");
|
||||
}
|
||||
|
||||
private int[] intArray() throws StringTagParseException {
|
||||
final IntStream.Builder builder = IntStream.builder();
|
||||
while (this.buffer.hasMore()) {
|
||||
final Tag value = this.tag();
|
||||
if (!(value instanceof IntTag)) {
|
||||
throw this.buffer.makeError("All elements of an int array must be ints!");
|
||||
}
|
||||
builder.add(((IntTag) value).getValue());
|
||||
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
|
||||
return builder.build().toArray();
|
||||
}
|
||||
}
|
||||
throw this.buffer.makeError("Reached end of document without array close");
|
||||
}
|
||||
|
||||
private long[] longArray() throws StringTagParseException {
|
||||
final List<Long> longs = new ArrayList<>();
|
||||
while (this.buffer.hasMore()) {
|
||||
final CharSequence value = this.buffer.skipWhitespace().takeUntil(Tokens.TYPE_LONG);
|
||||
try {
|
||||
longs.add(Long.valueOf(value.toString()));
|
||||
} catch (final NumberFormatException ex) {
|
||||
throw this.buffer.makeError("All elements of a long array must be longs!");
|
||||
}
|
||||
|
||||
if (this.separatorOrCompleteWith(Tokens.ARRAY_END)) {
|
||||
final long[] result = new long[longs.size()];
|
||||
for (int i = 0; i < longs.size(); ++i) { // todo yikes
|
||||
result[i] = longs.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw this.buffer.makeError("Reached end of document without array close");
|
||||
}
|
||||
|
||||
public String key() throws StringTagParseException {
|
||||
this.buffer.skipWhitespace();
|
||||
final char starChar = this.buffer.peek();
|
||||
try {
|
||||
if (starChar == Tokens.SINGLE_QUOTE || starChar == Tokens.DOUBLE_QUOTE) {
|
||||
return unescape(this.buffer.takeUntil(this.buffer.take()).toString());
|
||||
}
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
while (Tokens.id(this.buffer.peek())) {
|
||||
builder.append(this.buffer.take());
|
||||
}
|
||||
return builder.toString();
|
||||
} finally {
|
||||
this.buffer.expect(Tokens.COMPOUND_KEY_TERMINATOR);
|
||||
}
|
||||
}
|
||||
|
||||
public Tag tag() throws StringTagParseException {
|
||||
final char startToken = this.buffer.skipWhitespace().peek();
|
||||
switch (startToken) {
|
||||
case Tokens.COMPOUND_BEGIN:
|
||||
return this.compound();
|
||||
case Tokens.ARRAY_BEGIN:
|
||||
if (this.buffer.peek(2) == ';') { // we know we're an array tag
|
||||
return this.array(this.buffer.peek(1));
|
||||
} else {
|
||||
return this.list();
|
||||
}
|
||||
case Tokens.SINGLE_QUOTE:
|
||||
case Tokens.DOUBLE_QUOTE:
|
||||
// definitely a string tag
|
||||
this.buffer.advance();
|
||||
return new StringTag("", unescape(this.buffer.takeUntil(startToken).toString()));
|
||||
default: // scalar
|
||||
return this.scalar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A tag that is definitely some sort of scalar
|
||||
*
|
||||
* <p>Does not detect quoted strings, so </p>
|
||||
*
|
||||
* @return a parsed tag
|
||||
*/
|
||||
private Tag scalar() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
boolean possiblyNumeric = true;
|
||||
while (this.buffer.hasMore()) {
|
||||
final char current = this.buffer.peek();
|
||||
if (possiblyNumeric && !Tokens.numeric(current)) {
|
||||
if (builder.length() != 0) {
|
||||
Tag result = null;
|
||||
try {
|
||||
switch (Character.toUpperCase(current)) { // try to read and return as a number
|
||||
// case Tokens.TYPE_INTEGER: // handled below, ints are ~special~
|
||||
case Tokens.TYPE_BYTE:
|
||||
result = new ByteTag("", Byte.parseByte(builder.toString()));
|
||||
break;
|
||||
case Tokens.TYPE_SHORT:
|
||||
result = new ShortTag("", (Short.parseShort(builder.toString())));
|
||||
break;
|
||||
case Tokens.TYPE_LONG:
|
||||
result = new LongTag("", (Long.parseLong(builder.toString())));
|
||||
break;
|
||||
case Tokens.TYPE_FLOAT:
|
||||
result = new FloatTag("", (Float.parseFloat(builder.toString())));
|
||||
break;
|
||||
case Tokens.TYPE_DOUBLE:
|
||||
result = new DoubleTag("", (Double.parseDouble(builder.toString())));
|
||||
break;
|
||||
}
|
||||
} catch (final NumberFormatException ex) {
|
||||
possiblyNumeric = false; // fallback to treating as a String
|
||||
}
|
||||
if (result != null) {
|
||||
this.buffer.take();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current == '\\') { // escape -- we are significantly more lenient than original format at the moment
|
||||
this.buffer.advance();
|
||||
builder.append(this.buffer.take());
|
||||
} else if (Tokens.id(current)) {
|
||||
builder.append(this.buffer.take());
|
||||
} else { // end of value
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we run out of content without an explicit value separator, then we're either an integer or string tag -- all others have a character at the end
|
||||
final String built = builder.toString();
|
||||
if (possiblyNumeric) {
|
||||
try {
|
||||
return new IntTag("", Integer.parseInt(built));
|
||||
} catch (final NumberFormatException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return new StringTag("", built);
|
||||
|
||||
}
|
||||
|
||||
private boolean separatorOrCompleteWith(final char endCharacter) throws StringTagParseException {
|
||||
if (this.buffer.skipWhitespace().peek() == endCharacter) {
|
||||
this.buffer.take();
|
||||
return true;
|
||||
}
|
||||
this.buffer.expect(Tokens.VALUE_SEPARATOR);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove simple escape sequences from a string
|
||||
*
|
||||
* @param withEscapes input string with escapes
|
||||
* @return string with escapes processed
|
||||
*/
|
||||
private static String unescape(final String withEscapes) {
|
||||
int escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER);
|
||||
if (escapeIdx == -1) { // nothing to unescape
|
||||
return withEscapes;
|
||||
}
|
||||
int lastEscape = 0;
|
||||
final StringBuilder output = new StringBuilder(withEscapes.length());
|
||||
do {
|
||||
output.append(withEscapes, lastEscape, escapeIdx);
|
||||
lastEscape = escapeIdx + 1;
|
||||
} while ((escapeIdx = withEscapes.indexOf(Tokens.ESCAPE_MARKER, lastEscape + 1)) != -1); // add one extra character to make sure we don't include escaped backslashes
|
||||
output.append(withEscapes.substring(lastEscape));
|
||||
return output.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* See https://github.com/KyoriPowered/adventure.
|
||||
*/
|
||||
/* package */ final class TagStringWriter implements AutoCloseable {
|
||||
private final Appendable out;
|
||||
private final String indent = " "; // TODO: pretty-printing
|
||||
private int level;
|
||||
/**
|
||||
* Whether a {@link Tokens#VALUE_SEPARATOR} needs to be printed before the beginning of the next object.
|
||||
*/
|
||||
private boolean needsSeparator;
|
||||
|
||||
public TagStringWriter(final Appendable out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
// NBT-specific
|
||||
|
||||
public TagStringWriter writeTag(final Tag tag) throws IOException {
|
||||
if (tag instanceof CompoundTag) {
|
||||
return this.writeCompound((CompoundTag) tag);
|
||||
} else if (tag instanceof ListTag) {
|
||||
return this.writeList((ListTag) tag);
|
||||
} else if (tag instanceof ByteArrayTag) {
|
||||
return this.writeByteArray((ByteArrayTag) tag);
|
||||
} else if (tag instanceof IntArrayTag) {
|
||||
return this.writeIntArray((IntArrayTag) tag);
|
||||
} else if (tag instanceof LongArrayTag) {
|
||||
return this.writeLongArray((LongArrayTag) tag);
|
||||
} else if (tag instanceof StringTag) {
|
||||
return this.value(((StringTag) tag).getValue(), Tokens.EOF);
|
||||
} else if (tag instanceof ByteTag) {
|
||||
return this.value(Byte.toString(((ByteTag) tag).getValue()), Tokens.TYPE_BYTE);
|
||||
} else if (tag instanceof ShortTag) {
|
||||
return this.value(Short.toString(((ShortTag) tag).getValue()), Tokens.TYPE_SHORT);
|
||||
} else if (tag instanceof IntTag) {
|
||||
return this.value(Integer.toString(((IntTag) tag).getValue()), Tokens.TYPE_INT);
|
||||
} else if (tag instanceof LongTag) {
|
||||
return this.value(Long.toString(((LongTag) tag).getValue()), Tokens.TYPE_LONG);
|
||||
} else if (tag instanceof FloatTag) {
|
||||
return this.value(Float.toString(((FloatTag) tag).getValue()), Tokens.TYPE_FLOAT);
|
||||
} else if (tag instanceof DoubleTag) {
|
||||
return this.value(Double.toString(((DoubleTag) tag).getValue()), Tokens.TYPE_DOUBLE);
|
||||
} else {
|
||||
throw new IOException("Unknown tag type: " + tag.getClass().getSimpleName());
|
||||
// unknown!
|
||||
}
|
||||
}
|
||||
|
||||
private TagStringWriter writeCompound(final CompoundTag tag) throws IOException {
|
||||
this.beginCompound();
|
||||
for (Tag t : tag) {
|
||||
this.key(t.getName());
|
||||
this.writeTag(t);
|
||||
}
|
||||
this.endCompound();
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter writeList(final ListTag tag) throws IOException {
|
||||
this.beginList();
|
||||
for (final Tag el : tag) {
|
||||
this.printAndResetSeparator();
|
||||
this.writeTag(el);
|
||||
}
|
||||
this.endList();
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter writeByteArray(final ByteArrayTag tag) throws IOException {
|
||||
this.beginArray(Tokens.TYPE_BYTE);
|
||||
|
||||
final byte[] value = tag.getValue();
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
this.printAndResetSeparator();
|
||||
this.value(Byte.toString(value[i]), Tokens.TYPE_BYTE);
|
||||
}
|
||||
this.endArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter writeIntArray(final IntArrayTag tag) throws IOException {
|
||||
this.beginArray(Tokens.TYPE_INT);
|
||||
|
||||
final int[] value = tag.getValue();
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
this.printAndResetSeparator();
|
||||
this.value(Integer.toString(value[i]), Tokens.TYPE_INT);
|
||||
}
|
||||
this.endArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter writeLongArray(final LongArrayTag tag) throws IOException {
|
||||
this.beginArray(Tokens.TYPE_LONG);
|
||||
|
||||
final long[] value = tag.getValue();
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
this.printAndResetSeparator();
|
||||
this.value(Long.toString(value[i]), Tokens.TYPE_LONG);
|
||||
}
|
||||
this.endArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
// Value types
|
||||
|
||||
public TagStringWriter beginCompound() throws IOException {
|
||||
this.printAndResetSeparator();
|
||||
this.level++;
|
||||
this.out.append(Tokens.COMPOUND_BEGIN);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagStringWriter endCompound() throws IOException {
|
||||
this.out.append(Tokens.COMPOUND_END);
|
||||
this.level--;
|
||||
this.needsSeparator = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagStringWriter key(final String key) throws IOException {
|
||||
this.printAndResetSeparator();
|
||||
this.writeMaybeQuoted(key, false);
|
||||
this.out.append(Tokens.COMPOUND_KEY_TERMINATOR); // TODO: spacing/pretty-printing
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagStringWriter value(final String value, final char valueType) throws IOException {
|
||||
if (valueType == Tokens.EOF) { // string doesn't have its type
|
||||
this.writeMaybeQuoted(value, true);
|
||||
} else {
|
||||
this.out.append(value);
|
||||
if (valueType != Tokens.TYPE_INT) {
|
||||
this.out.append(valueType);
|
||||
}
|
||||
}
|
||||
this.needsSeparator = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagStringWriter beginList() throws IOException {
|
||||
this.printAndResetSeparator();
|
||||
this.level++;
|
||||
this.out.append(Tokens.ARRAY_BEGIN);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagStringWriter endList() throws IOException {
|
||||
this.out.append(Tokens.ARRAY_END);
|
||||
this.level--;
|
||||
this.needsSeparator = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter beginArray(final char type) throws IOException {
|
||||
this.beginList()
|
||||
.out.append(type)
|
||||
.append(Tokens.ARRAY_SIGNATURE_SEPARATOR);
|
||||
return this;
|
||||
}
|
||||
|
||||
private TagStringWriter endArray() throws IOException {
|
||||
return this.endList();
|
||||
}
|
||||
|
||||
private void writeMaybeQuoted(final String content, boolean requireQuotes) throws IOException {
|
||||
if (!requireQuotes) {
|
||||
for (int i = 0; i < content.length(); ++i) {
|
||||
if (!Tokens.id(content.charAt(i))) {
|
||||
requireQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requireQuotes) { // TODO: single quotes
|
||||
this.out.append(Tokens.DOUBLE_QUOTE);
|
||||
this.out.append(escape(content, Tokens.DOUBLE_QUOTE));
|
||||
this.out.append(Tokens.DOUBLE_QUOTE);
|
||||
} else {
|
||||
this.out.append(content);
|
||||
}
|
||||
}
|
||||
|
||||
private static String escape(final String content, final char quoteChar) {
|
||||
final StringBuilder output = new StringBuilder(content.length());
|
||||
for (int i = 0; i < content.length(); ++i) {
|
||||
final char c = content.charAt(i);
|
||||
if (c == quoteChar || c == '\\') {
|
||||
output.append(Tokens.ESCAPE_MARKER);
|
||||
}
|
||||
output.append(c);
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private void printAndResetSeparator() throws IOException {
|
||||
if (this.needsSeparator) {
|
||||
this.out.append(Tokens.VALUE_SEPARATOR);
|
||||
this.needsSeparator = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.level != 0) {
|
||||
throw new IllegalStateException("Document finished with unbalanced start and end objects");
|
||||
}
|
||||
if (this.out instanceof Writer) {
|
||||
((Writer) this.out).flush();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of adventure, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2017-2020 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package us.myles.ViaVersion.api.minecraft.nbt;
|
||||
|
||||
/**
|
||||
* See https://github.com/KyoriPowered/adventure.
|
||||
*/
|
||||
/* package */ final class Tokens {
|
||||
// Compounds
|
||||
static final char COMPOUND_BEGIN = '{';
|
||||
static final char COMPOUND_END = '}';
|
||||
static final char COMPOUND_KEY_TERMINATOR = ':';
|
||||
|
||||
// Arrays
|
||||
static final char ARRAY_BEGIN = '[';
|
||||
static final char ARRAY_END = ']';
|
||||
static final char ARRAY_SIGNATURE_SEPARATOR = ';';
|
||||
|
||||
static final char VALUE_SEPARATOR = ',';
|
||||
|
||||
static final char SINGLE_QUOTE = '\'';
|
||||
static final char DOUBLE_QUOTE = '"';
|
||||
static final char ESCAPE_MARKER = '\\';
|
||||
|
||||
static final char TYPE_BYTE = 'B';
|
||||
static final char TYPE_SHORT = 'S';
|
||||
static final char TYPE_INT = 'I'; // array only
|
||||
static final char TYPE_LONG = 'L';
|
||||
static final char TYPE_FLOAT = 'F';
|
||||
static final char TYPE_DOUBLE = 'D';
|
||||
|
||||
static final char EOF = '\0';
|
||||
|
||||
private Tokens() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a character is a valid component in an identifier
|
||||
*
|
||||
* <p>An identifier character must match the expression {@code [a-zA-Z0-9_+.-]}</p>
|
||||
*
|
||||
* @param c the character
|
||||
* @return identifier
|
||||
*/
|
||||
static boolean id(final char c) {
|
||||
return (c >= 'a' && c <= 'z')
|
||||
|| (c >= 'A' && c <= 'Z')
|
||||
|| (c >= '0' && c <= '9')
|
||||
|| c == '-' || c == '_'
|
||||
|| c == '.' || c == '+';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether a character could be at some position in a number.
|
||||
* <p>
|
||||
* A string passing this check does not necessarily mean it is syntactically valid
|
||||
*
|
||||
* @param c character to check
|
||||
* @return if possibly part of a number
|
||||
*/
|
||||
static boolean numeric(final char c) {
|
||||
return (c >= '0' && c <= '9') // digit
|
||||
|| c == '+' || c == '-' // positive or negative
|
||||
|| c == 'e' || c == 'E' // exponent
|
||||
|| c == '.'; // decimal
|
||||
}
|
||||
}
|
@ -1,16 +1,25 @@
|
||||
package us.myles.ViaVersion.protocols.protocol1_13to1_12_2;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import us.myles.ViaVersion.api.Via;
|
||||
import us.myles.ViaVersion.api.minecraft.item.Item;
|
||||
import us.myles.ViaVersion.api.minecraft.nbt.BinaryTagIO;
|
||||
import us.myles.ViaVersion.api.rewriters.ComponentRewriter;
|
||||
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData;
|
||||
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.packets.InventoryPackets;
|
||||
import us.myles.ViaVersion.util.GsonUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
@ -20,6 +29,71 @@ public class ChatRewriter {
|
||||
private static final Pattern URL = Pattern.compile("^(?:(https?)://)?([-\\w_.]{2,}\\.[a-z]{2,4})(/\\S*)?$");
|
||||
private static final BaseComponent[] EMPTY_COMPONENTS = new BaseComponent[0];
|
||||
private static final ComponentRewriter COMPONENT_REWRITER = new ComponentRewriter() {
|
||||
@Override
|
||||
protected void handleHoverEvent(JsonObject hoverEvent) {
|
||||
super.handleHoverEvent(hoverEvent);
|
||||
String action = hoverEvent.getAsJsonPrimitive("action").getAsString();
|
||||
if (!action.equals("show_item")) return;
|
||||
|
||||
JsonElement value = hoverEvent.getAsJsonObject("value");
|
||||
if (value == null) return;
|
||||
|
||||
String text = findItemNBT(value);
|
||||
System.out.println(text);
|
||||
try {
|
||||
CompoundTag tag = BinaryTagIO.readString(text);
|
||||
System.out.println(tag);
|
||||
CompoundTag itemTag = tag.get("tag");
|
||||
ShortTag damageTag = tag.get("Damage");
|
||||
|
||||
// Call item converter
|
||||
short damage = damageTag != null ? damageTag.getValue() : 0;
|
||||
Item item = new Item();
|
||||
item.setData(damage);
|
||||
item.setTag(itemTag);
|
||||
InventoryPackets.toClient(item);
|
||||
|
||||
// Serialize again
|
||||
if (damage != item.getData()) {
|
||||
tag.put(new ShortTag("Damage", item.getData()));
|
||||
}
|
||||
if (itemTag != null) {
|
||||
tag.put(itemTag);
|
||||
}
|
||||
|
||||
JsonArray array = new JsonArray();
|
||||
JsonObject object = new JsonObject();
|
||||
array.add(object);
|
||||
String serializedNBT = BinaryTagIO.writeString(tag);
|
||||
object.addProperty("text", serializedNBT);
|
||||
hoverEvent.add("value", array);
|
||||
|
||||
System.out.println(serializedNBT);
|
||||
} catch (IOException e) {
|
||||
Via.getPlatform().getLogger().warning("Invalid NBT in show_item:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String findItemNBT(JsonElement element) {
|
||||
if (element.isJsonArray()) {
|
||||
for (JsonElement jsonElement : element.getAsJsonArray()) {
|
||||
String value = findItemNBT(jsonElement);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} else if (element.isJsonObject()) {
|
||||
JsonPrimitive text = element.getAsJsonObject().getAsJsonPrimitive("text");
|
||||
if (text != null) {
|
||||
return text.getAsString();
|
||||
}
|
||||
} else if (element.isJsonPrimitive()) {
|
||||
return element.getAsJsonPrimitive().getAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTranslate(JsonObject object, String translate) {
|
||||
super.handleTranslate(object, translate);
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren