package net.glowstone.util.nbt; import java.io.Closeable; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.GZIPInputStream; /** * This class reads NBT, or Named Binary Tag streams, and produces an object * graph of subclasses of the {@link Tag} object. * <p> * The NBT format was created by Markus Persson, and the specification may * be found at <a href="http://www.minecraft.net/docs/NBT.txt"> * http://www.minecraft.net/docs/NBT.txt</a>. */ public final class NBTInputStream implements Closeable { /** * The data input stream. */ private final DataInputStream is; /** * Creates a new NBTInputStream, which will source its data * from the specified input stream. This assumes the stream is compressed. * * @param is The input stream. * @throws IOException if an I/O error occurs. */ public NBTInputStream(InputStream is) throws IOException { this(is, true); } /** * Creates a new 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. * @throws IOException if an I/O error occurs. */ @SuppressWarnings("resource") public NBTInputStream(InputStream is, boolean compressed) throws IOException { this.is = new DataInputStream(compressed ? new GZIPInputStream(is) : is); } /** * Reads the root NBT {@link CompoundTag} from the stream. * * @return The tag that was read. * @throws IOException if an I/O error occurs. */ public CompoundTag readCompound() throws IOException { return readCompound(NBTReadLimiter.UNLIMITED); } /** * Reads the root NBT {@link CompoundTag} from the stream. * * @param readLimiter The read limiter to prevent overflow when reading the NBT data. * @return The tag that was read. * @throws IOException if an I/O error occurs. */ public CompoundTag readCompound(NBTReadLimiter readLimiter) throws IOException { // read type TagType type = TagType.byIdOrError(is.readUnsignedByte()); if (type != TagType.COMPOUND) { throw new IOException("Root of NBTInputStream was " + type + ", not COMPOUND"); } // for now, throw away name int nameLength = is.readUnsignedShort(); is.skipBytes(nameLength); // read tag return (CompoundTag) readTagPayload(type, 0, readLimiter); } private CompoundTag readCompound(int depth, NBTReadLimiter readLimiter) throws IOException { CompoundTag result = new CompoundTag(); while (true) { // read type TagType type = TagType.byIdOrError(is.readUnsignedByte()); if (type == TagType.END) { break; } // read name String name = is.readUTF(); readLimiter.read(28 + 2 * name.length()); // read tag Tag tag = readTagPayload(type, depth + 1, readLimiter); readLimiter.read(36); result.put(name, tag); } return result; } /** * Reads the payload of a {@link Tag}, given the name and type. * * @param type The type. * @param depth The depth. * @return The tag. * @throws IOException if an I/O error occurs. */ @SuppressWarnings("unchecked") private Tag readTagPayload(TagType type, int depth, NBTReadLimiter readLimiter) throws IOException { if (depth > 512) { throw new IllegalStateException("Tried to read NBT tag with too high complexity, depth > 512"); } switch (type) { case BYTE: readLimiter.read(1); return new ByteTag(is.readByte()); case SHORT: readLimiter.read(2); return new ShortTag(is.readShort()); case INT: readLimiter.read(4); return new IntTag(is.readInt()); case LONG: readLimiter.read(8); return new LongTag(is.readLong()); case FLOAT: readLimiter.read(4); return new FloatTag(is.readFloat()); case DOUBLE: readLimiter.read(8); return new DoubleTag(is.readDouble()); case BYTE_ARRAY: readLimiter.read(24); int length = is.readInt(); readLimiter.read(length); byte[] bytes = new byte[length]; is.readFully(bytes); return new ByteArrayTag(bytes); case STRING: readLimiter.read(36); String s = is.readUTF(); readLimiter.read(2 * s.length()); return new StringTag(s); case LIST: readLimiter.read(37); TagType childType = TagType.byIdOrError(is.readUnsignedByte()); length = is.readInt(); readLimiter.read(4 * length); List<Tag> tagList = new ArrayList<>(length); for (int i = 0; i < length; i++) { tagList.add(readTagPayload(childType, depth + 1, readLimiter)); } return new ListTag(childType, tagList); case COMPOUND: readLimiter.read(48); return readCompound(depth + 1, readLimiter); case INT_ARRAY: readLimiter.read(37); length = is.readInt(); readLimiter.read(4 * length); int[] ints = new int[length]; for (int i = 0; i < length; ++i) { ints[i] = is.readInt(); } return new IntArrayTag(ints); default: throw new IOException("Invalid tag type: " + type + "."); } } @Override public void close() throws IOException { is.close(); } }