Skip to content
This repository has been archived by the owner on Nov 19, 2020. It is now read-only.

Commit

Permalink
* Added first version of reader/writer classes; completely untested.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeerix committed Jan 4, 2012
1 parent ca94101 commit 93c4ee6
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 1 deletion.
5 changes: 4 additions & 1 deletion README.TXT
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
A lightweight NBT file reader/writer library
A lightweight NBT file reader/writer library

Data format description:
http://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt
13 changes: 13 additions & 0 deletions grammar-notes.TXT
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
http://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt

NBT::Root ::= <gzip> Byte='10' Tag_Compound </gzip>
NBT::Tag ::= TAG_Compound || TAG_List || (Map<String, X>, List<X>)
TAG_String || TAG_Byte_Array || (String, byte[])
TAG_Primitive (Byte, Short, ...)
TAG_Compound ::= TypeID TAG_String NBT::Tag TAG_Compound || Byte='0'
TAG_List ::= TypeID Tag_Int(length) NBT::Tag{length}
TypeID ::= Byte='1' || ... || Byte='10'
TAG_String ::= TAG_Short(length) UTF8{length}
TAG_Byte_Array ::= TAG_Short(length) Byte{length}
TAG_Primitive ::= TAG_Byte || TAG_Short || TAG_Int || TAG_Long ||
TAG_Float || TAG_Double (all big endian)
102 changes: 102 additions & 0 deletions src/me/zeerix/nbt/NBTStreamReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package me.zeerix.nbt;

import java.io.BufferedInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

/**
* @author Zeerix
*/
public class NBTStreamReader {
/**
* Reads a NBT tag compound from a GZIP compressed InputStream
* @param inputstream
* @return Map<String, ?> describing the NBT tag compound
* @throws IOException
*/
public static Map<String, ?> read(InputStream in) throws IOException {
return read(in, true);
}

/**
* Reads a NBT tag compound from an InputStream, either GZIP compressed or uncompressed
* @param inputstream
* @param compressed If the data in InputStream is compressed
* @return Map<String, ?> describing the NBT tag compound
* @throws IOException
*/
public static Map<String, ?> read(InputStream in, boolean compressed) throws IOException {
DataInputStream data = compressed
? new DataInputStream(new GZIPInputStream(in))
: new DataInputStream(new BufferedInputStream(in));
try {
return read( (DataInput)data );
} finally {
data.close();
}
}

private static Map<String, ?> read(DataInput in) throws IOException {
if (in.readByte() != 10) // assume TypeID of NBT::TAG_Compound
throw new IOException("Root tag must be a named compound tag");

String name = readString(in); // TODO: store root tag name

return readCompound(in);
}

private static Object readTag(DataInput in, byte type) throws IOException {
switch (type) {
case 1: return in.readByte();
case 2: return in.readShort();
case 3: return in.readInt();
case 4: return in.readLong();
case 5: return in.readFloat();
case 6: return in.readDouble();

case 7: return readByteArray(in);
case 8: return readString(in);
case 9: return readList(in);
case 10: return readCompound(in);
default: throw new IOException("Invalid NBT tag type (1-10): " + type);
}
}

private static byte[] readByteArray(DataInput in) throws IOException {
byte[] data = new byte[ in.readInt() ];
in.readFully(data);
return data;
}

private static String readString(DataInput in) throws IOException {
return in.readUTF();
}

private static List<?> readList(DataInput in) throws IOException {
byte type = in.readByte();
int length = in.readInt();
List<Object> list = new ArrayList<Object>(length);
for (int i = 0; i < length; ++i) {
Object tag = readTag(in, type);
list.add(tag);
}
return list;
}

private static Map<String, ?> readCompound(DataInput in) throws IOException {
Map<String, Object> map = new HashMap<String, Object>();
for (byte type; (type = in.readByte()) != 0; ) {
String name = readString(in);
Object tag = readTag(in, type);
map.put(name, tag);
}
return map;
}
}
113 changes: 113 additions & 0 deletions src/me/zeerix/nbt/NBTStreamWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package me.zeerix.nbt;

import java.io.BufferedOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;

/**
* @author Zeerix
*/
public class NBTStreamWriter {
/**
* Reads a NBT tag compound from a GZIP compressed InputStream
* @param inputstream
* @return Map<String, ?> describing the NBT tag compound
* @throws IOException
*/
public static void write(OutputStream out, Map<String, ?> map) throws IOException {
write(out, map, true);
}

/**
* Reads a NBT tag compound from an InputStream, either GZIP compressed or uncompressed
* @param inputstream
* @param compressed If the data in InputStream is compressed
* @return Map<String, ?> describing the NBT tag compound
* @throws IOException
*/
public static void write(OutputStream out, Map<String, ?> map, boolean compressed) throws IOException {
DataOutputStream data = compressed
? new DataOutputStream(new GZIPOutputStream(out))
: new DataOutputStream(new BufferedOutputStream(out));
try {
write( (DataOutput)data, map );
} finally {
data.close();
}
}

private static void write(DataOutput out, Map<String, ?> map) throws IOException {
out.writeByte(10); // TypeID of NBT::TAG_Compound

writeString(out, ""); // TODO: write root tag name

writeCompound(out, map);
}

private static byte whichType(Object tag) {
if (tag instanceof Byte) return 1;
if (tag instanceof Short) return 2;
if (tag instanceof Integer) return 3;
if (tag instanceof Long) return 4;
if (tag instanceof Float) return 5;
if (tag instanceof Double) return 6;

if (tag instanceof byte[]) return 7;
if (tag instanceof String) return 8;
if (tag instanceof List<?>) return 9;
if (tag instanceof Map<?, ?>) return 10;

throw new RuntimeException("Cannot serialize unknown type " + tag.getClass());
}

@SuppressWarnings("unchecked")
private static void writeTag(DataOutput out, byte type, Object tag) throws IOException {
switch (type) {
case 1: out.writeByte( (Byte)tag ); break;
case 2: out.writeShort( (Short)tag ); break;
case 3: out.writeInt( (Integer)tag ); break;
case 4: out.writeLong( (Long)tag ); break;
case 5: out.writeFloat( (Float)tag ); break;
case 6: out.writeDouble( (Double)tag ); break;

case 7: writeByteArray(out, (byte[])tag); break;
case 8: writeString(out, (String)tag); break;
case 9: writeList(out, (List<?>)tag); break;
case 10: writeCompound(out, (Map<String, ?>)tag); break;
default: throw new IOException("Invalid NBT tag type (1-10): " + type);
}
}

private static void writeByteArray(DataOutput out, byte[] array) throws IOException {
out.writeInt(array.length);
out.write(array);
}

private static void writeString(DataOutput out, String str) throws IOException {
out.writeUTF(str);
}

private static void writeList(DataOutput out, List<?> list) throws IOException {
byte type = list.isEmpty() ? 1 : whichType(list.get(0));
out.writeByte(type);
out.writeInt( list.size() );
for (Object tag : list) {
writeTag(out, type, tag);
}
}

private static void writeCompound(DataOutput out, Map<String, ?> map) throws IOException {
for (Map.Entry<String, ?> entry : map.entrySet()) {
byte type = whichType(entry.getValue());
out.writeByte(type);
writeString(out, entry.getKey());
writeTag(out, type, entry.getValue());
}
out.writeByte(0);
}
}

0 comments on commit 93c4ee6

Please sign in to comment.