diff --git a/build.gradle b/build.gradle
index 1e6f45d..0ba09f8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -39,7 +39,7 @@ buildscript {
}
}
dependencies {
- classpath 'net.saliman:gradle-cobertura-plugin:2.2.8' // Coveralls dependency
+ classpath 'net.saliman:gradle-cobertura-plugin:2.6.0' // Coveralls dependency
classpath 'nl.javadude.gradle.plugins:license-gradle-plugin:0.10.0'
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0'
}
@@ -83,8 +83,8 @@ license {
// Source compiler configuration
configure([compileJava, compileTestJava]) {
- sourceCompatibility = '1.7'
- targetCompatibility = '1.7'
+ sourceCompatibility = '1.8'
+ targetCompatibility = '1.8'
options.encoding = 'UTF-8'
options.compilerArgs << '-Xlint:all'
options.compilerArgs << '-Xlint:-path'
diff --git a/pom.xml b/pom.xml
index fff3697..a799999 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
Flow NBT
com.flowpowered
flow-nbt
- 1.0.1-SNAPSHOT
+ 1.0.2-SNAPSHOT
jar
2011
https://flowpowered.com/nbt
diff --git a/src/main/java/com/flowpowered/nbt/ByteArrayTag.java b/src/main/java/com/flowpowered/nbt/ByteArrayTag.java
index 1c2d1bf..bad1352 100644
--- a/src/main/java/com/flowpowered/nbt/ByteArrayTag.java
+++ b/src/main/java/com/flowpowered/nbt/ByteArrayTag.java
@@ -32,7 +32,7 @@ public final class ByteArrayTag extends Tag {
/**
* The value.
*/
- private final byte[] value;
+ private byte[] value;
/**
* Creates the tag.
@@ -50,6 +50,11 @@ public byte[] getValue() {
return value;
}
+ @Override
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
StringBuilder hex = new StringBuilder();
diff --git a/src/main/java/com/flowpowered/nbt/ByteTag.java b/src/main/java/com/flowpowered/nbt/ByteTag.java
index d0232e3..284922d 100644
--- a/src/main/java/com/flowpowered/nbt/ByteTag.java
+++ b/src/main/java/com/flowpowered/nbt/ByteTag.java
@@ -30,7 +30,7 @@ public final class ByteTag extends Tag {
/**
* The value.
*/
- private final byte value;
+ private byte value;
/**
* Creates the tag.
Boolean true is stored as 1 and boolean false is stored as 0.
@@ -58,6 +58,11 @@ public Byte getValue() {
return value;
}
+ @Override
+ public void setValue(Byte value) {
+ this.value = value;
+ }
+
public boolean getBooleanValue() {
return value != 0;
}
diff --git a/src/main/java/com/flowpowered/nbt/CompoundMap.java b/src/main/java/com/flowpowered/nbt/CompoundMap.java
index 1124d03..04512d2 100644
--- a/src/main/java/com/flowpowered/nbt/CompoundMap.java
+++ b/src/main/java/com/flowpowered/nbt/CompoundMap.java
@@ -117,7 +117,7 @@ public CompoundMap(Iterable> initial, boolean sort, boolean reverse) {
}
}
if (initial != null) {
- for (Tag t : initial) {
+ for (Tag> t : initial) {
put(t);
}
}
@@ -205,8 +205,8 @@ public boolean equals(Object o) {
Iterator> iThis = iterator();
Iterator> iOther = other.iterator();
while (iThis.hasNext() && iOther.hasNext()) {
- Tag tThis = iThis.next();
- Tag tOther = iOther.next();
+ Tag> tThis = iThis.next();
+ Tag> tOther = iOther.next();
if (!tThis.equals(tOther)) {
return false;
}
diff --git a/src/main/java/com/flowpowered/nbt/CompoundTag.java b/src/main/java/com/flowpowered/nbt/CompoundTag.java
index 749933e..3cddd51 100644
--- a/src/main/java/com/flowpowered/nbt/CompoundTag.java
+++ b/src/main/java/com/flowpowered/nbt/CompoundTag.java
@@ -30,7 +30,7 @@ public class CompoundTag extends Tag {
/**
* The value.
*/
- private final CompoundMap value;
+ private CompoundMap value;
/**
* Creates the tag.
@@ -49,6 +49,11 @@ public CompoundMap getValue() {
return value;
}
+ @Override
+ public void setValue(CompoundMap value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
@@ -59,14 +64,15 @@ public String toString() {
StringBuilder bldr = new StringBuilder();
bldr.append("TAG_Compound").append(append).append(": ").append(value.size()).append(" entries\r\n{\r\n");
- for (Tag entry : value.values()) {
+ for (Tag> entry : value.values()) {
bldr.append(" ").append(entry.toString().replaceAll("\r\n", "\r\n ")).append("\r\n");
}
bldr.append("}");
return bldr.toString();
}
- public CompoundTag clone() {
+ @Override
+ public CompoundTag clone() {
CompoundMap map = new CompoundMap(value);
return new CompoundTag(getName(), map);
}
diff --git a/src/main/java/com/flowpowered/nbt/DoubleTag.java b/src/main/java/com/flowpowered/nbt/DoubleTag.java
index 678982c..d366089 100644
--- a/src/main/java/com/flowpowered/nbt/DoubleTag.java
+++ b/src/main/java/com/flowpowered/nbt/DoubleTag.java
@@ -30,7 +30,7 @@ public final class DoubleTag extends Tag {
/**
* The value.
*/
- private final double value;
+ private double value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public Double getValue() {
return value;
}
+ @Override
+ public void setValue(Double value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/EndTag.java b/src/main/java/com/flowpowered/nbt/EndTag.java
index fd96593..a3614d5 100644
--- a/src/main/java/com/flowpowered/nbt/EndTag.java
+++ b/src/main/java/com/flowpowered/nbt/EndTag.java
@@ -39,6 +39,11 @@ public Object getValue() {
return null;
}
+ @Override
+ public void setValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public String toString() {
return "TAG_End";
diff --git a/src/main/java/com/flowpowered/nbt/FloatTag.java b/src/main/java/com/flowpowered/nbt/FloatTag.java
index c5b1cbc..1c36784 100644
--- a/src/main/java/com/flowpowered/nbt/FloatTag.java
+++ b/src/main/java/com/flowpowered/nbt/FloatTag.java
@@ -30,7 +30,7 @@ public final class FloatTag extends Tag {
/**
* The value.
*/
- private final float value;
+ private float value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public Float getValue() {
return value;
}
+ @Override
+ public void setValue(Float value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/IntArrayTag.java b/src/main/java/com/flowpowered/nbt/IntArrayTag.java
index 4fb7fdc..373dccc 100644
--- a/src/main/java/com/flowpowered/nbt/IntArrayTag.java
+++ b/src/main/java/com/flowpowered/nbt/IntArrayTag.java
@@ -29,7 +29,7 @@ public class IntArrayTag extends Tag {
/**
* The value.
*/
- private final int[] value;
+ private int[] value;
/**
* Creates the tag.
@@ -47,6 +47,11 @@ public int[] getValue() {
return value;
}
+ @Override
+ public void setValue(int[] value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
StringBuilder hex = new StringBuilder();
diff --git a/src/main/java/com/flowpowered/nbt/IntTag.java b/src/main/java/com/flowpowered/nbt/IntTag.java
index fe66bbf..e8d1396 100644
--- a/src/main/java/com/flowpowered/nbt/IntTag.java
+++ b/src/main/java/com/flowpowered/nbt/IntTag.java
@@ -30,7 +30,7 @@ public final class IntTag extends Tag {
/**
* The value.
*/
- private final int value;
+ private int value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public Integer getValue() {
return value;
}
+ @Override
+ public void setValue(Integer value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/ListTag.java b/src/main/java/com/flowpowered/nbt/ListTag.java
index e2317b4..97ba63e 100644
--- a/src/main/java/com/flowpowered/nbt/ListTag.java
+++ b/src/main/java/com/flowpowered/nbt/ListTag.java
@@ -38,7 +38,7 @@ public class ListTag> extends Tag> {
/**
* The value.
*/
- private final List value;
+ private List value;
/**
* Creates the tag.
@@ -50,7 +50,7 @@ public class ListTag> extends Tag> {
public ListTag(String name, Class type, List value) {
super(TagType.TAG_LIST, name);
this.type = type;
- this.value = Collections.unmodifiableList(value);
+ this.value = value;
}
/**
@@ -67,6 +67,11 @@ public List getValue() {
return value;
}
+ @Override
+ public void setValue(List value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
@@ -77,14 +82,15 @@ public String toString() {
StringBuilder bldr = new StringBuilder();
bldr.append("TAG_List").append(append).append(": ").append(value.size()).append(" entries of type ").append(TagType.getByTagClass(type).getTypeName()).append("\r\n{\r\n");
- for (Tag t : value) {
+ for (Tag> t : value) {
bldr.append(" ").append(t.toString().replaceAll("\r\n", "\r\n ")).append("\r\n");
}
bldr.append("}");
return bldr.toString();
}
- @SuppressWarnings ("unchecked")
+ @Override
+ @SuppressWarnings ("unchecked")
public ListTag clone() {
List newList = new ArrayList();
diff --git a/src/main/java/com/flowpowered/nbt/LongArrayTag.java b/src/main/java/com/flowpowered/nbt/LongArrayTag.java
new file mode 100644
index 0000000..b0ae06f
--- /dev/null
+++ b/src/main/java/com/flowpowered/nbt/LongArrayTag.java
@@ -0,0 +1,101 @@
+/*
+ * This file is part of Flow NBT, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2011 Flow Powered
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.flowpowered.nbt;
+
+import java.util.Arrays;
+
+public class LongArrayTag extends Tag {
+ /**
+ * The value.
+ */
+ private long[] value;
+
+ /**
+ * Creates the tag.
+ *
+ * @param name The name.
+ * @param value The value.
+ */
+ public LongArrayTag(String name, long[] value) {
+ super(TagType.TAG_LONG_ARRAY, name);
+ this.value = value;
+ }
+
+ @Override
+ public long[] getValue() {
+ return value;
+ }
+
+ @Override
+ public void setValue(long[] value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder hex = new StringBuilder();
+ for (long s : value) {
+ String hexDigits = Long.toHexString(s).toUpperCase();
+ if (hexDigits.length() == 1) {
+ hex.append("0");
+ }
+ hex.append(hexDigits).append(" ");
+ }
+
+ String name = getName();
+ String append = "";
+ if (name != null && !name.equals("")) {
+ append = "(\"" + this.getName() + "\")";
+ }
+ return "TAG_Long_Array" + append + ": " + hex.toString();
+ }
+
+ @Override
+ public LongArrayTag clone() {
+ long[] clonedArray = cloneArray(value);
+
+ return new LongArrayTag(getName(), clonedArray);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof LongArrayTag)) {
+ return false;
+ }
+
+ LongArrayTag tag = (LongArrayTag) other;
+ return Arrays.equals(value, tag.value) && getName().equals(tag.getName());
+ }
+
+ private long[] cloneArray(long[] longArray) {
+ if (longArray == null) {
+ return null;
+ } else {
+ int length = longArray.length;
+ byte[] newArray = new byte[length];
+ System.arraycopy(longArray, 0, newArray, 0, length);
+ return longArray;
+ }
+ }
+}
diff --git a/src/main/java/com/flowpowered/nbt/LongTag.java b/src/main/java/com/flowpowered/nbt/LongTag.java
index d0e023e..dac34a1 100644
--- a/src/main/java/com/flowpowered/nbt/LongTag.java
+++ b/src/main/java/com/flowpowered/nbt/LongTag.java
@@ -30,7 +30,7 @@ public final class LongTag extends Tag {
/**
* The value.
*/
- private final long value;
+ private long value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public Long getValue() {
return value;
}
+ @Override
+ public void setValue(Long value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/NBTConstants.java b/src/main/java/com/flowpowered/nbt/NBTConstants.java
index 4400597..6903cbd 100644
--- a/src/main/java/com/flowpowered/nbt/NBTConstants.java
+++ b/src/main/java/com/flowpowered/nbt/NBTConstants.java
@@ -49,6 +49,7 @@ public final class NBTConstants {
TYPE_LIST = TagType.TAG_LIST.getId(),
TYPE_COMPOUND = TagType.TAG_COMPOUND.getId(),
TYPE_INT_ARRAY = TagType.TAG_INT_ARRAY.getId(),
+ TYPE_LONG_ARRAY = TagType.TAG_LONG_ARRAY.getId(),
TYPE_SHORT_ARRAY = TagType.TAG_SHORT_ARRAY.getId();
/**
diff --git a/src/main/java/com/flowpowered/nbt/NBTTester.java b/src/main/java/com/flowpowered/nbt/NBTTester.java
index 234ca9d..e4a69f8 100644
--- a/src/main/java/com/flowpowered/nbt/NBTTester.java
+++ b/src/main/java/com/flowpowered/nbt/NBTTester.java
@@ -60,7 +60,7 @@ public static void main(String[] args) {
}
try {
- Tag tag = input.readTag();
+ Tag> tag = input.readTag();
System.out.println("NBT data from file: " + argFile.getCanonicalPath());
System.out.println(tag);
} catch (IOException e) {
diff --git a/src/main/java/com/flowpowered/nbt/ShortArrayTag.java b/src/main/java/com/flowpowered/nbt/ShortArrayTag.java
index 37f32d9..a8a08f0 100644
--- a/src/main/java/com/flowpowered/nbt/ShortArrayTag.java
+++ b/src/main/java/com/flowpowered/nbt/ShortArrayTag.java
@@ -29,7 +29,7 @@ public class ShortArrayTag extends Tag {
/**
* The value.
*/
- private final short[] value;
+ private short[] value;
/**
* Creates the tag.
@@ -47,6 +47,11 @@ public short[] getValue() {
return value;
}
+ @Override
+ public void setValue(short[] value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
StringBuilder hex = new StringBuilder();
diff --git a/src/main/java/com/flowpowered/nbt/ShortTag.java b/src/main/java/com/flowpowered/nbt/ShortTag.java
index 11f9e28..22d94fa 100644
--- a/src/main/java/com/flowpowered/nbt/ShortTag.java
+++ b/src/main/java/com/flowpowered/nbt/ShortTag.java
@@ -30,7 +30,7 @@ public final class ShortTag extends Tag {
/**
* The value.
*/
- private final short value;
+ private short value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public Short getValue() {
return value;
}
+ @Override
+ public void setValue(Short value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/StringTag.java b/src/main/java/com/flowpowered/nbt/StringTag.java
index d974c7a..d8ff32f 100644
--- a/src/main/java/com/flowpowered/nbt/StringTag.java
+++ b/src/main/java/com/flowpowered/nbt/StringTag.java
@@ -30,7 +30,7 @@ public final class StringTag extends Tag {
/**
* The value.
*/
- private final String value;
+ private String value;
/**
* Creates the tag.
@@ -48,6 +48,11 @@ public String getValue() {
return value;
}
+ @Override
+ public void setValue(String value) {
+ this.value = value;
+ }
+
@Override
public String toString() {
String name = getName();
diff --git a/src/main/java/com/flowpowered/nbt/Tag.java b/src/main/java/com/flowpowered/nbt/Tag.java
index c76cbba..f4fb8cc 100644
--- a/src/main/java/com/flowpowered/nbt/Tag.java
+++ b/src/main/java/com/flowpowered/nbt/Tag.java
@@ -79,6 +79,13 @@ public TagType getType() {
*/
public abstract T getValue();
+ /**
+ * Sets the value of this tag
+ *
+ * @param value The value of this tag.
+ */
+ public abstract void setValue(T value);
+
/**
* Clones a Map
*
@@ -107,7 +114,7 @@ public boolean equals(Object other) {
}
@Override
- public int compareTo(Tag other) {
+ public int compareTo(Tag> other) {
if (equals(other)) {
return 0;
} else {
@@ -124,5 +131,6 @@ public int compareTo(Tag other) {
*
* @return the clone
*/
- public abstract Tag clone();
+ @Override
+ public abstract Tag clone();
}
diff --git a/src/main/java/com/flowpowered/nbt/TagType.java b/src/main/java/com/flowpowered/nbt/TagType.java
index 96beede..330cdf7 100644
--- a/src/main/java/com/flowpowered/nbt/TagType.java
+++ b/src/main/java/com/flowpowered/nbt/TagType.java
@@ -40,6 +40,7 @@ public enum TagType {
// Java generics, y u so suck
TAG_COMPOUND(CompoundTag.class, "TAG_Compound", 10),
TAG_INT_ARRAY(IntArrayTag.class, "TAG_Int_Array", 11),
+ TAG_LONG_ARRAY(LongArrayTag.class, "TAG_Long_Array", 12),
TAG_SHORT_ARRAY(ShortArrayTag.class, "TAG_Short_Array", 100),;
private static final Map>, TagType> BY_CLASS = new HashMap>, TagType>();
private static final Map BY_NAME = new HashMap();
diff --git a/src/main/java/com/flowpowered/nbt/exception/InvalidTagException.java b/src/main/java/com/flowpowered/nbt/exception/InvalidTagException.java
index cfa728e..38a4686 100644
--- a/src/main/java/com/flowpowered/nbt/exception/InvalidTagException.java
+++ b/src/main/java/com/flowpowered/nbt/exception/InvalidTagException.java
@@ -26,7 +26,10 @@
import com.flowpowered.nbt.Tag;
public class InvalidTagException extends Exception {
- public InvalidTagException(Tag t) {
+
+ private static final long serialVersionUID = -5446188798223632410L;
+
+ public InvalidTagException(Tag> t) {
System.out.println("Invalid tag: " + t.toString() + " encountered!");
}
}
diff --git a/src/main/java/com/flowpowered/nbt/gui/NBTViewer.java b/src/main/java/com/flowpowered/nbt/gui/NBTViewer.java
index a3d6ddc..935d081 100644
--- a/src/main/java/com/flowpowered/nbt/gui/NBTViewer.java
+++ b/src/main/java/com/flowpowered/nbt/gui/NBTViewer.java
@@ -34,6 +34,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
@@ -99,7 +100,8 @@ public NBTViewer() {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
- public void run() {
+ @Override
+ public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
@@ -141,12 +143,12 @@ private void openFile() {
}
private List> readFile(File f) {
- List> tags = readRawNBT(f, true);
+ List> tags = readRawNBT(f, NBTInputStream.GZIP_COMPRESSION);
if (tags != null) {
format = "Compressed NBT";
return tags;
}
- tags = readRawNBT(f, false);
+ tags = readRawNBT(f, NBTInputStream.NO_COMPRESSION);
if (tags != null) {
format = "Uncompressed NBT";
return tags;
@@ -166,11 +168,11 @@ private List> readFile(File f) {
return null;
}
- private List> readRawNBT(File f, boolean compressed) {
+ private List> readRawNBT(File f, int compression) {
List> tags = new ArrayList>();
try {
InputStream is = new FileInputStream(f);
- NBTInputStream ns = new NBTInputStream(is, compressed);
+ NBTInputStream ns = new NBTInputStream(is, compression);
try {
boolean eof = false;
while (!eof) {
diff --git a/src/main/java/com/flowpowered/nbt/holder/FieldHolder.java b/src/main/java/com/flowpowered/nbt/holder/FieldHolder.java
index 7d1fa29..9a7e71a 100644
--- a/src/main/java/com/flowpowered/nbt/holder/FieldHolder.java
+++ b/src/main/java/com/flowpowered/nbt/holder/FieldHolder.java
@@ -67,22 +67,44 @@ public void load(CompoundTag tag) {
}
}
+ @Deprecated
public void save(File file, boolean compressed) throws IOException {
save(new FileOutputStream(file), compressed);
}
+ public void save(File file, int compression) throws IOException {
+ save(new FileOutputStream(file), compression);
+ }
+
+ @Deprecated
public void save(OutputStream stream, boolean compressed) throws IOException {
- NBTOutputStream os = new NBTOutputStream(stream, compressed);
+ save(stream, compressed ? NBTInputStream.GZIP_COMPRESSION : NBTInputStream.NO_COMPRESSION);
+ }
+
+ public void save(OutputStream stream, int compression) throws IOException {
+ NBTOutputStream os = new NBTOutputStream(stream, compression);
os.writeTag(new CompoundTag("", save()));
+ os.close();
}
+ @Deprecated
public void load(File file, boolean compressed) throws IOException {
load(new FileInputStream(file), compressed);
}
+ public void load(File file, int compression) throws IOException {
+ load(new FileInputStream(file), compression);
+ }
+
+ @Deprecated
public void load(InputStream stream, boolean compressed) throws IOException {
- NBTInputStream is = new NBTInputStream(stream, compressed);
+ load(stream, compressed ? NBTInputStream.GZIP_COMPRESSION : NBTInputStream.NO_COMPRESSION);
+ }
+
+ public void load(InputStream stream, int compression) throws IOException {
+ NBTInputStream is = new NBTInputStream(stream, compression);
Tag> tag = is.readTag();
+ is.close();
if (!(tag instanceof CompoundTag)) {
throw new IllegalArgumentException("Expected CompoundTag, got " + tag.getClass());
}
diff --git a/src/main/java/com/flowpowered/nbt/holder/FieldValue.java b/src/main/java/com/flowpowered/nbt/holder/FieldValue.java
index 3537ba8..d7cc875 100644
--- a/src/main/java/com/flowpowered/nbt/holder/FieldValue.java
+++ b/src/main/java/com/flowpowered/nbt/holder/FieldValue.java
@@ -53,7 +53,7 @@ public FieldValue(String key, Field field, T defaultValue) {
* @return The value
*/
public T load(CompoundTag tag) {
- Tag subTag = tag.getValue().get(key);
+ Tag> subTag = tag.getValue().get(key);
if (subTag == null) {
return (value = defaultValue);
}
@@ -67,7 +67,7 @@ public void save(CompoundMap tag) {
return;
}
}
- Tag t = field.getValue(key, value);
+ Tag> t = field.getValue(key, value);
tag.put(t);
}
diff --git a/src/main/java/com/flowpowered/nbt/itemmap/StringMapReader.java b/src/main/java/com/flowpowered/nbt/itemmap/StringMapReader.java
index 117f528..f069024 100644
--- a/src/main/java/com/flowpowered/nbt/itemmap/StringMapReader.java
+++ b/src/main/java/com/flowpowered/nbt/itemmap/StringMapReader.java
@@ -38,9 +38,8 @@ public class StringMapReader {
public static List> readFile(File f) {
List> list = new ArrayList>();
- try {
- FileInputStream fis = new FileInputStream(f);
- DataInputStream dis = new DataInputStream(fis);
+ try (FileInputStream fis = new FileInputStream(f);
+ DataInputStream dis = new DataInputStream(fis);){
boolean eof = false;
while (!eof) {
int value;
diff --git a/src/main/java/com/flowpowered/nbt/regionfile/Chunk.java b/src/main/java/com/flowpowered/nbt/regionfile/Chunk.java
new file mode 100644
index 0000000..5fa4800
--- /dev/null
+++ b/src/main/java/com/flowpowered/nbt/regionfile/Chunk.java
@@ -0,0 +1,298 @@
+package com.flowpowered.nbt.regionfile;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import com.flowpowered.nbt.CompoundMap;
+import com.flowpowered.nbt.CompoundTag;
+import com.flowpowered.nbt.DoubleTag;
+import com.flowpowered.nbt.IntTag;
+import com.flowpowered.nbt.ListTag;
+import com.flowpowered.nbt.Tag;
+import com.flowpowered.nbt.stream.NBTInputStream;
+import com.flowpowered.nbt.stream.NBTOutputStream;
+
+/**
+ * Each instance of this class represents a Minecraft chunk within a Region file. The data is represented as a binary blob in form of a
+ * {@link ByteBuffer}. Each object is self-contained and not linked to any physical file or other object. Each object is immutable and
+ * should be treated as such. The data is set in the constructor
+ * The data must have a length of a multiple of 4096 bytes (to be able to write it to disk more easily). The four first bytes specify the
+ * amount of actual data bytes in the buffer following. The fifth byte contains the compression method. All following bytes are NBT data.
+ *
+ * @author piegames
+ */
+public class Chunk {
+ /** The x coordinate of the chunk relative to its RegionFile's origin */
+ public final int x;
+ /** The z coordinate of the chunk relative to its RegionFile's origin */
+ public final int z;
+ /**
+ * The point in time when this chunk last got written. Equal to {@code (int) (System.currentTimeMillis() / 1000L)}
+ */
+ public final int timestamp;
+
+ protected final ByteBuffer data;
+
+ /**
+ * Create a Chunk object by setting its data directly through a buffer.
+ *
+ * @param x
+ * The x coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param z
+ * The z coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param data
+ * The data of the chunk
+ * @throws IllegalArgumentException
+ * if the coordinates are out of bounds or the data does not have a size multiple of 4096
+ * @author piegames
+ */
+ public Chunk(int x, int z, int timestamp, ByteBuffer data) {
+ if (x < 0 || z < 0 || x >= 32 || z >= 32)
+ throw new IllegalArgumentException("Coordinates must be in range [0..32), but were x=" + x + ", z=" + z + ")");
+ this.x = x;
+ this.z = z;
+ this.timestamp = timestamp;
+
+ if ((data.capacity() & 4095) != 0)
+ throw new IllegalArgumentException("Data buffer size must be multiple of 4096, but is " + data.capacity());
+ this.data = Objects.requireNonNull(data);
+ }
+
+ /**
+ * Create a Chunk object by reading specified data from a region file (*.mca, *.mcr).
+ *
+ * @param x
+ * The x coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param z
+ * The z coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param raf
+ * The file channel to the region file from which to load the data
+ * @param start
+ * The number of the 4096 byte sector where the chunk is located in the file. Don't forget that the first five bytes are used to
+ * store the size and compression of the chunk. The position of the first byte of NBT data is thus {@code start*4096 + 5}.
+ * @param length
+ * The amount of 4096 byte sectors to load. It should be large enough to contain all NBT data in that chunk or it will be
+ * corrupted.
+ * @throws IllegalArgumentException
+ * if the coordinates are out of bounds
+ * @author piegames
+ */
+ Chunk(int x, int z, int timestamp, FileChannel raf, int start, int length) throws IOException {
+ if (x < 0 || z < 0 || x >= 32 || z >= 32)
+ throw new IllegalArgumentException("Coordinates must be in range [0..32), but were x=" + x + ", z=" + z + ")");
+ this.x = x;
+ this.z = z;
+ this.timestamp = timestamp;
+
+ data = ByteBuffer.allocate(4096 * length);
+ raf.read(data, 4096 * start);
+ data.flip();
+ }
+
+ /**
+ * Create a Chunk object by filling the NBT tag's data to a {@link ByteBuffer} using the specified compression method.
+ *
+ * @param x
+ * The x coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param z
+ * The z coordinate of the chunk within its region file. Must be in the range of [0..32) because region files are always 32*32
+ * chunks large.
+ * @param data
+ * The NBT data the chunk will contain. Should be a {@link CompoundTag}
+ * @param compression
+ * The compression to use
+ * @throws IllegalArgumentException
+ * if the coordinates are out of bounds
+ * @author piegames
+ */
+ public Chunk(int x, int z, int timestamp, Tag> data, byte compression) throws IOException {
+ if (x < 0 || z < 0 || x >= 32 || z >= 32)
+ throw new IllegalArgumentException("Coordinates must be in range [0..32), but were x=" + x + ", z=" + z + ")");
+ this.x = x;
+ this.z = z;
+ this.timestamp = timestamp;
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
+ NBTOutputStream out = new NBTOutputStream(baos, compression)) {
+ out.writeTag(data);
+ out.flush();
+ out.close();
+
+ byte[] bytes = baos.toByteArray();
+ int sectionLength = (bytes.length + 5) / 4096 + 1;
+ this.data = ByteBuffer.allocate(sectionLength * 4096);
+ this.data.putInt(bytes.length + 1);
+ this.data.put(compression);
+ this.data.put(bytes);
+ this.data.flip();
+ }
+ }
+
+ /**
+ * Create a Chunk object identical to an existing one, except for a different timestamp.
+ *
+ * @param data
+ * The chunk to clone
+ * @author piegames
+ */
+ public Chunk(Chunk data, int timestamp) {
+ this.x = data.x;
+ this.z = data.z;
+ this.timestamp = timestamp;
+
+ this.data = data.data;
+ }
+
+ /**
+ * Returns the compression method used in this chunk as specified by the format. This value corresponds to the compression that an
+ * {@link NBTInputStream} takes in its constructor.
+ */
+ public byte getCompression() {
+ return data.get(4);
+ }
+
+ /**
+ * The real length of the NBT data in this chunk in bytes.
+ */
+ public int getRealLength() {
+ return data.getInt(0) - 1;
+ }
+
+ /**
+ * Get the amount of 4kiB-sized sectors on the hard disk that would be required to save this chunk.
+ */
+ public int getSectorLength() {
+ return (getRealLength() + 5) / 4096 + 1;
+ }
+
+ /**
+ * Returns the {@link ByteBuffer} containing all the data in this chunk, including the five bytes before the actual NBT data. It will always
+ * contain a multiple of 4096 bytes. Altering its content will result in undefined behavior!
+ */
+ public ByteBuffer getData() {
+ return data;
+ }
+
+ /**
+ * Open an {@link NBTInputStream} for reading the NBT data contained in that chunk.
+ */
+ public NBTInputStream getInputStream() throws IOException {
+ return new NBTInputStream(new ByteArrayInputStream(data.array(), 5, getRealLength()), getCompression());
+ }
+
+ /**
+ * Reads the NBT chunk data and returns it. The normally nameless root tag will be renamed to "chunk".
+ */
+ public CompoundTag readTag() throws IOException {
+ try (NBTInputStream nbtIn = getInputStream();) {
+ return new CompoundTag("chunk", ((CompoundTag) nbtIn.readTag()).getValue());
+ }
+ }
+
+ /**
+ * Return a timestamp in the format used by Minecraft representing the point in time this method was called
+ *
+ * @see #timestamp
+ */
+ public static int getCurrentTimestamp() {
+ return (int) (System.currentTimeMillis() / 1000L);
+ }
+
+ public static int bitsPerIndex(long[] blocks) {
+ /* There are {@code 16*16*16=4096} blocks in each chunk, and a long has 64 bits */
+ return blocks.length * 64 / 4096;
+ }
+
+ /**
+ * Extract a palette index from the long array. This data is located at {@code /Level/Sections[i]/BlockStates}.
+ *
+ * @param blocks
+ * a long array containing all the block states as Minecraft encodes them to {@code /Level/Sections[i]/BlockStates} within each
+ * section of a chunk.
+ * @param i
+ * The index of the block to be extracted. Since the data is mapped XZY, {@code i = x | (z<<4) | (y<<8)}.
+ * @param bitsPerIndex
+ * The amount of bits each index has. This is to avoid redundant calculation on each call.
+ *
+ * @see #bitsPerIndex(long[])
+ * @author piegames
+ */
+ public static long extractFromLong(long[] blocks, int i, int bitsPerIndex) {
+ int startByte = (bitsPerIndex * i) >> 6; // >> 6 equals / 64
+ int endByte = (bitsPerIndex * (i + 1)) >> 6;
+ // The bit within the long where our value starts. Counting from the right LSB (!).
+ int startByteBit = ((bitsPerIndex * i)) & 63; // % 64 equals & 63
+ int endByteBit = ((bitsPerIndex * (i + 1))) & 63;
+
+ // Use bit shifting and & bit masking to extract bit sequences out of longs as numbers
+ // -1L is the value with every bit set
+ long blockIndex;
+ if (startByte == endByte) {
+ // Normal case: the bit string we need is within a single long
+ blockIndex = (blocks[startByte] << (64 - endByteBit)) >>> (64 + startByteBit - endByteBit);
+ } else if (endByteBit == 0) {
+ // The bit string is exactly at the beginning of a long
+ blockIndex = blocks[startByte] >>> startByteBit;
+ } else {
+ // The bit string is overlapping two longs
+ blockIndex = ((blocks[startByte] >>> startByteBit))
+ | ((blocks[endByte] << (64 - endByteBit)) >>> (startByteBit - endByteBit));
+ }
+ return blockIndex;
+ }
+
+ /** Incubating utility method, use with care */
+ @SuppressWarnings("unchecked")
+ public static void moveChunk(CompoundTag level, int sourceX, int sourceZ, int destX, int destZ) {
+ CompoundMap value = level.getValue();
+ /* The difference in blocks between the two chunks */
+ int diffX = (destX - sourceX) << 4;
+ int diffY = 0;
+ int diffZ = (destZ - sourceZ) << 4;
+
+ value.put(new IntTag("xPos", destX));
+ value.put(new IntTag("zPos", destZ));
+
+ /* Update entities */
+ for (CompoundTag entity : ((ListTag) value.get("Entities")).getValue()) {
+ List pos = ((ListTag) entity.getValue().get("Pos")).getValue();
+ entity.getValue().put(new ListTag<>("Pos", DoubleTag.class,
+ Arrays.asList(
+ new DoubleTag(null, pos.get(0).getValue() + diffX),
+ new DoubleTag(null, pos.get(1).getValue() + diffY),
+ new DoubleTag(null, pos.get(2).getValue() + diffZ))));
+ }
+ /* Update tile entities */
+ for (CompoundTag tileEntity : ((ListTag) value.get("TileEntities")).getValue()) {
+ CompoundMap map = tileEntity.getValue();
+ map.put(new IntTag("x", ((IntTag) map.get("x")).getValue() + diffX));
+ map.put(new IntTag("y", ((IntTag) map.get("y")).getValue() + diffY));
+ map.put(new IntTag("z", ((IntTag) map.get("z")).getValue() + diffZ));
+ }
+ /* Update tile ticks */
+ for (CompoundTag tileTick : ((ListTag) value.get("TileTicks")).getValue()) {
+ CompoundMap map = tileTick.getValue();
+ map.put(new IntTag("x", ((IntTag) map.get("x")).getValue() + diffX));
+ map.put(new IntTag("y", ((IntTag) map.get("y")).getValue() + diffY));
+ map.put(new IntTag("z", ((IntTag) map.get("z")).getValue() + diffZ));
+ }
+ /* Update liquid ticks */
+ for (CompoundTag liquidTick : ((ListTag) value.get("LiquidTicks")).getValue()) {
+ CompoundMap map = liquidTick.getValue();
+ map.put(new IntTag("x", ((IntTag) map.get("x")).getValue() + diffX));
+ map.put(new IntTag("y", ((IntTag) map.get("y")).getValue() + diffY));
+ map.put(new IntTag("z", ((IntTag) map.get("z")).getValue() + diffZ));
+ }
+ }
+}
diff --git a/src/main/java/com/flowpowered/nbt/regionfile/RegionFile.java b/src/main/java/com/flowpowered/nbt/regionfile/RegionFile.java
new file mode 100644
index 0000000..70011ec
--- /dev/null
+++ b/src/main/java/com/flowpowered/nbt/regionfile/RegionFile.java
@@ -0,0 +1,242 @@
+package com.flowpowered.nbt.regionfile;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+/**
+ * This helper class provides functionality to read the data of single chunks in a region/anvil file. It uses modern {@code java.nio}
+ * classes like {@link Path} and {@link FileChannel} to access its data from the file. Each instance of the class represents a single file,
+ * whose header will be loaded in the constructor.
+ *
+ * @author piegames
+ */
+public class RegionFile implements Closeable {
+
+ protected final Path file;
+ protected FileChannel raf;
+
+ protected ByteBuffer locations;
+ protected IntBuffer locations2;
+ protected ByteBuffer timestamps;
+ protected IntBuffer timestamps2;
+
+ /**
+ * Create a new RegionFile object representing the region file at the given path and load it's header to memory.
+ *
+ * @throws IllegalArgumentException
+ * if the file is smaller than 4kiB
+ * @throws NoSuchFileException
+ * if the file does not exist
+ * @see #open(Path)
+ * @author piegames
+ */
+ public RegionFile(Path file) throws IOException {
+ this.file = Objects.requireNonNull(file);
+
+ if (!Files.exists(file))
+ throw new NoSuchFileException(file.toString());
+ if (Files.size(file) < 4096 * 2)
+ throw new IllegalArgumentException("File size must be at least 4kiB, is this file corrupt?");
+ raf = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
+
+ locations = ByteBuffer.allocate(4096);
+ raf.read(locations);
+ locations.flip();
+ locations2 = locations.asIntBuffer();
+
+ timestamps = ByteBuffer.allocate(4096);
+ raf.read(timestamps);
+ timestamps.flip();
+ timestamps2 = timestamps.asIntBuffer();
+ }
+
+ /**
+ * Load the {@link Chunk} at the given coordinate
+ *
+ * @see #coordsToPosition(int, int)
+ * @return the chunk at that coordinate or {@code null} if the chunk does not exist
+ * @throws IOException
+ * @author piegames
+ */
+ public Chunk loadChunk(int x, int z) throws IOException {
+ return loadChunk(coordsToPosition(x, z));
+ }
+
+ /** @see #loadChunk(int, int) */
+ public Chunk loadChunk(int i) throws IOException {
+ int chunkPos = locations2.get(i) >>> 8;
+ int chunkLength = locations2.get(i) & 0xFF;
+ if (chunkPos > 0) {
+ /* i & 31 retrieves the last 5 bit which store the x coordinate */
+ return new Chunk(i & 31, i >> 5, timestamps2.get(i), raf, chunkPos, chunkLength);
+ }
+ return null;
+ }
+
+ /**
+ * Tell if the file contains a chunk at this position.
+ *
+ * @return {@code true} if there is a chunk at this position
+ * @see #coordsToPosition(int, int)
+ */
+ public boolean hasChunk(int x, int z) {
+ return hasChunk((x & 31) | (z << 5));
+ }
+
+ /** @see #hasChunk(int, int) */
+ public boolean hasChunk(int i) {
+ return (locations2.get(i) >>> 8) > 0;
+ }
+
+ /**
+ * Same as {@link #listChunks()}, but as stream.
+ */
+ public Stream streamChunks() {
+ return IntStream.range(0, 32 * 32).filter(pos -> hasChunk(pos))
+ .boxed()
+ .sorted(Comparator.comparingInt(i -> locations2.get(i) >>> 8));
+ }
+
+ /**
+ * List the positions of all chunks that exist in this file sorted by their their appearance order in the file. Use this to read all chunks
+ * in their sequential order to speed up seek times.
+ *
+ * @see #coordsToPosition(int, int)
+ */
+ public List listChunks() {
+ return streamChunks().collect(Collectors.toList());
+ }
+
+ /**
+ * Write all given chunks to disk, update the file's header (chunk locations and timestamps) and truncate the file at the end
+ *
+ * @param changedChunks
+ * {@link HashMap} of all changes to write. Each key is the position of one changed chunk (use
+ * {@link #coordsToPosition(int, int)} to calculate the key from a coordinate). The map may contain {@code null} values,
+ * indicating that the chunk should be removed from the file.
+ * @author piegames
+ */
+ public void writeChunks(HashMap changedChunks) throws IOException {
+ synchronized (raf) {
+ /* Mark all 4kib sectors in the file if they are used. */
+ BitSet usedSectors = new BitSet();
+ /* Set the first two sectors as used since they always are (by the header) */
+ usedSectors.set(0, 2);
+
+ /* Mark the currently used sectors, but omit those that are going to be deleted or overwritten. */
+ for (int i = 0; i < 32 * 32; i++) {
+ int chunkPos = locations2.get(i) >>> 8;
+ int chunkLength = locations2.get(i) & 0xFF;
+ if (chunkLength > 0 && !changedChunks.containsKey(i))
+ usedSectors.set(chunkPos, chunkPos + chunkLength);
+ }
+
+ /* Iterate through all changed chunks and try to fit them in somewhere */
+ for (Integer chunkPos : changedChunks.keySet()) {
+ Chunk chunk = changedChunks.get(chunkPos);
+ if (chunk == null) {
+ /* Position zero, length zero */
+ locations2.put(chunkPos, 0);
+ } else {
+ int length = 0;
+ int start = 0;
+ /* Increase start until we found a solid place to put our data */
+ while (length < chunk.getSectorLength()) {
+ if (!usedSectors.get(start + length)) {
+ length++;
+ } else {
+ start = usedSectors.nextClearBit(start + length);
+ length = 0;
+ }
+ }
+ if (length > 255)
+ throw new IOException("Chunks are limited to a length of maximum 255 sectors, or ~1MiB");
+ { /* Write the chunk to disk */
+ raf.position(start * 4096);
+ raf.write(chunk.data);
+ timestamps2.put(chunkPos, chunk.timestamp);
+ }
+ locations2.put(chunkPos, start << 8 | length);
+ usedSectors.set(start, start + length);
+ }
+ }
+ /* Write updated header */
+ raf.position(0);
+ raf.write(locations);
+ raf.write(timestamps);
+ locations.flip();
+ timestamps.flip();
+
+ raf.truncate(4096 * usedSectors.previousSetBit(usedSectors.size()) + 4096);
+ }
+ changedChunks.clear();
+ }
+
+ /** Get the path this file is associated with. It will never change over time. */
+ public Path getPath() {
+ return file;
+ }
+
+ @Override
+ public void close() throws IOException {
+ raf.close();
+ }
+
+ /**
+ * Create a new region file by writing an empty header to it.
+ *
+ * @author piegames
+ */
+ public static RegionFile createNew(Path file) throws IOException {
+ try (FileChannel raf = FileChannel.open(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);) {
+ /* Write empty header */
+ raf.write(ByteBuffer.wrap(new byte[2 * 4096]));
+ }
+ return new RegionFile(file);
+ }
+
+ /**
+ * Open an existing region file, creating it if it does not exist
+ *
+ * @author piegames
+ */
+ public static RegionFile open(Path file) throws IOException {
+ if (Files.exists(file))
+ return new RegionFile(file);
+ else {
+ Files.createDirectories(file.getParent());
+ return createNew(file);
+ }
+ }
+
+ /**
+ * Convert a coordinate into a position index.
+ *
+ * @param x
+ * The x position of the chunk in chunk coordinates (1 unit <=> 16 blocks). The coordinate should be relative to the region
+ * file's position, but using the world's origin works fine as well.
+ * @param z
+ * The z position of the chunk in chunk coordinates (1 unit <=> 16 blocks). The coordinate should be relative to the region
+ * file's position, but using the world's origin works fine as well.
+ * @return The index of this chunk in the file. This is a number between 0 (inclusive) and 32*32 (exclusive). The coordinate is flattened in
+ * x-z-order.
+ */
+ public static int coordsToPosition(int x, int z) {
+ return (x & 31) | ((z & 31) << 5);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/flowpowered/nbt/regionfile/SimpleRegionFileReader.java b/src/main/java/com/flowpowered/nbt/regionfile/SimpleRegionFileReader.java
index 068980b..90bcebd 100644
--- a/src/main/java/com/flowpowered/nbt/regionfile/SimpleRegionFileReader.java
+++ b/src/main/java/com/flowpowered/nbt/regionfile/SimpleRegionFileReader.java
@@ -23,81 +23,33 @@
*/
package com.flowpowered.nbt.regionfile;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.ArrayList;
import java.util.List;
-import java.util.zip.InflaterInputStream;
+import java.util.Objects;
+import java.util.stream.Collectors;
import com.flowpowered.nbt.Tag;
-import com.flowpowered.nbt.stream.NBTInputStream;
+@Deprecated
public class SimpleRegionFileReader {
- private static int EXPECTED_VERSION = 1;
- public static List> readFile(File f) {
- RandomAccessFile raf;
- try {
- raf = new RandomAccessFile(f, "r");
- } catch (FileNotFoundException e) {
- return null;
- }
-
- try {
- int version = raf.readInt();
-
- if (version != EXPECTED_VERSION) {
- return null;
- }
-
- int segmentSize = raf.readInt();
- int segmentMask = (1 << segmentSize) - 1;
- int entries = raf.readInt();
-
- List> list = new ArrayList>(entries);
-
- int[] blockSegmentStart = new int[entries];
- int[] blockActualLength = new int[entries];
-
- for (int i = 0; i < entries; i++) {
- blockSegmentStart[i] = raf.readInt();
- blockActualLength[i] = raf.readInt();
- }
-
- for (int i = 0; i < entries; i++) {
- if (blockActualLength[i] == 0) {
- list.add(null);
- continue;
- }
- byte[] data = new byte[blockActualLength[i]];
- raf.seek(blockSegmentStart[i] << segmentSize);
- raf.readFully(data);
- ByteArrayInputStream in = new ByteArrayInputStream(data);
- InflaterInputStream iis = new InflaterInputStream(in);
- NBTInputStream ns = new NBTInputStream(iis, false);
- try {
- Tag> t = ns.readTag();
- list.add(t);
- } catch (IOException ioe) {
- list.add(null);
- }
- try {
- ns.close();
- } catch (IOException ioe) {
- }
- }
-
- return list;
- } catch (IOException ioe) {
- return null;
- } finally {
- try {
- raf.close();
- } catch (IOException ioe) {
- }
- }
- }
+ /** @deprecated Legacy crap. Use {@link RegionFile} directly instead. */
+ @Deprecated
+ public static List> readFile(File f) {
+ try (RegionFile file = new RegionFile(f.toPath())) {
+ return file.streamChunks()
+ .map(i -> {
+ try {
+ return file.loadChunk(i).readTag();
+ } catch (IOException e) {
+ return null;
+ }
+ })
+ .filter(Objects::isNull)
+ .collect(Collectors.toList());
+ } catch (IOException e1) {
+ return null;
+ }
+ }
}
diff --git a/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableInputStream.java b/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableInputStream.java
deleted file mode 100644
index f6c900f..0000000
--- a/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableInputStream.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * This file is part of Flow NBT, licensed under the MIT License (MIT).
- *
- * Copyright (c) 2011 Flow Powered
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package com.flowpowered.nbt.stream;
-
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteOrder;
-
-/**
- * A wrapper around {@link DataInputStream} that allows changing the endianness of data. By default, everything in Java is big-endian
- */
-public class EndianSwitchableInputStream extends FilterInputStream implements DataInput {
- private final ByteOrder endianness;
-
- public EndianSwitchableInputStream(InputStream stream, ByteOrder endianness) {
- super(stream instanceof DataInputStream ? stream : new DataInputStream(stream));
- this.endianness = endianness;
- }
-
- public ByteOrder getEndianness() {
- return endianness;
- }
-
- protected DataInputStream getBackingStream() {
- return (DataInputStream) super.in;
- }
-
- public void readFully(byte[] bytes) throws IOException {
- getBackingStream().readFully(bytes);
- }
-
- public void readFully(byte[] bytes, int i, int i1) throws IOException {
- getBackingStream().readFully(bytes, i, i1);
- }
-
- public int skipBytes(int i) throws IOException {
- return getBackingStream().skipBytes(i);
- }
-
- public boolean readBoolean() throws IOException {
- return getBackingStream().readBoolean();
- }
-
- public byte readByte() throws IOException {
- return getBackingStream().readByte();
- }
-
- public int readUnsignedByte() throws IOException {
- return getBackingStream().readUnsignedByte();
- }
-
- public short readShort() throws IOException {
- short ret = getBackingStream().readShort();
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- ret = Short.reverseBytes(ret);
- }
- return ret;
- }
-
- public int readUnsignedShort() throws IOException {
- int ret = getBackingStream().readUnsignedShort();
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- ret = (char) (Integer.reverseBytes(ret) >> 16);
- }
- return ret;
- }
-
- public char readChar() throws IOException {
- char ret = getBackingStream().readChar();
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- ret = Character.reverseBytes(ret);
- }
- return ret;
- }
-
- public int readInt() throws IOException {
- return endianness == ByteOrder.LITTLE_ENDIAN ? Integer.reverseBytes(getBackingStream().readInt()) : getBackingStream().readInt();
- }
-
- public long readLong() throws IOException {
- return endianness == ByteOrder.LITTLE_ENDIAN ? Long.reverseBytes(getBackingStream().readLong()) : getBackingStream().readLong();
- }
-
- public float readFloat() throws IOException {
- int result = readInt();
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- result = Integer.reverseBytes(result);
- }
- return Float.intBitsToFloat(result);
- }
-
- public double readDouble() throws IOException {
- long result = readLong();
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- result = Long.reverseBytes(result);
- }
- return Double.longBitsToDouble(result);
- }
-
- @SuppressWarnings ("deprecation") // This method is deprecated
- public String readLine() throws IOException {
- return getBackingStream().readLine();
- }
-
- public String readUTF() throws IOException {
- return getBackingStream().readUTF();
- }
-}
diff --git a/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableOutputStream.java b/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableOutputStream.java
deleted file mode 100644
index a64de79..0000000
--- a/src/main/java/com/flowpowered/nbt/stream/EndianSwitchableOutputStream.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * This file is part of Flow NBT, licensed under the MIT License (MIT).
- *
- * Copyright (c) 2011 Flow Powered
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-package com.flowpowered.nbt.stream;
-
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteOrder;
-
-public class EndianSwitchableOutputStream extends FilterOutputStream implements DataOutput {
- private final ByteOrder endianness;
-
- public EndianSwitchableOutputStream(OutputStream backingStream, ByteOrder endianness) {
- super(backingStream instanceof DataOutputStream ? (DataOutputStream) backingStream : new DataOutputStream(backingStream));
- this.endianness = endianness;
- }
-
- public ByteOrder getEndianness() {
- return endianness;
- }
-
- protected DataOutputStream getBackingStream() {
- return (DataOutputStream) super.out;
- }
-
- public void writeBoolean(boolean b) throws IOException {
- getBackingStream().writeBoolean(b);
- }
-
- public void writeByte(int i) throws IOException {
- getBackingStream().writeByte(i);
- }
-
- public void writeShort(int i) throws IOException {
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- i = Integer.reverseBytes(i) >> 16;
- }
- getBackingStream().writeShort(i);
- }
-
- public void writeChar(int i) throws IOException {
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- i = Character.reverseBytes((char) i);
- }
- getBackingStream().writeChar(i);
- }
-
- public void writeInt(int i) throws IOException {
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- i = Integer.reverseBytes(i);
- }
- getBackingStream().writeInt(i);
- }
-
- public void writeLong(long l) throws IOException {
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- l = Long.reverseBytes(l);
- }
- getBackingStream().writeLong(l);
- }
-
- public void writeFloat(float v) throws IOException {
- int intBits = Float.floatToIntBits(v);
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- intBits = Integer.reverseBytes(intBits);
- }
- getBackingStream().writeInt(intBits);
- }
-
- public void writeDouble(double v) throws IOException {
- long longBits = Double.doubleToLongBits(v);
- if (endianness == ByteOrder.LITTLE_ENDIAN) {
- longBits = Long.reverseBytes(longBits);
- }
- getBackingStream().writeLong(longBits);
- }
-
- public void writeBytes(String s) throws IOException {
- getBackingStream().writeBytes(s);
- }
-
- public void writeChars(String s) throws IOException {
- getBackingStream().writeChars(s);
- }
-
- public void writeUTF(String s) throws IOException {
- getBackingStream().writeUTF(s);
- }
-}
diff --git a/src/main/java/com/flowpowered/nbt/stream/LittleEndianInputStream.java b/src/main/java/com/flowpowered/nbt/stream/LittleEndianInputStream.java
new file mode 100644
index 0000000..33cc426
--- /dev/null
+++ b/src/main/java/com/flowpowered/nbt/stream/LittleEndianInputStream.java
@@ -0,0 +1,126 @@
+/*
+ * This file is part of Flow NBT, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2011 Flow Powered
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.flowpowered.nbt.stream;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+
+/**
+ * A wrapper around {@link DataInputStream} that allows changing the endianness of data. By default, everything in Java is big-endian
+ */
+public class LittleEndianInputStream extends FilterInputStream implements DataInput {
+
+ public LittleEndianInputStream(InputStream stream) {
+ super(stream instanceof DataInputStream ? stream : new DataInputStream(stream));
+ }
+
+ @Deprecated
+ public ByteOrder getEndianness() {
+ return ByteOrder.LITTLE_ENDIAN;
+ }
+
+ protected DataInputStream getBackingStream() {
+ return (DataInputStream) super.in;
+ }
+
+ @Override
+ public void readFully(byte[] bytes) throws IOException {
+ getBackingStream().readFully(bytes);
+ }
+
+ @Override
+ public void readFully(byte[] bytes, int i, int i1) throws IOException {
+ getBackingStream().readFully(bytes, i, i1);
+ }
+
+ @Override
+ public int skipBytes(int i) throws IOException {
+ return getBackingStream().skipBytes(i);
+ }
+
+ @Override
+ public boolean readBoolean() throws IOException {
+ return getBackingStream().readBoolean();
+ }
+
+ @Override
+ public byte readByte() throws IOException {
+ return getBackingStream().readByte();
+ }
+
+ @Override
+ public int readUnsignedByte() throws IOException {
+ return getBackingStream().readUnsignedByte();
+ }
+
+ @Override
+ public short readShort() throws IOException {
+ return Short.reverseBytes(getBackingStream().readShort());
+ }
+
+ @Override
+ public int readUnsignedShort() throws IOException {
+ return (char) (Integer.reverseBytes(getBackingStream().readUnsignedShort()) >> 16);
+ }
+
+ @Override
+ public char readChar() throws IOException {
+ return Character.reverseBytes(getBackingStream().readChar());
+ }
+
+ @Override
+ public int readInt() throws IOException {
+ return Integer.reverseBytes(getBackingStream().readInt());
+ }
+
+ @Override
+ public long readLong() throws IOException {
+ return Long.reverseBytes(getBackingStream().readLong());
+ }
+
+ @Override
+ public float readFloat() throws IOException {
+ return Float.intBitsToFloat(Integer.reverseBytes(readInt()));
+ }
+
+ @Override
+ public double readDouble() throws IOException {
+ return Double.longBitsToDouble(Long.reverseBytes(readLong()));
+ }
+
+ @Override
+ @SuppressWarnings("deprecation") // This method is deprecated
+ public String readLine() throws IOException {
+ return getBackingStream().readLine();
+ }
+
+ @Override
+ public String readUTF() throws IOException {
+ return getBackingStream().readUTF();
+ }
+}
diff --git a/src/main/java/com/flowpowered/nbt/stream/LittleEndianOutputStream.java b/src/main/java/com/flowpowered/nbt/stream/LittleEndianOutputStream.java
new file mode 100644
index 0000000..d84d06c
--- /dev/null
+++ b/src/main/java/com/flowpowered/nbt/stream/LittleEndianOutputStream.java
@@ -0,0 +1,102 @@
+/*
+ * This file is part of Flow NBT, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2011 Flow Powered
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.flowpowered.nbt.stream;
+
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+
+public class LittleEndianOutputStream extends FilterOutputStream implements DataOutput {
+
+ public LittleEndianOutputStream(OutputStream backingStream) {
+ super(backingStream instanceof DataOutputStream ? (DataOutputStream) backingStream : new DataOutputStream(backingStream));
+ }
+
+ @Deprecated
+ public ByteOrder getEndianness() {
+ return ByteOrder.LITTLE_ENDIAN;
+ }
+
+ protected DataOutputStream getBackingStream() {
+ return (DataOutputStream) super.out;
+ }
+
+ @Override
+ public void writeBoolean(boolean b) throws IOException {
+ getBackingStream().writeBoolean(b);
+ }
+
+ @Override
+ public void writeByte(int i) throws IOException {
+ getBackingStream().writeByte(i);
+ }
+
+ @Override
+ public void writeShort(int i) throws IOException {
+ getBackingStream().writeShort(Integer.reverseBytes(i) >> 16);
+ }
+
+ @Override
+ public void writeChar(int i) throws IOException {
+ getBackingStream().writeChar(Character.reverseBytes((char) i));
+ }
+
+ @Override
+ public void writeInt(int i) throws IOException {
+ getBackingStream().writeInt(Integer.reverseBytes(i));
+ }
+
+ @Override
+ public void writeLong(long l) throws IOException {
+ getBackingStream().writeLong(Long.reverseBytes(l));
+ }
+
+ @Override
+ public void writeFloat(float v) throws IOException {
+ getBackingStream().writeInt(Integer.reverseBytes(Float.floatToIntBits(v)));
+ }
+
+ @Override
+ public void writeDouble(double v) throws IOException {
+ getBackingStream().writeLong(Long.reverseBytes(Double.doubleToLongBits(v)));
+ }
+
+ @Override
+ public void writeBytes(String s) throws IOException {
+ getBackingStream().writeBytes(s);
+ }
+
+ @Override
+ public void writeChars(String s) throws IOException {
+ getBackingStream().writeChars(s);
+ }
+
+ @Override
+ public void writeUTF(String s) throws IOException {
+ getBackingStream().writeUTF(s);
+ }
+}
diff --git a/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java b/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java
index c87515b..d717c86 100644
--- a/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java
+++ b/src/main/java/com/flowpowered/nbt/stream/NBTInputStream.java
@@ -24,218 +24,303 @@
package com.flowpowered.nbt.stream;
import java.io.Closeable;
+import java.io.DataInput;
+import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
-import com.flowpowered.nbt.ByteArrayTag;
-import com.flowpowered.nbt.ByteTag;
-import com.flowpowered.nbt.CompoundMap;
-import com.flowpowered.nbt.CompoundTag;
-import com.flowpowered.nbt.DoubleTag;
-import com.flowpowered.nbt.EndTag;
-import com.flowpowered.nbt.FloatTag;
-import com.flowpowered.nbt.IntArrayTag;
-import com.flowpowered.nbt.IntTag;
-import com.flowpowered.nbt.ListTag;
-import com.flowpowered.nbt.LongTag;
-import com.flowpowered.nbt.NBTConstants;
-import com.flowpowered.nbt.ShortArrayTag;
-import com.flowpowered.nbt.ShortTag;
-import com.flowpowered.nbt.StringTag;
-import com.flowpowered.nbt.Tag;
-import com.flowpowered.nbt.TagType;
+import com.flowpowered.nbt.*;
/**
- * This class reads NBT, or Named Binary Tag streams, and produces an object graph of subclasses of the {@link Tag} object. The NBT format was created by Markus Persson, and the specification
- * may be found at https://flowpowered.com/nbt/spec.txt.
+ * This class reads NBT, or Named Binary Tag streams, and produces an object graph of subclasses of the {@link Tag} object.
+ *
+ * The NBT format was created by Markus Persson, and the specification may be found at
+ * https://flowpowered.com/nbt/spec.txt.
*/
public final class NBTInputStream implements Closeable {
- /**
- * The data input stream.
- */
- private final EndianSwitchableInputStream is;
-
- /**
- * Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. This assumes the stream is compressed.
- *
- * @param is The input stream.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTInputStream(InputStream is) throws IOException {
- this(is, true, ByteOrder.BIG_ENDIAN);
- }
-
- /**
- * Creates a new {@link 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. This assumes the
- * stream uses big endian encoding.
- *
- * @param is The input stream.
- * @param compressed A flag indicating if the stream is compressed.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTInputStream(InputStream is, boolean compressed) throws IOException {
- this(is, compressed, ByteOrder.BIG_ENDIAN);
- }
-
- /**
- * Creates a new {@link 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.
- * @param endianness Whether to read numbers from the InputStream with little endian encoding.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTInputStream(InputStream is, boolean compressed, ByteOrder endianness) throws IOException {
- this.is = new EndianSwitchableInputStream(compressed ? new GZIPInputStream(is) : is, endianness);
- }
-
- /**
- * Reads an NBT {@link Tag} from the stream.
- *
- * @return The tag that was read.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public Tag readTag() throws IOException {
- return readTag(0);
- }
-
- /**
- * Reads an NBT {@link Tag} from the stream.
- *
- * @param depth The depth of this tag.
- * @return The tag that was read.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private Tag readTag(int depth) throws IOException {
- int typeId = is.readByte() & 0xFF;
- TagType type = TagType.getById(typeId);
-
- String name;
- if (type != TagType.TAG_END) {
- int nameLength = is.readShort() & 0xFFFF;
- byte[] nameBytes = new byte[nameLength];
- is.readFully(nameBytes);
- name = new String(nameBytes, NBTConstants.CHARSET.name());
- } else {
- name = "";
- }
-
- return readTagPayload(type, name, depth);
- }
-
- /**
- * Reads the payload of a {@link Tag}, given the name and type.
- *
- * @param type The type.
- * @param name The name.
- * @param depth The depth.
- * @return The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- @SuppressWarnings ({"unchecked", "rawtypes"})
- private Tag readTagPayload(TagType type, String name, int depth) throws IOException {
- switch (type) {
- case TAG_END:
- if (depth == 0) {
- throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
- } else {
- return new EndTag();
- }
-
- case TAG_BYTE:
- return new ByteTag(name, is.readByte());
-
- case TAG_SHORT:
- return new ShortTag(name, is.readShort());
-
- case TAG_INT:
- return new IntTag(name, is.readInt());
-
- case TAG_LONG:
- return new LongTag(name, is.readLong());
-
- case TAG_FLOAT:
- return new FloatTag(name, is.readFloat());
-
- case TAG_DOUBLE:
- return new DoubleTag(name, is.readDouble());
-
- case TAG_BYTE_ARRAY:
- int length = is.readInt();
- byte[] bytes = new byte[length];
- is.readFully(bytes);
- return new ByteArrayTag(name, bytes);
-
- case TAG_STRING:
- length = is.readShort();
- bytes = new byte[length];
- is.readFully(bytes);
- return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name()));
-
- case TAG_LIST:
- TagType childType = TagType.getById(is.readByte());
- length = is.readInt();
-
- Class extends Tag> clazz = childType.getTagClass();
- List tagList = new ArrayList(length);
- for (int i = 0; i < length; i++) {
- Tag tag = readTagPayload(childType, "", depth + 1);
- if (tag instanceof EndTag) {
- throw new IOException("TAG_End not permitted in a list.");
- } else if (!clazz.isInstance(tag)) {
- throw new IOException("Mixed tag types within a list.");
- }
- tagList.add(tag);
- }
-
- return new ListTag(name, clazz, tagList);
-
- case TAG_COMPOUND:
- CompoundMap compoundTagList = new CompoundMap();
- while (true) {
- Tag tag = readTag(depth + 1);
- if (tag instanceof EndTag) {
- break;
- } else {
- compoundTagList.put(tag);
- }
- }
-
- return new CompoundTag(name, compoundTagList);
-
- case TAG_INT_ARRAY:
- length = is.readInt();
- int[] ints = new int[length];
- for (int i = 0; i < length; i++) {
- ints[i] = is.readInt();
- }
- return new IntArrayTag(name, ints);
-
- case TAG_SHORT_ARRAY:
- length = is.readInt();
- short[] shorts = new short[length];
- for (int i = 0; i < length; i++) {
- shorts[i] = is.readShort();
- }
- return new ShortArrayTag(name, shorts);
-
- default:
- throw new IOException("Invalid tag type: " + type + ".");
- }
- }
-
- public void close() throws IOException {
- is.close();
- }
-
- /**
- * @return whether this NBTInputStream reads numbers in little-endian format.
- */
- public ByteOrder getByteOrder() {
- return is.getEndianness();
- }
+
+ /**
+ * Flag indicating that the given data stream is not compressed.
+ */
+ public static final int NO_COMPRESSION = 0;
+ /**
+ * Flag indicating that the given data will be compressed with the GZIP compression algorithm. This is the default compression method used
+ * to compress nbt files. Chunks in Minecraft Region/Anvil files with compression method {@code 1} (see the respective format documentation)
+ * will use this compression method too, although this is not actively used anymore.
+ */
+ public static final int GZIP_COMPRESSION = 1;
+ /**
+ * Flag indicating that the given data will be compressed with the ZLIB compression algorithm. This is the default compression method used
+ * to compress the nbt data of the chunks in Minecraft Region/Anvil files, but only if its compression method is {@code 2} (see the
+ * respective format documentation), which is default for all newer versions.
+ */
+ public static final int ZLIB_COMPRESSION = 2;
+
+ private final DataInput dataIn;
+ private final InputStream inputStream;
+
+ /**
+ * Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. This assumes the stream is compressed.
+ *
+ * @param is
+ * The input stream.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTInputStream(InputStream is) throws IOException {
+ this(is, GZIP_COMPRESSION, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link 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. This assumes the stream uses big endian encoding.
+ *
+ * @param is
+ * The input stream.
+ * @param compressed
+ * A flag indicating if the stream is compressed.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ * @deprecated Use {@link #NBTInputStream(InputStream, int)} instead
+ */
+ @Deprecated
+ public NBTInputStream(InputStream is, boolean compressed) throws IOException {
+ this(is, compressed, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. The stream may be wrapped into a
+ * decompressing input stream depending on the chosen compression method. This assumes the stream uses big endian encoding.
+ *
+ * @param is
+ * The input stream.
+ * @param compression
+ * The compression algorithm used for the input stream. Must be {@link #NO_COMPRESSION}, {@link #GZIP_COMPRESSION} or
+ * {@link #ZLIB_COMPRESSION}.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTInputStream(InputStream is, int compression) throws IOException {
+ this(is, compression, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link 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.
+ * @param endianness
+ * Whether to read numbers from the InputStream with little endian encoding.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ * @deprecated Use {@link #NBTInputStream(InputStream, int, ByteOrder)} instead
+ */
+ @Deprecated
+ public NBTInputStream(InputStream is, boolean compressed, ByteOrder endianness) throws IOException {
+ this(is, compressed ? GZIP_COMPRESSION : NO_COMPRESSION, endianness);
+ }
+
+ /**
+ * Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. The stream may be wrapped into a
+ * decompressing input stream depending on the chosen compression method.
+ *
+ * @param is
+ * The input stream.
+ * @param compression
+ * The compression algorithm used for the input stream. Must be {@link #NO_COMPRESSION}, {@link #GZIP_COMPRESSION} or
+ * {@link #ZLIB_COMPRESSION}.
+ * @param endianness
+ * Whether to read numbers from the InputStream with little endian encoding.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTInputStream(InputStream is, int compression, ByteOrder endianness) throws IOException {
+ switch (compression) {
+ case NO_COMPRESSION:
+ break;
+ case GZIP_COMPRESSION:
+ is = new GZIPInputStream(is);
+ break;
+ case ZLIB_COMPRESSION:
+ is = new InflaterInputStream(is);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported compression type, must be between 0 and 2 (inclusive)");
+ }
+ if (endianness == ByteOrder.LITTLE_ENDIAN)
+ this.inputStream = (InputStream) (this.dataIn = new LittleEndianInputStream(is));
+ else
+ this.inputStream = (InputStream) (this.dataIn = new DataInputStream(is));
+ }
+
+ /**
+ * Reads an NBT {@link Tag} from the stream.
+ *
+ * @return The tag that was read.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public Tag> readTag() throws IOException {
+ return readTag(0);
+ }
+
+ /**
+ * Reads an NBT {@link Tag} from the stream.
+ *
+ * @param depth
+ * The depth of this tag.
+ * @return The tag that was read.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private Tag> readTag(int depth) throws IOException {
+ int typeId = dataIn.readByte() & 0xFF;
+ TagType type = TagType.getById(typeId);
+
+ String name;
+ if (type != TagType.TAG_END) {
+ int nameLength = dataIn.readShort() & 0xFFFF;
+ byte[] nameBytes = new byte[nameLength];
+ dataIn.readFully(nameBytes);
+ name = new String(nameBytes, NBTConstants.CHARSET.name());
+ } else {
+ name = "";
+ }
+
+ return readTagPayload(type, name, depth);
+ }
+
+ /**
+ * Reads the payload of a {@link Tag}, given the name and type.
+ *
+ * @param type
+ * The type.
+ * @param name
+ * The name.
+ * @param depth
+ * The depth.
+ * @return The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private Tag readTagPayload(TagType type, String name, int depth) throws IOException {
+ switch (type) {
+ case TAG_END:
+ if (depth == 0) {
+ throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
+ } else {
+ return new EndTag();
+ }
+
+ case TAG_BYTE:
+ return new ByteTag(name, dataIn.readByte());
+
+ case TAG_SHORT:
+ return new ShortTag(name, dataIn.readShort());
+
+ case TAG_INT:
+ return new IntTag(name, dataIn.readInt());
+
+ case TAG_LONG:
+ return new LongTag(name, dataIn.readLong());
+
+ case TAG_FLOAT:
+ return new FloatTag(name, dataIn.readFloat());
+
+ case TAG_DOUBLE:
+ return new DoubleTag(name, dataIn.readDouble());
+
+ case TAG_BYTE_ARRAY:
+ int length = dataIn.readInt();
+ byte[] bytes = new byte[length];
+ dataIn.readFully(bytes);
+ return new ByteArrayTag(name, bytes);
+
+ case TAG_STRING:
+ length = dataIn.readShort();
+ bytes = new byte[length];
+ dataIn.readFully(bytes);
+ return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name()));
+
+ case TAG_LIST:
+ TagType childType = TagType.getById(dataIn.readByte());
+ length = dataIn.readInt();
+
+ Class extends Tag> clazz = childType.getTagClass();
+ List tagList = new ArrayList(length);
+ for (int i = 0; i < length; i++) {
+ Tag tag = readTagPayload(childType, "", depth + 1);
+ if (tag instanceof EndTag) {
+ throw new IOException("TAG_End not permitted in a list.");
+ } else if (!clazz.isInstance(tag)) {
+ throw new IOException("Mixed tag types within a list.");
+ }
+ tagList.add(tag);
+ }
+
+ return new ListTag(name, clazz, tagList);
+
+ case TAG_COMPOUND:
+ CompoundMap compoundTagList = new CompoundMap();
+ while (true) {
+ Tag tag = readTag(depth + 1);
+ if (tag instanceof EndTag) {
+ break;
+ } else {
+ compoundTagList.put(tag);
+ }
+ }
+
+ return new CompoundTag(name, compoundTagList);
+
+ case TAG_INT_ARRAY:
+ length = dataIn.readInt();
+ int[] ints = new int[length];
+ for (int i = 0; i < length; i++) {
+ ints[i] = dataIn.readInt();
+ }
+ return new IntArrayTag(name, ints);
+
+ case TAG_LONG_ARRAY:
+ length = dataIn.readInt();
+ long[] longs = new long[length];
+ for (int i = 0; i < length; i++) {
+ longs[i] = dataIn.readLong();
+ }
+ return new LongArrayTag(name, longs);
+
+ case TAG_SHORT_ARRAY:
+ length = dataIn.readInt();
+ short[] shorts = new short[length];
+ for (int i = 0; i < length; i++) {
+ shorts[i] = dataIn.readShort();
+ }
+ return new ShortArrayTag(name, shorts);
+
+ default:
+ throw new IOException("Invalid tag type: " + type + ".");
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ inputStream.close();
+ }
+
+ /**
+ * @return whether this NBTInputStream reads numbers in little-endian format.
+ */
+ @Deprecated
+ public ByteOrder getByteOrder() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/main/java/com/flowpowered/nbt/stream/NBTOutputStream.java b/src/main/java/com/flowpowered/nbt/stream/NBTOutputStream.java
index bdb9504..e9756ca 100644
--- a/src/main/java/com/flowpowered/nbt/stream/NBTOutputStream.java
+++ b/src/main/java/com/flowpowered/nbt/stream/NBTOutputStream.java
@@ -24,326 +24,432 @@
package com.flowpowered.nbt.stream;
import java.io.Closeable;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.List;
+import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
-import com.flowpowered.nbt.ByteArrayTag;
-import com.flowpowered.nbt.ByteTag;
-import com.flowpowered.nbt.CompoundTag;
-import com.flowpowered.nbt.DoubleTag;
-import com.flowpowered.nbt.EndTag;
-import com.flowpowered.nbt.FloatTag;
-import com.flowpowered.nbt.IntArrayTag;
-import com.flowpowered.nbt.IntTag;
-import com.flowpowered.nbt.ListTag;
-import com.flowpowered.nbt.LongTag;
-import com.flowpowered.nbt.NBTConstants;
-import com.flowpowered.nbt.ShortArrayTag;
-import com.flowpowered.nbt.ShortTag;
-import com.flowpowered.nbt.StringTag;
-import com.flowpowered.nbt.Tag;
-import com.flowpowered.nbt.TagType;
+import com.flowpowered.nbt.*;
/**
- * This class writes NBT, or Named Binary Tag, {@link Tag} objects to an underlying {@link java.io.OutputStream}. The NBT format was created by Markus Persson, and the specification may be found
- * at https://flowpowered.com/nbt/spec.txt.
+ * This class writes NBT, or Named Binary Tag, {@link Tag} objects to an underlying {@link java.io.OutputStream}.
+ *
+ * The NBT format was created by Markus Persson, and the specification may be found at
+ * https://flowpowered.com/nbt/spec.txt.
*/
public final class NBTOutputStream implements Closeable {
- /**
- * The output stream.
- */
- private final EndianSwitchableOutputStream os;
-
- /**
- * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. This assumes the output stream should be compressed with GZIP.
- *
- * @param os The output stream.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTOutputStream(OutputStream os) throws IOException {
- this(os, true, ByteOrder.BIG_ENDIAN);
- }
-
- /**
- * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. A flag indicates if the output should be compressed with GZIP or not.
- *
- * @param os The output stream.
- * @param compressed A flag that indicates if the output should be compressed.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTOutputStream(OutputStream os, boolean compressed) throws IOException {
- this(os, compressed, ByteOrder.BIG_ENDIAN);
- }
-
- /**
- * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. A flag indicates if the output should be compressed with GZIP or not.
- *
- * @param os The output stream.
- * @param compressed A flag that indicates if the output should be compressed.
- * @param endianness A flag that indicates if numbers in the output should be output in little-endian format.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public NBTOutputStream(OutputStream os, boolean compressed, ByteOrder endianness) throws IOException {
- this.os = new EndianSwitchableOutputStream(compressed ? new GZIPOutputStream(os) : os, endianness);
- }
-
- /**
- * Writes a tag.
- *
- * @param tag The tag to write.
- * @throws java.io.IOException if an I/O error occurs.
- */
- public void writeTag(Tag> tag) throws IOException {
- String name = tag.getName();
- byte[] nameBytes = name.getBytes(NBTConstants.CHARSET.name());
-
- os.writeByte(tag.getType().getId());
- os.writeShort(nameBytes.length);
- os.write(nameBytes);
-
- if (tag.getType() == TagType.TAG_END) {
- throw new IOException("Named TAG_End not permitted.");
- }
-
- writeTagPayload(tag);
- }
-
- /**
- * Writes tag payload.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeTagPayload(Tag> tag) throws IOException {
- switch (tag.getType()) {
- case TAG_END:
- writeEndTagPayload((EndTag) tag);
- break;
-
- case TAG_BYTE:
- writeByteTagPayload((ByteTag) tag);
- break;
-
- case TAG_SHORT:
- writeShortTagPayload((ShortTag) tag);
- break;
-
- case TAG_INT:
- writeIntTagPayload((IntTag) tag);
- break;
-
- case TAG_LONG:
- writeLongTagPayload((LongTag) tag);
- break;
-
- case TAG_FLOAT:
- writeFloatTagPayload((FloatTag) tag);
- break;
-
- case TAG_DOUBLE:
- writeDoubleTagPayload((DoubleTag) tag);
- break;
-
- case TAG_BYTE_ARRAY:
- writeByteArrayTagPayload((ByteArrayTag) tag);
- break;
-
- case TAG_STRING:
- writeStringTagPayload((StringTag) tag);
- break;
-
- case TAG_LIST:
- writeListTagPayload((ListTag>) tag);
- break;
-
- case TAG_COMPOUND:
- writeCompoundTagPayload((CompoundTag) tag);
- break;
-
- case TAG_INT_ARRAY:
- writeIntArrayTagPayload((IntArrayTag) tag);
- break;
-
- case TAG_SHORT_ARRAY:
- writeShortArrayTagPayload((ShortArrayTag) tag);
- break;
-
- default:
- throw new IOException("Invalid tag type: " + tag.getType() + ".");
- }
- }
-
- /**
- * Writes a {@code TAG_Byte} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeByteTagPayload(ByteTag tag) throws IOException {
- os.writeByte(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Byte_Array} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException {
- byte[] bytes = tag.getValue();
- os.writeInt(bytes.length);
- os.write(bytes);
- }
-
- /**
- * Writes a {@code TAG_Compound} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeCompoundTagPayload(CompoundTag tag) throws IOException {
- for (Tag> childTag : tag.getValue().values()) {
- writeTag(childTag);
- }
- os.writeByte(TagType.TAG_END.getId()); // end tag - better way?
- }
-
- /**
- * Writes a {@code TAG_List} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- @SuppressWarnings ("unchecked")
- private void writeListTagPayload(ListTag> tag) throws IOException {
- Class extends Tag>> clazz = tag.getElementType();
- List> tags = (List>) tag.getValue();
- int size = tags.size();
-
- os.writeByte(TagType.getByTagClass(clazz).getId());
- os.writeInt(size);
- for (Tag> tag1 : tags) {
- writeTagPayload(tag1);
- }
- }
-
- /**
- * Writes a {@code TAG_String} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeStringTagPayload(StringTag tag) throws IOException {
- byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET.name());
- os.writeShort(bytes.length);
- os.write(bytes);
- }
-
- /**
- * Writes a {@code TAG_Double} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeDoubleTagPayload(DoubleTag tag) throws IOException {
- os.writeDouble(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Float} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeFloatTagPayload(FloatTag tag) throws IOException {
- os.writeFloat(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Long} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeLongTagPayload(LongTag tag) throws IOException {
- os.writeLong(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Int} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeIntTagPayload(IntTag tag) throws IOException {
- os.writeInt(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Short} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeShortTagPayload(ShortTag tag) throws IOException {
- os.writeShort(tag.getValue());
- }
-
- /**
- * Writes a {@code TAG_Int_Array} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException {
- int[] ints = tag.getValue();
- os.writeInt(ints.length);
- for (int i = 0; i < ints.length; i++) {
- os.writeInt(ints[i]);
- }
- }
-
- /**
- * Writes a {@code TAG_Short_Array} tag.
- *
- * @param tag The tag.
- * @throws java.io.IOException if an I/O error occurs.
- */
- private void writeShortArrayTagPayload(ShortArrayTag tag) throws IOException {
- short[] shorts = tag.getValue();
- os.writeInt(shorts.length);
- for (int i = 0; i < shorts.length; i++) {
- os.writeShort(shorts[i]);
- }
- }
-
- /**
- * Writes a {@code TAG_Empty} tag.
- *
- * @param tag The tag.
- */
- private void writeEndTagPayload(EndTag tag) {
- /* empty */
- }
-
- public void close() throws IOException {
- os.close();
- }
-
- /**
- * @return whether this NBTInputStream writes numbers in little-endian format.
- */
- public ByteOrder getEndianness() {
- return os.getEndianness();
- }
-
- /**
- * Flushes the stream
- */
- public void flush() throws IOException {
- os.flush();
- }
+
+ private final DataOutput dataOut;
+ private final OutputStream outputStream;
+
+ /**
+ * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. This assumes the output stream
+ * should be compressed with GZIP.
+ *
+ * @param os
+ * The output stream.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTOutputStream(OutputStream os) throws IOException {
+ this(os, NBTInputStream.GZIP_COMPRESSION, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. A flag indicates if the output
+ * should be compressed with GZIP or not.
+ *
+ * @param os
+ * The output stream.
+ * @param compressed
+ * A flag that indicates if the output should be compressed.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ * @deprecated Use {@link #NBTOutputStream(InputStream, int)} instead
+ */
+ @Deprecated
+ public NBTOutputStream(OutputStream os, boolean compressed) throws IOException {
+ this(os, compressed, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. The stream may be wrapped into a
+ * compressing output stream depending on the chosen compression method. A flag indicates if the output should be compressed with GZIP or
+ * not.
+ *
+ * @param os
+ * The output stream.
+ * @param compression
+ * The compression algorithm used for the input stream. Must be {@link NBTInputStream#NO_COMPRESSION},
+ * {@link NBTInputStream#GZIP_COMPRESSION} or {@link NBTInputStream#ZLIB_COMPRESSION}.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTOutputStream(OutputStream os, int compression) throws IOException {
+ this(os, compression, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. A flag indicates if the output
+ * should be compressed with GZIP or not.
+ *
+ * @param os
+ * The output stream.
+ * @param compressed
+ * A flag that indicates if the output should be compressed.
+ * @param endianness
+ * A flag that indicates if numbers in the output should be output in little-endian format.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ * @deprecated Use {@link #NBTOutputStream(InputStream, int, ByteOrder)} instead
+ */
+ @Deprecated
+ public NBTOutputStream(OutputStream os, boolean compressed, ByteOrder endianness) throws IOException {
+ this(os, compressed ? NBTInputStream.GZIP_COMPRESSION : NBTInputStream.NO_COMPRESSION, endianness);
+ }
+
+ /**
+ * Creates a new {@link NBTOutputStream}, which will write data to the specified underlying output stream. The stream may be wrapped into a
+ * compressing output stream depending on the chosen compression method.
+ *
+ * @param os
+ * The output stream.
+ * @param compression
+ * The compression algorithm used for the input stream. Must be {@link NBTInputStream#NO_COMPRESSION},
+ * {@link NBTInputStream#GZIP_COMPRESSION} or {@link NBTInputStream#ZLIB_COMPRESSION}.
+ * @param endianness
+ * A flag that indicates if numbers in the output should be output in little-endian format.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public NBTOutputStream(OutputStream os, int compression, ByteOrder endianness) throws IOException {
+ switch (compression) {
+ case NBTInputStream.NO_COMPRESSION:
+ break;
+ case NBTInputStream.GZIP_COMPRESSION:
+ os = new GZIPOutputStream(os);
+ break;
+ case NBTInputStream.ZLIB_COMPRESSION:
+ os = new DeflaterOutputStream(os);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported compression type, must be between 0 and 2 (inclusive)");
+ }
+ if (endianness == ByteOrder.LITTLE_ENDIAN)
+ this.outputStream = (OutputStream) (this.dataOut = new LittleEndianOutputStream(os));
+ else
+ this.outputStream = (OutputStream) (this.dataOut = new DataOutputStream(os));
+ }
+
+ /**
+ * Writes a tag.
+ *
+ * @param tag
+ * The tag to write.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ public void writeTag(Tag> tag) throws IOException {
+ String name = tag.getName();
+ byte[] nameBytes = name.getBytes(NBTConstants.CHARSET.name());
+
+ dataOut.writeByte(tag.getType().getId());
+ dataOut.writeShort(nameBytes.length);
+ dataOut.write(nameBytes);
+
+ if (tag.getType() == TagType.TAG_END) {
+ throw new IOException("Named TAG_End not permitted.");
+ }
+
+ writeTagPayload(tag);
+ }
+
+ /**
+ * Writes tag payload.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeTagPayload(Tag> tag) throws IOException {
+ switch (tag.getType()) {
+ case TAG_END:
+ writeEndTagPayload((EndTag) tag);
+ break;
+
+ case TAG_BYTE:
+ writeByteTagPayload((ByteTag) tag);
+ break;
+
+ case TAG_SHORT:
+ writeShortTagPayload((ShortTag) tag);
+ break;
+
+ case TAG_INT:
+ writeIntTagPayload((IntTag) tag);
+ break;
+
+ case TAG_LONG:
+ writeLongTagPayload((LongTag) tag);
+ break;
+
+ case TAG_FLOAT:
+ writeFloatTagPayload((FloatTag) tag);
+ break;
+
+ case TAG_DOUBLE:
+ writeDoubleTagPayload((DoubleTag) tag);
+ break;
+
+ case TAG_BYTE_ARRAY:
+ writeByteArrayTagPayload((ByteArrayTag) tag);
+ break;
+
+ case TAG_STRING:
+ writeStringTagPayload((StringTag) tag);
+ break;
+
+ case TAG_LIST:
+ writeListTagPayload((ListTag>) tag);
+ break;
+
+ case TAG_COMPOUND:
+ writeCompoundTagPayload((CompoundTag) tag);
+ break;
+
+ case TAG_INT_ARRAY:
+ writeIntArrayTagPayload((IntArrayTag) tag);
+ break;
+
+ case TAG_LONG_ARRAY:
+ writeLongArrayTagPayload((LongArrayTag) tag);
+ break;
+
+ case TAG_SHORT_ARRAY:
+ writeShortArrayTagPayload((ShortArrayTag) tag);
+ break;
+
+ default:
+ throw new IOException("Invalid tag type: " + tag.getType() + ".");
+ }
+ }
+
+ /**
+ * Writes a {@code TAG_Byte} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeByteTagPayload(ByteTag tag) throws IOException {
+ dataOut.writeByte(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Byte_Array} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException {
+ byte[] bytes = tag.getValue();
+ dataOut.writeInt(bytes.length);
+ dataOut.write(bytes);
+ }
+
+ /**
+ * Writes a {@code TAG_Compound} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeCompoundTagPayload(CompoundTag tag) throws IOException {
+ for (Tag> childTag : tag.getValue().values()) {
+ writeTag(childTag);
+ }
+ dataOut.writeByte(TagType.TAG_END.getId()); // end tag - better way?
+ }
+
+ /**
+ * Writes a {@code TAG_List} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ @SuppressWarnings("unchecked")
+ private void writeListTagPayload(ListTag> tag) throws IOException {
+ Class extends Tag>> clazz = tag.getElementType();
+ List> tags = (List>) tag.getValue();
+ int size = tags.size();
+
+ dataOut.writeByte(TagType.getByTagClass(clazz).getId());
+ dataOut.writeInt(size);
+ for (Tag> tag1 : tags) {
+ writeTagPayload(tag1);
+ }
+ }
+
+ /**
+ * Writes a {@code TAG_String} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeStringTagPayload(StringTag tag) throws IOException {
+ byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET.name());
+ dataOut.writeShort(bytes.length);
+ dataOut.write(bytes);
+ }
+
+ /**
+ * Writes a {@code TAG_Double} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeDoubleTagPayload(DoubleTag tag) throws IOException {
+ dataOut.writeDouble(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Float} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeFloatTagPayload(FloatTag tag) throws IOException {
+ dataOut.writeFloat(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Long} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeLongTagPayload(LongTag tag) throws IOException {
+ dataOut.writeLong(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Int} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeIntTagPayload(IntTag tag) throws IOException {
+ dataOut.writeInt(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Short} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeShortTagPayload(ShortTag tag) throws IOException {
+ dataOut.writeShort(tag.getValue());
+ }
+
+ /**
+ * Writes a {@code TAG_Int_Array} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException {
+ int[] ints = tag.getValue();
+ dataOut.writeInt(ints.length);
+ for (int i = 0; i < ints.length; i++) {
+ dataOut.writeInt(ints[i]);
+ }
+ }
+
+ /**
+ * Writes a {@code TAG_Long_Array} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeLongArrayTagPayload(LongArrayTag tag) throws IOException {
+ long[] longs = tag.getValue();
+ dataOut.writeInt(longs.length);
+ for (int i = 0; i < longs.length; i++) {
+ dataOut.writeLong(longs[i]);
+ }
+ }
+
+ /**
+ * Writes a {@code TAG_Short_Array} tag.
+ *
+ * @param tag
+ * The tag.
+ * @throws java.io.IOException
+ * if an I/O error occurs.
+ */
+ private void writeShortArrayTagPayload(ShortArrayTag tag) throws IOException {
+ short[] shorts = tag.getValue();
+ dataOut.writeInt(shorts.length);
+ for (int i = 0; i < shorts.length; i++) {
+ dataOut.writeShort(shorts[i]);
+ }
+ }
+
+ /**
+ * Writes a {@code TAG_Empty} tag.
+ *
+ * @param tag
+ * The tag.
+ */
+ private void writeEndTagPayload(EndTag tag) {
+ /* empty */
+ }
+
+ @Override
+ public void close() throws IOException {
+ outputStream.close();
+ }
+
+ /**
+ * @return whether this NBTInputStream writes numbers in little-endian format.
+ */
+ @Deprecated
+ public ByteOrder getEndianness() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Flushes the stream
+ */
+ public void flush() throws IOException {
+ outputStream.flush();
+ }
}
diff --git a/src/test/java/com/flowpowered/nbt/LongTest.java b/src/test/java/com/flowpowered/nbt/LongTest.java
new file mode 100644
index 0000000..d1f7762
--- /dev/null
+++ b/src/test/java/com/flowpowered/nbt/LongTest.java
@@ -0,0 +1,53 @@
+package com.flowpowered.nbt;
+
+import static org.junit.Assert.assertEquals;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.LongBuffer;
+import java.util.Random;
+
+import org.junit.Test;
+
+import com.flowpowered.nbt.regionfile.Chunk;
+
+public class LongTest {
+
+ @Test
+ public void test() {
+ for (int BITS = 4; BITS <= 12; BITS++) {
+ Random random = new Random(1234);
+
+ // Fill a byte buffer with random data
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[BITS * 64 * 8]);
+ buffer.order(ByteOrder.BIG_ENDIAN);
+ random.nextBytes(buffer.array());
+
+ // Convert it to a long buffer
+ LongBuffer data = buffer.asLongBuffer();
+ data.rewind();
+ long[] longData = new long[BITS * 64];
+ data.get(longData);
+
+ // Control data: Convert all bytes to a binary string and zero-pad them, append them to a large bit string
+ // Slicing the bit string into equal length substrings will give the control data.
+ // Add a double reverse because Mojang does silly stuff sometimes.
+ StringBuffer number = new StringBuffer();
+ for (long l : longData)
+ number.append(convertLong(Long.reverse(l)));
+ for (int i = 0; i < BITS * 64; i++) {
+ // Compare conversion with control data
+ assertEquals(Long.parseLong(new StringBuilder(number.substring(i * BITS, i * BITS + BITS)).reverse().toString(), 2),
+ Chunk.extractFromLong(longData, i, BITS));
+ }
+ }
+ }
+
+ /** Convert long to binary string and zero pad it */
+ private static String convertLong(long l) {
+ String s = Long.toBinaryString(l);
+ // Fancy way of zero padding :)
+ s = "0000000000000000000000000000000000000000000000000000000000000000".substring(s.length()) + s;
+ return s;
+ }
+}
diff --git a/src/test/java/com/flowpowered/nbt/RegionFileTest.java b/src/test/java/com/flowpowered/nbt/RegionFileTest.java
new file mode 100644
index 0000000..751c756
--- /dev/null
+++ b/src/test/java/com/flowpowered/nbt/RegionFileTest.java
@@ -0,0 +1,52 @@
+package com.flowpowered.nbt;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import com.flowpowered.nbt.regionfile.Chunk;
+import com.flowpowered.nbt.regionfile.RegionFile;
+
+public class RegionFileTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ /**
+ * Test reading the NBT data in a region file
+ *
+ * @throws URISyntaxException
+ * @throws IOException
+ */
+ @Test
+ public void testRead() throws IOException, URISyntaxException {
+ try (RegionFile file = new RegionFile(Paths.get(getClass().getResource("/r.1.3.mca").toURI()));) {
+ for (int i : file.listChunks()) {
+ Chunk chunk = file.loadChunk(i);
+ if (chunk != null)
+ chunk.readTag();
+ }
+ }
+ }
+
+ @Test
+ public void testCreateNew() throws IOException {
+ File file = folder.newFile();
+ file.delete();
+ RegionFile.createNew(file.toPath()).close();
+ file.delete();
+ RegionFile rf = RegionFile.open(folder.newFolder().toPath().resolve("test").resolve("test.mca"));
+ rf.writeChunks(new HashMap<>());
+ assertEquals(4096 * 2, Files.size(rf.getPath()));
+ rf.close();
+ }
+}
diff --git a/src/test/java/com/flowpowered/nbt/stream/EndianSwitchableStreamTest.java b/src/test/java/com/flowpowered/nbt/stream/EndianSwitchableStreamTest.java
index a2a7e12..380955b 100644
--- a/src/test/java/com/flowpowered/nbt/stream/EndianSwitchableStreamTest.java
+++ b/src/test/java/com/flowpowered/nbt/stream/EndianSwitchableStreamTest.java
@@ -23,17 +23,16 @@
*/
package com.flowpowered.nbt.stream;
+import static org.junit.Assert.assertEquals;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.nio.ByteOrder;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-
/**
- * Test for both {@link EndianSwitchableInputStream EndianSwitchableInput} and {@link EndianSwitchableOutputStream Output} Streams
+ * Test for both {@link LittleEndianInputStream EndianSwitchableInput} and {@link LittleEndianOutputStream Output} Streams
*/
public class EndianSwitchableStreamTest {
@Test
@@ -41,12 +40,14 @@ public void testWriteLEUnsignedShort() throws IOException {
int unsigned = Short.MAX_VALUE + 5;
char testChar = 'b';
ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
- EndianSwitchableOutputStream output = new EndianSwitchableOutputStream(rawOutput, ByteOrder.LITTLE_ENDIAN);
+ LittleEndianOutputStream output = new LittleEndianOutputStream(rawOutput );
output.writeShort(unsigned);
output.writeChar(testChar);
+ output.close();
- EndianSwitchableInputStream input = new EndianSwitchableInputStream(new ByteArrayInputStream(rawOutput.toByteArray()), ByteOrder.LITTLE_ENDIAN);
+ LittleEndianInputStream input = new LittleEndianInputStream (new ByteArrayInputStream(rawOutput.toByteArray()) );
assertEquals(unsigned, input.readUnsignedShort());
assertEquals(testChar, input.readChar());
+ input.close();
}
}
diff --git a/src/test/java/com/flowpowered/nbt/stream/NBTInputStreamTest.java b/src/test/java/com/flowpowered/nbt/stream/NBTInputStreamTest.java
new file mode 100644
index 0000000..fb0b069
--- /dev/null
+++ b/src/test/java/com/flowpowered/nbt/stream/NBTInputStreamTest.java
@@ -0,0 +1,26 @@
+package com.flowpowered.nbt.stream;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.junit.Test;
+
+import com.flowpowered.nbt.Tag;
+
+public class NBTInputStreamTest {
+
+ /** Read a simple NBT file and compare it to a previous result */
+ @Test
+ public void testNBT() throws IOException, URISyntaxException {
+ try (NBTInputStream in = new NBTInputStream(getClass().getResourceAsStream("/level.dat"), NBTInputStream.GZIP_COMPRESSION)) {
+ Tag> tag = in.readTag();
+ assertArrayEquals(
+ Files.readAllLines(Paths.get(getClass().getResource("/level.txt").toURI())).toArray(new String[] {}),
+ tag.toString().split("\r\n"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/level.dat b/src/test/resources/level.dat
new file mode 100644
index 0000000..97ea390
Binary files /dev/null and b/src/test/resources/level.dat differ
diff --git a/src/test/resources/level.txt b/src/test/resources/level.txt
new file mode 100644
index 0000000..61cbf27
--- /dev/null
+++ b/src/test/resources/level.txt
@@ -0,0 +1,489 @@
+TAG_Compound: 1 entries
+{
+ TAG_Compound("Data"): 40 entries
+ {
+ TAG_Long("RandomSeed"): 4294967295
+ TAG_String("generatorName"): default
+ TAG_Double("BorderCenterZ"): 0.0
+ TAG_Byte("Difficulty"): 2
+ TAG_Long("BorderSizeLerpTime"): 0
+ TAG_Byte("raining"): 0
+ TAG_Compound("DimensionData"): 1 entries
+ {
+ TAG_Compound("1"): 1 entries
+ {
+ TAG_Compound("DragonFight"): 3 entries
+ {
+ TAG_List("Gateways"): 20 entries of type TAG_Int
+ {
+ TAG_Int: 9
+ TAG_Int: 13
+ TAG_Int: 6
+ TAG_Int: 12
+ TAG_Int: 1
+ TAG_Int: 8
+ TAG_Int: 11
+ TAG_Int: 3
+ TAG_Int: 4
+ TAG_Int: 18
+ TAG_Int: 16
+ TAG_Int: 19
+ TAG_Int: 2
+ TAG_Int: 17
+ TAG_Int: 0
+ TAG_Int: 14
+ TAG_Int: 10
+ TAG_Int: 7
+ TAG_Int: 15
+ TAG_Int: 5
+ }
+ TAG_Byte("DragonKilled"): 1
+ TAG_Byte("PreviouslyKilled"): 1
+ }
+ }
+ }
+ TAG_Long("Time"): 714595
+ TAG_Int("GameType"): 0
+ TAG_Byte("MapFeatures"): 1
+ TAG_Double("BorderCenterX"): 0.0
+ TAG_Double("BorderDamagePerBlock"): 0.2
+ TAG_Double("BorderWarningBlocks"): 5.0
+ TAG_Double("BorderSizeLerpTarget"): 6.0E7
+ TAG_Compound("Version"): 3 entries
+ {
+ TAG_Byte("Snapshot"): 0
+ TAG_Int("Id"): 1519
+ TAG_String("Name"): 1.13
+ }
+ TAG_Long("DayTime"): 922064
+ TAG_Byte("initialized"): 1
+ TAG_Byte("allowCommands"): 1
+ TAG_Long("SizeOnDisk"): 0
+ TAG_Compound("CustomBossEvents"): 0 entries
+ {
+ }
+ TAG_Compound("GameRules"): 23 entries
+ {
+ TAG_String("doTileDrops"): false
+ TAG_String("doFireTick"): true
+ TAG_String("maxCommandChainLength"): 65536
+ TAG_String("reducedDebugInfo"): false
+ TAG_String("naturalRegeneration"): true
+ TAG_String("disableElytraMovementCheck"): false
+ TAG_String("doMobLoot"): true
+ TAG_String("announceAdvancements"): true
+ TAG_String("keepInventory"): false
+ TAG_String("doEntityDrops"): true
+ TAG_String("doLimitedCrafting"): false
+ TAG_String("mobGriefing"): true
+ TAG_String("randomTickSpeed"): 3
+ TAG_String("commandBlockOutput"): true
+ TAG_String("spawnRadius"): 10
+ TAG_String("doMobSpawning"): true
+ TAG_String("maxEntityCramming"): 24
+ TAG_String("logAdminCommands"): true
+ TAG_String("spectatorsGenerateChunks"): false
+ TAG_String("doWeatherCycle"): true
+ TAG_String("sendCommandFeedback"): true
+ TAG_String("doDaylightCycle"): false
+ TAG_String("showDeathMessages"): true
+ }
+ TAG_Compound("Player"): 43 entries
+ {
+ TAG_Int("HurtByTimestamp"): 128850
+ TAG_Short("SleepTimer"): 0
+ TAG_Byte("SpawnForced"): 0
+ TAG_List("Attributes"): 8 entries of type TAG_Compound
+ {
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 20.0
+ TAG_String("Name"): generic.maxHealth
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 0.0
+ TAG_String("Name"): generic.knockbackResistance
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 0.10000000149011612
+ TAG_String("Name"): generic.movementSpeed
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 0.0
+ TAG_String("Name"): generic.armor
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 0.0
+ TAG_String("Name"): generic.armorToughness
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 1.0
+ TAG_String("Name"): generic.attackDamage
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 4.0
+ TAG_String("Name"): generic.attackSpeed
+ }
+ TAG_Compound: 2 entries
+ {
+ TAG_Double("Base"): 0.0
+ TAG_String("Name"): generic.luck
+ }
+ }
+ TAG_Byte("Invulnerable"): 0
+ TAG_Byte("FallFlying"): 0
+ TAG_Int("PortalCooldown"): 0
+ TAG_Float("AbsorptionAmount"): 0.0
+ TAG_Compound("abilities"): 7 entries
+ {
+ TAG_Byte("invulnerable"): 1
+ TAG_Byte("mayfly"): 1
+ TAG_Byte("instabuild"): 1
+ TAG_Float("walkSpeed"): 0.1
+ TAG_Byte("mayBuild"): 1
+ TAG_Byte("flying"): 1
+ TAG_Float("flySpeed"): 0.05
+ }
+ TAG_Float("FallDistance"): 0.0
+ TAG_Compound("recipeBook"): 6 entries
+ {
+ TAG_List("recipes"): 42 entries of type TAG_String
+ {
+ TAG_String: minecraft:activator_rail
+ TAG_String: minecraft:cobblestone_stairs
+ TAG_String: minecraft:stone_shovel
+ TAG_String: minecraft:chest
+ TAG_String: minecraft:crafting_table
+ TAG_String: minecraft:spruce_stairs
+ TAG_String: minecraft:stone
+ TAG_String: minecraft:powered_rail
+ TAG_String: minecraft:cobblestone_wall
+ TAG_String: minecraft:charcoal
+ TAG_String: minecraft:oak_planks
+ TAG_String: minecraft:stone_hoe
+ TAG_String: minecraft:oak_button
+ TAG_String: minecraft:oak_fence
+ TAG_String: minecraft:lever
+ TAG_String: minecraft:oak_stairs
+ TAG_String: minecraft:stone_pressure_plate
+ TAG_String: minecraft:oak_pressure_plate
+ TAG_String: minecraft:stone_sword
+ TAG_String: minecraft:stone_button
+ TAG_String: minecraft:stone_pickaxe
+ TAG_String: minecraft:spruce_trapdoor
+ TAG_String: minecraft:spruce_fence_gate
+ TAG_String: minecraft:spruce_fence
+ TAG_String: minecraft:stick
+ TAG_String: minecraft:cobblestone_slab
+ TAG_String: minecraft:spruce_door
+ TAG_String: minecraft:detector_rail
+ TAG_String: minecraft:oak_fence_gate
+ TAG_String: minecraft:oak_slab
+ TAG_String: minecraft:furnace
+ TAG_String: minecraft:stone_bricks
+ TAG_String: minecraft:stone_slab
+ TAG_String: minecraft:spruce_slab
+ TAG_String: minecraft:spruce_button
+ TAG_String: minecraft:oak_trapdoor
+ TAG_String: minecraft:oak_door
+ TAG_String: minecraft:sign
+ TAG_String: minecraft:stone_axe
+ TAG_String: minecraft:spruce_pressure_plate
+ TAG_String: minecraft:trapped_chest
+ TAG_String: minecraft:oak_wood
+ }
+ TAG_Byte("isFilteringCraftable"): 0
+ TAG_List("toBeDisplayed"): 42 entries of type TAG_String
+ {
+ TAG_String: minecraft:activator_rail
+ TAG_String: minecraft:cobblestone_stairs
+ TAG_String: minecraft:stone_shovel
+ TAG_String: minecraft:chest
+ TAG_String: minecraft:crafting_table
+ TAG_String: minecraft:spruce_stairs
+ TAG_String: minecraft:stone
+ TAG_String: minecraft:powered_rail
+ TAG_String: minecraft:cobblestone_wall
+ TAG_String: minecraft:charcoal
+ TAG_String: minecraft:oak_planks
+ TAG_String: minecraft:stone_hoe
+ TAG_String: minecraft:oak_button
+ TAG_String: minecraft:oak_fence
+ TAG_String: minecraft:lever
+ TAG_String: minecraft:oak_stairs
+ TAG_String: minecraft:stone_pressure_plate
+ TAG_String: minecraft:oak_pressure_plate
+ TAG_String: minecraft:stone_sword
+ TAG_String: minecraft:stone_button
+ TAG_String: minecraft:stone_pickaxe
+ TAG_String: minecraft:spruce_trapdoor
+ TAG_String: minecraft:spruce_fence_gate
+ TAG_String: minecraft:spruce_fence
+ TAG_String: minecraft:stick
+ TAG_String: minecraft:cobblestone_slab
+ TAG_String: minecraft:spruce_door
+ TAG_String: minecraft:detector_rail
+ TAG_String: minecraft:oak_fence_gate
+ TAG_String: minecraft:oak_slab
+ TAG_String: minecraft:furnace
+ TAG_String: minecraft:stone_bricks
+ TAG_String: minecraft:stone_slab
+ TAG_String: minecraft:spruce_slab
+ TAG_String: minecraft:spruce_button
+ TAG_String: minecraft:oak_trapdoor
+ TAG_String: minecraft:oak_door
+ TAG_String: minecraft:sign
+ TAG_String: minecraft:stone_axe
+ TAG_String: minecraft:spruce_pressure_plate
+ TAG_String: minecraft:trapped_chest
+ TAG_String: minecraft:oak_wood
+ }
+ TAG_Byte("isFurnaceGuiOpen"): 0
+ TAG_Byte("isGuiOpen"): 0
+ TAG_Byte("isFurnaceFilteringCraftable"): 0
+ }
+ TAG_Short("DeathTime"): 0
+ TAG_Int("XpSeed"): -962796252
+ TAG_Int("XpTotal"): 733
+ TAG_Int("playerGameType"): 1
+ TAG_Byte("seenCredits"): 0
+ TAG_List("Motion"): 3 entries of type TAG_Double
+ {
+ TAG_Double: 0.0
+ TAG_Double: 0.0
+ TAG_Double: 0.0
+ }
+ TAG_Int("SpawnY"): 82
+ TAG_Long("UUIDLeast"): -7307225268105228908
+ TAG_Float("Health"): 20.0
+ TAG_Int("SpawnZ"): 2236
+ TAG_Float("foodSaturationLevel"): 18.0
+ TAG_Int("SpawnX"): -4048
+ TAG_Short("Air"): 300
+ TAG_Byte("OnGround"): 0
+ TAG_Int("Dimension"): 0
+ TAG_List("Rotation"): 2 entries of type TAG_Float
+ {
+ TAG_Float: 16.425446
+ TAG_Float: 90.0
+ }
+ TAG_Int("XpLevel"): 22
+ TAG_Int("Score"): 733
+ TAG_Long("UUIDMost"): -6620843258805795274
+ TAG_Byte("Sleeping"): 0
+ TAG_List("Pos"): 3 entries of type TAG_Double
+ {
+ TAG_Double: 508.30314538488165
+ TAG_Double: 78.19136852275403
+ TAG_Double: 2035.9523347535592
+ }
+ TAG_Short("Fire"): -20
+ TAG_Float("XpP"): 0.74999964
+ TAG_List("EnderItems"): 0 entries of type TAG_End
+ {
+ }
+ TAG_Int("DataVersion"): 1519
+ TAG_Int("foodLevel"): 20
+ TAG_Float("foodExhaustionLevel"): 3.953829
+ TAG_Short("HurtTime"): 0
+ TAG_Int("SelectedItemSlot"): 5
+ TAG_List("ActiveEffects"): 1 entries of type TAG_Compound
+ {
+ TAG_Compound: 6 entries
+ {
+ TAG_Byte("Ambient"): 0
+ TAG_Byte("ShowIcon"): 1
+ TAG_Byte("ShowParticles"): 1
+ TAG_Int("Duration"): 1949261
+ TAG_Byte("Id"): 16
+ TAG_Byte("Amplifier"): 0
+ }
+ }
+ TAG_List("Inventory"): 24 entries of type TAG_Compound
+ {
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 0
+ TAG_String("id"): minecraft:dirt
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 1
+ TAG_String("id"): minecraft:cobblestone
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 2
+ TAG_String("id"): minecraft:stone
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 3
+ TAG_String("id"): minecraft:oak_log
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 4
+ TAG_String("id"): minecraft:oak_planks
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 5
+ TAG_String("id"): minecraft:hopper
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 6
+ TAG_String("id"): minecraft:chest
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 7
+ TAG_String("id"): minecraft:spruce_planks
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 8
+ TAG_String("id"): minecraft:polished_andesite
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 9
+ TAG_String("id"): minecraft:dirt
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 10
+ TAG_String("id"): minecraft:powered_rail
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 11
+ TAG_String("id"): minecraft:spruce_slab
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 12
+ TAG_String("id"): minecraft:tripwire_hook
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 13
+ TAG_String("id"): minecraft:bookshelf
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 14
+ TAG_String("id"): minecraft:stone_pressure_plate
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 4 entries
+ {
+ TAG_Byte("Slot"): 15
+ TAG_String("id"): minecraft:flint_and_steel
+ TAG_Byte("Count"): 1
+ TAG_Compound("tag"): 1 entries
+ {
+ TAG_Int("Damage"): 0
+ }
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 19
+ TAG_String("id"): minecraft:cobblestone
+ TAG_Byte("Count"): 10
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 20
+ TAG_String("id"): minecraft:oak_log
+ TAG_Byte("Count"): 10
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 28
+ TAG_String("id"): minecraft:cobblestone_slab
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 31
+ TAG_String("id"): minecraft:torch
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 32
+ TAG_String("id"): minecraft:furnace
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 33
+ TAG_String("id"): minecraft:polished_andesite
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 34
+ TAG_String("id"): minecraft:rail
+ TAG_Byte("Count"): 1
+ }
+ TAG_Compound: 3 entries
+ {
+ TAG_Byte("Slot"): 35
+ TAG_String("id"): minecraft:tnt
+ TAG_Byte("Count"): 1
+ }
+ }
+ TAG_Int("foodTickTimer"): 0
+ }
+ TAG_Int("SpawnY"): 64
+ TAG_Int("rainTime"): 16093
+ TAG_Int("thunderTime"): 17595
+ TAG_Int("SpawnZ"): 256
+ TAG_Byte("hardcore"): 0
+ TAG_Byte("DifficultyLocked"): 0
+ TAG_Int("SpawnX"): 128
+ TAG_Int("clearWeatherTime"): 0
+ TAG_Byte("thundering"): 0
+ TAG_Int("generatorVersion"): 1
+ TAG_Int("version"): 19133
+ TAG_Double("BorderSafeZone"): 5.0
+ TAG_Long("LastPlayed"): 1534516348794
+ TAG_Double("BorderWarningTime"): 15.0
+ TAG_String("LevelName"): Multi 5
+ TAG_Double("BorderSize"): 6.0E7
+ TAG_Int("DataVersion"): 1519
+ TAG_Compound("DataPacks"): 2 entries
+ {
+ TAG_List("Enabled"): 1 entries of type TAG_String
+ {
+ TAG_String: vanilla
+ }
+ TAG_List("Disabled"): 0 entries of type TAG_End
+ {
+ }
+ }
+ }
+}
diff --git a/src/test/resources/pre-1.13.mca b/src/test/resources/pre-1.13.mca
new file mode 100755
index 0000000..903f501
Binary files /dev/null and b/src/test/resources/pre-1.13.mca differ
diff --git a/src/test/resources/r.0.0.mca b/src/test/resources/r.0.0.mca
new file mode 100644
index 0000000..afe07d4
Binary files /dev/null and b/src/test/resources/r.0.0.mca differ
diff --git a/src/test/resources/r.1.3.mca b/src/test/resources/r.1.3.mca
new file mode 100755
index 0000000..1bac8d2
Binary files /dev/null and b/src/test/resources/r.1.3.mca differ