Skip to content

Instantly share code, notes, and snippets.

@md-5
Created October 5, 2012 10:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save md-5/3839168 to your computer and use it in GitHub Desktop.
Save md-5/3839168 to your computer and use it in GitHub Desktop.
nbt lib
/**
* Copyright (c) 2012, md_5. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* The name of the author may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.md_5.nbt;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class NBT {
public static class NBTOutputStream {
private final DataOutputStream out;
public NBTOutputStream(OutputStream out) {
this.out = new DataOutputStream(new BufferedOutputStream(out));
}
public void write(TagCompound root) throws IOException {
// write root tag
write(root, false);
// flush
out.flush();
}
private void write(Tag tag, boolean isList) throws IOException {
// lists
if (!isList) {
// type id
if (tag.getType() != TagType.TAG_END) {
out.writeByte(tag.getType().ordinal());
}
// name
out.writeUTF(tag.getName());
}
// data
switch (tag.getType()) {
case TAG_BYTE:
out.writeByte(((TagByte) tag).getValue());
break;
case TAG_BYTE_ARRAY:
TagByteArray bArray = (TagByteArray) tag;
out.writeInt(bArray.getValue().length);
for (byte b : bArray.getValue()) {
out.writeByte(b);
}
break;
case TAG_COMPOUND:
TagCompound compound = (TagCompound) tag;
for (Tag t : compound.getValue().values()) {
write(t, false);
}
out.writeByte(TagType.TAG_END.ordinal());
break;
case TAG_DOUBLE:
out.writeDouble(((TagDouble) tag).getValue());
break;
case TAG_FLOAT:
out.writeFloat(((TagFloat) tag).getValue());
break;
case TAG_INT:
out.writeInt(((TagInt) tag).getValue());
break;
case TAG_INT_ARRAY:
TagIntArray iArray = (TagIntArray) tag;
out.writeInt(iArray.getValue().length);
for (int i : iArray.getValue()) {
out.writeInt(i);
}
break;
case TAG_LIST:
TagList list = (TagList) tag;
out.writeByte(list.getChildType().ordinal());
out.writeInt(list.getValue().size());
for (Tag l : list.getValue()) {
write(l, true);
}
break;
case TAG_LONG:
out.writeLong(((TagLong) tag).getValue());
break;
case TAG_SHORT:
out.writeShort(((TagShort) tag).getValue());
break;
case TAG_STRING:
out.writeUTF(((TagString) tag).getValue());
break;
default:
throw new RuntimeException("Don't know how to handle tag type: " + tag);
}
}
/**
* Short method to write nbt to a file.
*
* @param file to read write to
* @param root root compound tag
* @throws IOException
*/
public static void toFile(File file, TagCompound root) throws IOException {
FileOutputStream out = new FileOutputStream(file);
new NBTOutputStream(out).write(root);
out.close();
}
}
public static class NBTInputStream {
private final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
private final ByteBuffer buf;
public NBTInputStream(ByteBuffer buf) {
this.buf = buf;
}
public TagCompound read() throws IOException {
return (TagCompound) readTag(true, true);
}
private Tag readTag(boolean root, boolean hasName) throws CharacterCodingException {
// read type id
byte typeId = buf.get();
// map to tag type
TagType tag = TagType.values()[typeId];
// check it is a compound
if (root && tag != TagType.TAG_COMPOUND) {
throw new IllegalArgumentException("NBT Data does not begin with " + TagType.TAG_COMPOUND);
}
// read the tag body
return readTag(tag, hasName);
}
private Tag readTag(TagType tag, boolean hasName) throws CharacterCodingException {
// assign name
String name = "";
// skip name for some tags
if (hasName && tag != TagType.TAG_END) {
// read tag name
name = readString();
}
// get ready to return a tag
Tag ret = null;
// switch
switch (tag) {
case TAG_BYTE:
ret = new TagByte(name, buf.get());
break;
case TAG_BYTE_ARRAY:
Byte[] byteArray = new Byte[buf.getInt()];
for (int i = 0; i < byteArray.length; i++) {
byteArray[i] = buf.get();
}
ret = new TagByteArray(name, byteArray);
break;
case TAG_COMPOUND:
TagCompound compound = new TagCompound(name);
while (true) {
Tag child = readTag(false, true);
if (child == null) {
break;
} else {
compound.getValue().put(child.getName(), child);
}
}
ret = compound;
break;
case TAG_DOUBLE:
ret = new TagDouble(name, buf.getDouble());
break;
case TAG_FLOAT:
ret = new TagFloat(name, buf.getFloat());
break;
case TAG_END:
return null;
case TAG_INT:
ret = new TagInt(name, buf.getInt());
break;
case TAG_INT_ARRAY:
Integer[] intArray = new Integer[buf.getInt()];
for (int i = 0; i < intArray.length; i++) {
intArray[i] = buf.getInt();
}
ret = new TagIntArray(name, intArray);
break;
case TAG_LIST:
TagType listType = TagType.values()[buf.get()];
int listLength = buf.getInt();
TagList list = new TagList(name, listType);
for (int i = 0; i < listLength; i++) {
Tag child = readTag(listType, false);
list.add(child);
}
ret = list;
break;
case TAG_LONG:
ret = new TagLong(name, buf.getLong());
break;
case TAG_SHORT:
ret = new TagShort(name, buf.getShort());
break;
case TAG_STRING:
ret = new TagString(name, readString());
break;
default:
throw new RuntimeException("Don't know how to handle tag type: " + tag);
}
return ret;
}
private String readString() throws CharacterCodingException {
// read length
int length = buf.getShort();
// only read up to the strings length
buf.limit(buf.position() + length);
// return the string
String ret = decoder.decode(buf).toString();
// go back to max length
buf.limit(buf.capacity());
// return the string
return ret;
}
/**
* Short method to read nbt from a file.
*
* @param file to read from
* @return root compound tag
* @throws IOException
*/
public static TagCompound fromFile(File file) throws IOException {
FileChannel chan = new FileInputStream(file).getChannel();
ByteBuffer buf = chan.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
TagCompound ret = new NBTInputStream(buf).read();
chan.close();
return ret;
}
}
public static abstract class Tag<T> {
/**
* The name of this tag.
*/
private final String name;
/**
* The type of this tag.
*/
private final TagType type;
/**
* This tags value.
*/
private final T value;
/**
* Create a the tag with the specified type, name and value.
*
* @param type type of this tag
* @param name name of this tag
* @param value value of this tag
*/
protected Tag(TagType type, String name, T value) {
this.name = name;
this.type = type;
this.value = value;
}
/**
* Gets the name of this tag.
*
* @return The name of this tag.
*/
public final String getName() {
return name;
}
/**
* Returns the type of this tag
*
* @return The type of this tag.
*/
public TagType getType() {
return type;
}
/**
* Gets the value of this tag.
*
* @return The value of this tag.
*/
public T getValue() {
return value;
}
@Override
public String toString() {
return getValue().toString();
}
}
public enum TagType {
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;
}
public static class TagByte extends Tag<Byte> {
public TagByte(String name, byte value) {
super(TagType.TAG_BYTE, name, value);
}
}
public static class TagByteArray extends Tag<Byte[]> {
public TagByteArray(String name, Byte[] value) {
super(TagType.TAG_BYTE_ARRAY, name, value);
}
}
public static class TagCompound extends Tag<Map<String, Tag<?>>> {
public TagCompound(String name) {
super(TagType.TAG_COMPOUND, name, new LinkedHashMap<String, Tag<?>>());
}
}
public static class TagDouble extends Tag<Double> {
public TagDouble(String name, Double value) {
super(TagType.TAG_DOUBLE, name, value);
}
}
public static class TagFloat extends Tag<Float> {
public TagFloat(String name, float value) {
super(TagType.TAG_FLOAT, name, value);
}
}
public static class TagInt extends Tag<Integer> {
public TagInt(String name, int value) {
super(TagType.TAG_INT, name, value);
}
}
public static class TagIntArray extends Tag<Integer[]> {
public TagIntArray(String name, Integer[] value) {
super(TagType.TAG_INT_ARRAY, name, value);
}
}
public static class TagList extends Tag<List<Tag<?>>> {
private final TagType childType;
public TagList(String name, TagType childType) {
super(TagType.TAG_LIST, name, new LinkedList<Tag<?>>());
this.childType = childType;
}
/**
* Get the type of tag this list contains.
*
* @return the lists child type
*/
public TagType getChildType() {
return childType;
}
public void add(Tag tag) {
if (tag.getType() != childType) {
throw new IllegalArgumentException(childType + " " + getType() + " expected " + childType + " child, but got " + tag.getType());
}
getValue().add(tag);
}
}
public static class TagLong extends Tag<Long> {
public TagLong(String name, long value) {
super(TagType.TAG_LONG, name, value);
}
}
public static class TagShort extends Tag<Short> {
public TagShort(String name, short value) {
super(TagType.TAG_SHORT, name, value);
}
}
public static class TagString extends Tag<String> {
public TagString(String name, String value) {
super(TagType.TAG_STRING, name, value);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment