Created
October 5, 2012 10:36
-
-
Save md-5/3839168 to your computer and use it in GitHub Desktop.
nbt lib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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