This Java class will read an NBT structure and return the topmost tag from an InputStream and let you write an NBT structure through the topmost tag to an OutputStream. A condensed documentation is at the bottom of the page.
Note: As of Minecraft weekly version 12w07a, there's a new tag with ID 11 - an "Int Array Tag." Minecraft worlds generated with 12w07a and later will use this tag.
Note 2: Code has been updated to include the new Tag ID 11 "Int Array".
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * NBT IO class * * @see <a href="https://github.com/udoprog/c10t/blob/master/docs/NBT.txt">Online NBT specification</a> */ public class Tag { private final Type type; private Type listType = null; private final String name; private Object value; /** * Enum for the tag types. */ public enum Type { TAG_End, TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound, TAG_Int_Array } /** * Create a new TAG_List or TAG_Compound NBT tag. * * @param type either TAG_List or TAG_Compound * @param name name for the new tag or null to create an unnamed tag. * @param value list of tags to add to the new tag. */ public Tag(Type type, String name, Tag[] value) { this(type, name, (Object) value); } /** * Create a new TAG_List with an empty list. Use {@link Tag#addTag(Tag)} to add tags later. * * @param name name for this tag or null to create an unnamed tag. * @param listType type of the elements in this empty list. */ public Tag(String name, Type listType) { this(Type.TAG_List, name, listType); } /** * Create a new NBT tag. * * @param type any value from the {@link Type} enum. * @param name name for the new tag or null to create an unnamed tag. * @param value an object that fits the tag type or a {@link Type} to create an empty TAG_List with this list type. */ public Tag(Type type, String name, Object value) { switch (type) { case TAG_End: if (value != null) throw new IllegalArgumentException(); break; case TAG_Byte: if (!(value instanceof Byte)) throw new IllegalArgumentException(); break; case TAG_Short: if (!(value instanceof Short)) throw new IllegalArgumentException(); break; case TAG_Int: if (!(value instanceof Integer)) throw new IllegalArgumentException(); break; case TAG_Long: if (!(value instanceof Long)) throw new IllegalArgumentException(); break; case TAG_Float: if (!(value instanceof Float)) throw new IllegalArgumentException(); break; case TAG_Double: if (!(value instanceof Double)) throw new IllegalArgumentException(); break; case TAG_Byte_Array: if (!(value instanceof byte[])) throw new IllegalArgumentException(); break; case TAG_String: if (!(value instanceof String)) throw new IllegalArgumentException(); break; case TAG_List: if (value instanceof Type) { this.listType = (Type) value; value = new Tag[0]; } else { if (!(value instanceof Tag[])) throw new IllegalArgumentException(); this.listType = (((Tag[]) value)[0]).getType(); } break; case TAG_Compound: if (!(value instanceof Tag[])) throw new IllegalArgumentException(); break; case TAG_Int_Array: if (!(value instanceof int[])) throw new IllegalArgumentException(); break; default: throw new IllegalArgumentException(); } this.type = type; this.name = name; this.value = value; } public Type getType() { return type; } public String getName() { return name; } public Object getValue() { return value; } public void setValue(Object newValue) { switch (type) { case TAG_End: if (value != null) throw new IllegalArgumentException(); break; case TAG_Byte: if (!(value instanceof Byte)) throw new IllegalArgumentException(); break; case TAG_Short: if (!(value instanceof Short)) throw new IllegalArgumentException(); break; case TAG_Int: if (!(value instanceof Integer)) throw new IllegalArgumentException(); break; case TAG_Long: if (!(value instanceof Long)) throw new IllegalArgumentException(); break; case TAG_Float: if (!(value instanceof Float)) throw new IllegalArgumentException(); break; case TAG_Double: if (!(value instanceof Double)) throw new IllegalArgumentException(); break; case TAG_Byte_Array: if (!(value instanceof byte[])) throw new IllegalArgumentException(); break; case TAG_String: if (!(value instanceof String)) throw new IllegalArgumentException(); break; case TAG_List: if (value instanceof Type) { this.listType = (Type) value; value = new Tag[0]; } else { if (!(value instanceof Tag[])) throw new IllegalArgumentException(); this.listType = (((Tag[]) value)[0]).getType(); } break; case TAG_Compound: if (!(value instanceof Tag[])) throw new IllegalArgumentException(); break; case TAG_Int_Array: if (!(value instanceof int[])) throw new IllegalArgumentException(); break; default: throw new IllegalArgumentException(); } value = newValue; } public Type getListType() { return listType; } /** * Add a tag to a TAG_List or a TAG_Compound. */ public void addTag(Tag tag) { if (type != Type.TAG_List && type != Type.TAG_Compound) throw new RuntimeException(); Tag[] subtags = (Tag[]) value; int index = subtags.length; //For TAG_Compund entries, we need to add the tag BEFORE the end, //or the new tag gets placed after the TAG_End, messing up the data. //TAG_End MUST be kept at the very end of the TAG_Compound. if(type == Type.TAG_Compound) index--; insertTag(tag, index); } /** * Add a tag to a TAG_List or a TAG_Compound at the specified index. */ public void insertTag(Tag tag, int index) { if (type != Type.TAG_List && type != Type.TAG_Compound) throw new RuntimeException(); Tag[] subtags = (Tag[]) value; if (subtags.length > 0) if (type == Type.TAG_List && tag.getType() != getListType()) throw new IllegalArgumentException(); if (index > subtags.length) throw new IndexOutOfBoundsException(); Tag[] newValue = new Tag[subtags.length + 1]; System.arraycopy(subtags, 0, newValue, 0, index); newValue[index] = tag; System.arraycopy(subtags, index, newValue, index + 1, subtags.length - index); value = newValue; } /** * Remove a tag from a TAG_List or a TAG_Compound at the specified index. * * @return the removed tag */ public Tag removeTag(int index) { if (type != Type.TAG_List && type != Type.TAG_Compound) throw new RuntimeException(); Tag[] subtags = (Tag[]) value; Tag victim = subtags[index]; Tag[] newValue = new Tag[subtags.length - 1]; System.arraycopy(subtags, 0, newValue, 0, index); index++; System.arraycopy(subtags, index, newValue, index - 1, subtags.length - index); value = newValue; return victim; } /** * Remove a tag from a TAG_List or a TAG_Compound. If the tag is not a child of this tag then nested tags are searched. * * @param tag tag to look for */ public void removeSubTag(Tag tag) { if (type != Type.TAG_List && type != Type.TAG_Compound) throw new RuntimeException(); if (tag == null) return; Tag[] subtags = (Tag[]) value; for (int i = 0; i < subtags.length; i++) { if (subtags[i] == tag) { removeTag(i); return; } else { if (subtags[i].type == Type.TAG_List || subtags[i].type == Type.TAG_Compound) { subtags[i].removeSubTag(tag); } } } } /** * Find the first nested tag with specified name in a TAG_Compound. * * @param name the name to look for. May be null to look for unnamed tags. * @return the first nested tag that has the specified name. */ public Tag findTagByName(String name) { return findNextTagByName(name, null); } /** * Find the first nested tag with specified name in a TAG_List or TAG_Compound after a tag with the same name. * * @param name the name to look for. May be null to look for unnamed tags. * @param found the previously found tag with the same name. * @return the first nested tag that has the specified name after the previously found tag. */ public Tag findNextTagByName(String name, Tag found) { if (type != Type.TAG_List && type != Type.TAG_Compound) return null; Tag[] subtags = (Tag[]) value; for (Tag subtag : subtags) { if ((subtag.name == null && name == null) || (subtag.name != null && subtag.name.equals(name))) { return subtag; } else { Tag newFound = subtag.findTagByName(name); if (newFound != null) if (newFound == found) continue; else return newFound; } } return null; } /** * Read a tag and its nested tags from an InputStream. * * @param is stream to read from, like a FileInputStream * @return NBT tag or structure read from the InputStream * @throws IOException if there was no valid NBT structure in the InputStream or if another IOException occurred. */ public static Tag readFrom(InputStream is) throws IOException { DataInputStream dis = new DataInputStream(new GZIPInputStream(is)); byte type = dis.readByte(); Tag tag = null; if (type == 0) { tag = new Tag(Type.TAG_End, null, null); } else { tag = new Tag(Type.values()[type], dis.readUTF(), readPayload(dis, type)); } dis.close(); return tag; } private static Object readPayload(DataInputStream dis, byte type) throws IOException { switch (type) { case 0: return null; case 1: return dis.readByte(); case 2: return dis.readShort(); case 3: return dis.readInt(); case 4: return dis.readLong(); case 5: return dis.readFloat(); case 6: return dis.readDouble(); case 7: int length = dis.readInt(); byte[] ba = new byte[length]; dis.readFully(ba); return ba; case 8: return dis.readUTF(); case 9: byte lt = dis.readByte(); int ll = dis.readInt(); Tag[] lo = new Tag[ll]; for (int i = 0; i < ll; i++) { lo[i] = new Tag(Type.values()[lt], null, readPayload(dis, lt)); } if (lo.length == 0) return Type.values()[lt]; else return lo; case 10: byte stt; Tag[] tags = new Tag[0]; do { stt = dis.readByte(); String name = null; if (stt != 0) { name = dis.readUTF(); } Tag[] newTags = new Tag[tags.length + 1]; System.arraycopy(tags, 0, newTags, 0, tags.length); newTags[tags.length] = new Tag(Type.values()[stt], name, readPayload(dis, stt)); tags = newTags; } while (stt != 0); return tags; case 11: int len = dis.readInt(); int[] ia = new int[len]; for (int i=0;i<len;i++) ia[i] = dis.readInt(); return ia; } return null; } /** * Read a tag and its nested tags from an InputStream. * * @param os stream to write to, like a FileOutputStream * @throws IOException if this is not a valid NBT structure or if any IOException occurred. */ public void writeTo(OutputStream os) throws IOException { DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)); dos.writeByte(type.ordinal()); if (type != Type.TAG_End) { dos.writeUTF(name); writePayload(dos); } dos.writeByte(0); dos.close(); } private void writePayload(DataOutputStream dos) throws IOException { switch (type) { case TAG_End: break; case TAG_Byte: dos.writeByte((Byte) value); break; case TAG_Short: dos.writeShort((Short) value); break; case TAG_Int: dos.writeInt((Integer) value); break; case TAG_Long: dos.writeLong((Long) value); break; case TAG_Float: dos.writeFloat((Float) value); break; case TAG_Double: dos.writeDouble((Double) value); break; case TAG_Byte_Array: byte[] ba = (byte[]) value; dos.writeInt(ba.length); dos.write(ba); break; case TAG_String: dos.writeUTF((String) value); break; case TAG_List: Tag[] list = (Tag[]) value; dos.writeByte(getListType().ordinal()); dos.writeInt(list.length); for (Tag tt : list) { tt.writePayload(dos); } break; case TAG_Compound: Tag[] subtags = (Tag[]) value; for (Tag st : subtags) { Tag subtag = st; Type type = subtag.getType(); dos.writeByte(type.ordinal()); if (type != Type.TAG_End) { dos.writeUTF(subtag.getName()); subtag.writePayload(dos); } } break; case TAG_Int_Array: int[] ia = (int[]) value; dos.writeInt(ia.length); for (int i=0;i<ia.length;i++) dos.writeInt(ia[i]); break; } } /** * Print the NBT structure to System.out */ public void print() { print(this, 0); } private String getTypeString(Type type) { return type.name(); } private void indent(int indent) { for (int i = 0; i < indent; i++) { System.out.print(" "); } } private void print(Tag t, int indent) { Type type = t.getType(); if (type == Type.TAG_End) return; String name = t.getName(); indent(indent); System.out.print(getTypeString(t.getType())); if (name != null) System.out.print("(\"" + t.getName() + "\")"); if (type == Type.TAG_Byte_Array) { byte[] b = (byte[]) t.getValue(); System.out.println(": [" + b.length + " bytes]"); } else if (type == Type.TAG_List) { Tag[] subtags = (Tag[]) t.getValue(); System.out.println(": " + subtags.length + " entries of type " + getTypeString(t.getListType())); for (Tag st : subtags) { print(st, indent + 1); } indent(indent); System.out.println("}"); } else if (type == Type.TAG_Compound) { Tag[] subtags = (Tag[]) t.getValue(); System.out.println(": " + (subtags.length - 1) + " entries"); indent(indent); System.out.println("{"); for (Tag st : subtags) { print(st, indent + 1); } indent(indent); System.out.println("}"); } else if (type == Type.TAG_Int_Array) { int[] i = (int[]) t.getValue(); System.out.println(": [" + i.length * 4 + " bytes]"); } else { System.out.println(": " + t.getValue()); } } /** * Replaces a tag's contents with another. * * @param toTag * @param fromTag */ public void replaceTag(Tag toTag, Tag fromTag) { if (toTag.getType() != fromTag.getType()) throw new RuntimeException("Mismatched types when copying " + toTag.getName() + ", from " + fromTag.getType() + " to " + toTag.getType()); toTag.setValue(fromTag.value); toTag.listType = fromTag.listType; } /** * Replaces this tags with the contents of this compound or list tag. * * @param copyTag The compound or list tag. */ public void replaceTags(Tag copyTag) { if (getType() != Type.TAG_Compound && getType() != Type.TAG_List) throw new RuntimeException("Replacing tags of non-list type: " + getType()); Tag[] subtags = (Tag[]) copyTag.getValue(); for (Tag fromTag : subtags) { if (fromTag.getType() == Type.TAG_End) { addTag(fromTag); break; } Tag toTag = findTagByName(fromTag.name); if (toTag == null) addTag(fromTag); else replaceTag(toTag, fromTag); } } }
If you find bugs: fix this page.
If you dislike this code, maybe the JNBT project is for you. The class above is unrelated to JNBT.
Documentation[]
These are the functions and documentation from the class above, in a condensed form. Private functions are not included. The functions are broken up into four categories: Constructors, Data Values, Tag Manaipulation and IO Functions. Hopefully this documentation helps someone understand what is happening, and allows them to use the Tag class. It definitely helped me.
Constructors:
- Tag(Type type,String name,Tag[] value)
- Description: Create a new TAG_List or TAG_Compound NBT tag.
- type: Either TAG_List or TAG_Compound.
- name: Name for the new tag or null to create an unnamed tag.
- value: List of tags to add to the new tag.
- Tag(String name, Type listType)
- Description: Create a new TAG_List with an empty list. Use {@link Tag#addTag(Tag)} to add tags later.
- name: Name for this tag or null to create an unnamed tag.
- listType: Type of the elements in this empty list.
- Tag(Type type, String name, Object value)
- Description: Create a new NBT tag.
- type: Any value from the {@link Type} enum.
- name: Name for the new tag or null to create an unnamed tag.
- value: An object that fits the tag type or a {@link Type} to create an empty TAG_List with this list type.
Data Values:
- getType()
- getName()
- getValue()
- getListType()
- setValue()
Tag Manipulation:
- addTag(Tag tag)
- Description: Add a tag to a TAG_List or a TAG_Compound.
- insertTag(Tag tag, int index)
- Description: Add a tag to a TAG_List or a TAG_Compound at the specified index.
- removeTag(int index)
- Description: Remove a tag from a TAG_List or a TAG_Compound at the specified index.
- Return: The removed tag.
- removeSubTag(Tag tag)
- Description: Remove a tag from a TAG_List or a TAG_Compound. If the tag is not a child of this tag then nested tags are searched.
- findTagByName(String name)
- Description: Find the first nested tag with specified name in a TAG_Compound.
- name: The name to look for. May be null to look for unnamed tags.
- Return: The first nested tag that has the specified name.
- findNextTagByName(String name, Tag found)
- Description: Find the first nested tag with specified name in a TAG_List or TAG_Compound after a tag with the same name.
- name: The name to look for. May be null to look for unnamed tags.
- found: the previously found tag with the same name.
- Return: The first nested tag that has the specified name after the previously found tag.
IO Functions:
- readFrom(InputStream is)
- Description: Read a tag and its nested tags from an InputStream.
- is: Stream to read from, like a FileInputStream.
- Return: NBT tag or structure read from the InputStream.
- Throws: IOException if there was no valid NBT structure in the InputStream or if another IOException occurred.
- writeTo(OutputStream os)
- Description: Write a tag and its nested tags to an OutputStream.
- os: Stream to write to, like a FileOutputStream.
- Throws: IOException if this is not a valid NBT structure or if any IOException occurred.
- print()
- Description: Print the NBT structure to System.out.