From 51b6e5cd8cd3a0ae575d406a83d2d8afd99e182b Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Sat, 18 Aug 2018 17:20:01 +0300 Subject: [PATCH] =?UTF-8?q?NBT:=20=D0=B2=D0=BD=D0=BE=D1=81=D0=B8=D0=BC=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2=20=D0=B1=D0=B8?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D1=83=20flow-nbt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В используемой версии отсутствует Tag_Long_Array --- .../com/flowpowered/nbt/LongArrayTag.java | 73 ++++++ .../com/flowpowered/nbt/NBTConstants.java | 38 ++++ .../java/com/flowpowered/nbt/TagType.java | 92 ++++++++ .../nbt/stream/NBTInputStream.java | 210 ++++++++++++++++++ 4 files changed, 413 insertions(+) create mode 100644 anvil-loader/src/main/java/com/flowpowered/nbt/LongArrayTag.java create mode 100644 anvil-loader/src/main/java/com/flowpowered/nbt/NBTConstants.java create mode 100644 anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java create mode 100644 anvil-loader/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java diff --git a/anvil-loader/src/main/java/com/flowpowered/nbt/LongArrayTag.java b/anvil-loader/src/main/java/com/flowpowered/nbt/LongArrayTag.java new file mode 100644 index 0000000..47b09ee --- /dev/null +++ b/anvil-loader/src/main/java/com/flowpowered/nbt/LongArrayTag.java @@ -0,0 +1,73 @@ +package com.flowpowered.nbt; + +import java.util.Arrays; + +public class LongArrayTag extends Tag { + /** + * The value. + */ + private final long[] value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public LongArrayTag(String name, long[] value) { + super(TagType.TAG_LONG_ARRAY, name); + this.value = value; + } + + @Override + public long[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for (long s : value) { + String hexDigits = Long.toHexString(s).toUpperCase(); + if (hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Long_Array" + append + ": " + hex.toString(); + } + + @Override + public LongArrayTag clone() { + long[] clonedArray = cloneArray(value); + + return new LongArrayTag(getName(), clonedArray); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof IntArrayTag)) { + return false; + } + + LongArrayTag tag = (LongArrayTag) other; + return Arrays.equals(value, tag.value) && getName().equals(tag.getName()); + } + + private long[] cloneArray(long[] longArray) { + if (longArray == null) { + return null; + } else { + int length = longArray.length; + byte[] newArray = new byte[length]; + System.arraycopy(longArray, 0, newArray, 0, length); + return longArray; + } + } +} diff --git a/anvil-loader/src/main/java/com/flowpowered/nbt/NBTConstants.java b/anvil-loader/src/main/java/com/flowpowered/nbt/NBTConstants.java new file mode 100644 index 0000000..f6dd1b4 --- /dev/null +++ b/anvil-loader/src/main/java/com/flowpowered/nbt/NBTConstants.java @@ -0,0 +1,38 @@ +package com.flowpowered.nbt; + +import java.nio.charset.Charset; + +/** + * A class which holds constant values. + */ +public final class NBTConstants { + /** + * The character set used by NBT (UTF-8). + */ + public static final Charset CHARSET = Charset.forName("UTF-8"); + /** + * Tag type constants. + */ + @Deprecated + public static final int TYPE_END = TagType.TAG_END.getId(), + TYPE_BYTE = TagType.TAG_BYTE.getId(), + TYPE_SHORT = TagType.TAG_SHORT.getId(), + TYPE_INT = TagType.TAG_INT.getId(), + TYPE_LONG = TagType.TAG_LONG.getId(), + TYPE_FLOAT = TagType.TAG_FLOAT.getId(), + TYPE_DOUBLE = TagType.TAG_DOUBLE.getId(), + TYPE_BYTE_ARRAY = TagType.TAG_BYTE_ARRAY.getId(), + TYPE_STRING = TagType.TAG_STRING.getId(), + TYPE_LIST = TagType.TAG_LIST.getId(), + TYPE_COMPOUND = TagType.TAG_COMPOUND.getId(), + TYPE_INT_ARRAY = TagType.TAG_INT_ARRAY.getId(), + TYPE_SHORT_ARRAY = TagType.TAG_SHORT_ARRAY.getId(), + TYPE_LONG_ARRAY = TagType.TAG_LONG_ARRAY.getId(); + + /** + * Default private constructor. + */ + private NBTConstants() { + } +} + diff --git a/anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java b/anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java new file mode 100644 index 0000000..8034468 --- /dev/null +++ b/anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java @@ -0,0 +1,92 @@ +package com.flowpowered.nbt; + +import java.util.HashMap; +import java.util.Map; + +public enum TagType { + TAG_END(EndTag.class, "TAG_End", 0), + TAG_BYTE(ByteTag.class, "TAG_Byte", 1), + TAG_SHORT(ShortTag.class, "TAG_Short", 2), + TAG_INT(IntTag.class, "TAG_Int", 3), + TAG_LONG(LongTag.class, "TAG_Long", 4), + TAG_FLOAT(FloatTag.class, "TAG_Float", 5), + TAG_DOUBLE(DoubleTag.class, "TAG_Double", 6), + TAG_BYTE_ARRAY(ByteArrayTag.class, "TAG_Byte_Array", 7), + TAG_STRING(StringTag.class, "TAG_String", 8), + TAG_LIST((Class) ListTag.class, "TAG_List", 9), + // Java generics, y u so suck + TAG_COMPOUND(CompoundTag.class, "TAG_Compound", 10), + TAG_INT_ARRAY(IntArrayTag.class, "TAG_Int_Array", 11), + TAG_LONG_ARRAY(LongArrayTag.class, "TAG_Long_Array", 12), + TAG_SHORT_ARRAY(ShortArrayTag.class, "TAG_Short_Array", 100),; + private static final Map>, TagType> BY_CLASS = new HashMap>, TagType>(); + private static final Map BY_NAME = new HashMap(); + private static final TagType[] BY_ID; + + static { + BY_ID = new TagType[BaseData.maxId + 1]; + for (TagType type : TagType.values()) { + BY_CLASS.put(type.getTagClass(), type); + BY_NAME.put(type.getTypeName(), type); + BY_ID[type.getId()] = type; + } + } + + private final Class> tagClass; + private final String typeName; + private final int id; + + private TagType(Class> tagClass, String typeName, int id) { + this.tagClass = tagClass; + this.typeName = typeName; + this.id = id; + // Such a hack, shame that Java makes this such a pain + if (this.id > BaseData.maxId) { + BaseData.maxId = this.id; + } + } + + public Class> getTagClass() { + return tagClass; + } + + public String getTypeName() { + return typeName; + } + + public int getId() { + return id; + } + + public static TagType getByTagClass(Class> clazz) { + TagType ret = BY_CLASS.get(clazz); + if (ret == null) { + throw new IllegalArgumentException("Tag type " + clazz + " is unknown!"); + } + return ret; + } + + public static TagType getByTypeName(String typeName) { + TagType ret = BY_NAME.get(typeName); + if (ret == null) { + throw new IllegalArgumentException("Tag type " + typeName + " is unknown!"); + } + return ret; + } + + public static TagType getById(int id) { + if (id >= 0 && id < BY_ID.length) { + TagType ret = BY_ID[id]; + if (ret == null) { + throw new IllegalArgumentException("Tag type id " + id + " is unknown!"); + } + return ret; + } else { + throw new IndexOutOfBoundsException("Tag type id " + id + " is out of bounds!"); + } + } + + private static class BaseData { + private static int maxId = 0; + } +} diff --git a/anvil-loader/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java b/anvil-loader/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java new file mode 100644 index 0000000..8298779 --- /dev/null +++ b/anvil-loader/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java @@ -0,0 +1,210 @@ +package com.flowpowered.nbt.stream; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +import com.flowpowered.nbt.*; + +/** + * This class reads NBT, or Named Binary Tag streams, and produces an object graph of subclasses of the {@link Tag} object.

The NBT format was created by Markus Persson, and the specification + * may be found at https://flowpowered.com/nbt/spec.txt. + */ +public final class NBTInputStream implements Closeable { + /** + * The data input stream. + */ + private final EndianSwitchableInputStream is; + + /** + * Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. This assumes the stream is compressed. + * + * @param is The input stream. + * @throws java.io.IOException if an I/O error occurs. + */ + public NBTInputStream(InputStream is) throws IOException { + this(is, true, ByteOrder.BIG_ENDIAN); + } + + /** + * Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not. This assumes the + * stream uses big endian encoding. + * + * @param is The input stream. + * @param compressed A flag indicating if the stream is compressed. + * @throws java.io.IOException if an I/O error occurs. + */ + public NBTInputStream(InputStream is, boolean compressed) throws IOException { + this(is, compressed, ByteOrder.BIG_ENDIAN); + } + + /** + * Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not. + * + * @param is The input stream. + * @param compressed A flag indicating if the stream is compressed. + * @param endianness Whether to read numbers from the InputStream with little endian encoding. + * @throws java.io.IOException if an I/O error occurs. + */ + public NBTInputStream(InputStream is, boolean compressed, ByteOrder endianness) throws IOException { + this.is = new EndianSwitchableInputStream(compressed ? new GZIPInputStream(is) : is, endianness); + } + + /** + * Reads an NBT {@link Tag} from the stream. + * + * @return The tag that was read. + * @throws java.io.IOException if an I/O error occurs. + */ + public Tag readTag() throws IOException { + return readTag(0); + } + + /** + * Reads an NBT {@link Tag} from the stream. + * + * @param depth The depth of this tag. + * @return The tag that was read. + * @throws java.io.IOException if an I/O error occurs. + */ + private Tag readTag(int depth) throws IOException { + int typeId = is.readByte() & 0xFF; + TagType type = TagType.getById(typeId); + + String name; + if (type != TagType.TAG_END) { + int nameLength = is.readShort() & 0xFFFF; + byte[] nameBytes = new byte[nameLength]; + is.readFully(nameBytes); + name = new String(nameBytes, NBTConstants.CHARSET.name()); + } else { + name = ""; + } + + return readTagPayload(type, name, depth); + } + + /** + * Reads the payload of a {@link Tag}, given the name and type. + * + * @param type The type. + * @param name The name. + * @param depth The depth. + * @return The tag. + * @throws java.io.IOException if an I/O error occurs. + */ + @SuppressWarnings ({"unchecked", "rawtypes"}) + private Tag readTagPayload(TagType type, String name, int depth) throws IOException { + switch (type) { + case TAG_END: + if (depth == 0) { + throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it."); + } else { + return new EndTag(); + } + + case TAG_BYTE: + return new ByteTag(name, is.readByte()); + + case TAG_SHORT: + return new ShortTag(name, is.readShort()); + + case TAG_INT: + return new IntTag(name, is.readInt()); + + case TAG_LONG: + return new LongTag(name, is.readLong()); + + case TAG_FLOAT: + return new FloatTag(name, is.readFloat()); + + case TAG_DOUBLE: + return new DoubleTag(name, is.readDouble()); + + case TAG_BYTE_ARRAY: + int length = is.readInt(); + byte[] bytes = new byte[length]; + is.readFully(bytes); + return new ByteArrayTag(name, bytes); + + case TAG_STRING: + length = is.readShort(); + bytes = new byte[length]; + is.readFully(bytes); + return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name())); + + case TAG_LIST: + TagType childType = TagType.getById(is.readByte()); + length = is.readInt(); + + Class clazz = childType.getTagClass(); + List tagList = new ArrayList(length); + for (int i = 0; i < length; i++) { + Tag tag = readTagPayload(childType, "", depth + 1); + if (tag instanceof EndTag) { + throw new IOException("TAG_End not permitted in a list."); + } else if (!clazz.isInstance(tag)) { + throw new IOException("Mixed tag types within a list."); + } + tagList.add(tag); + } + + return new ListTag(name, clazz, tagList); + + case TAG_COMPOUND: + CompoundMap compoundTagList = new CompoundMap(); + while (true) { + Tag tag = readTag(depth + 1); + if (tag instanceof EndTag) { + break; + } else { + compoundTagList.put(tag); + } + } + + return new CompoundTag(name, compoundTagList); + + case TAG_INT_ARRAY: + length = is.readInt(); + int[] ints = new int[length]; + for (int i = 0; i < length; i++) { + ints[i] = is.readInt(); + } + return new IntArrayTag(name, ints); + + case TAG_SHORT_ARRAY: + length = is.readInt(); + short[] shorts = new short[length]; + for (int i = 0; i < length; i++) { + shorts[i] = is.readShort(); + } + return new ShortArrayTag(name, shorts); + + case TAG_LONG_ARRAY: + length = is.readInt(); + long[] longs = new long[length]; + for (int i = 0; i < length; i++) { + longs[i] = is.readLong(); + } + return new LongArrayTag(name, longs); + + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + public void close() throws IOException { + is.close(); + } + + /** + * @return whether this NBTInputStream reads numbers in little-endian format. + */ + public ByteOrder getByteOrder() { + return is.getEndianness(); + } +}