From 66e39f021c494220e97a77cc549b1938d7f733c4 Mon Sep 17 00:00:00 2001 From: Johny Muffin Date: Sun, 4 Aug 2024 13:04:14 +1000 Subject: [PATCH] Sequential Program --- .gitignore | 222 ++++++++++ pom.xml | 22 + .../java/com/mojang/nbt/ByteArrayTag.java | 57 +++ src/main/java/com/mojang/nbt/ByteTag.java | 52 +++ src/main/java/com/mojang/nbt/CompoundTag.java | 197 +++++++++ src/main/java/com/mojang/nbt/DoubleTag.java | 53 +++ src/main/java/com/mojang/nbt/EndTag.java | 41 ++ src/main/java/com/mojang/nbt/FloatTag.java | 53 +++ src/main/java/com/mojang/nbt/IntArrayTag.java | 61 +++ src/main/java/com/mojang/nbt/IntTag.java | 53 +++ src/main/java/com/mojang/nbt/ListTag.java | 103 +++++ src/main/java/com/mojang/nbt/LongTag.java | 53 +++ src/main/java/com/mojang/nbt/NbtIo.java | 88 ++++ src/main/java/com/mojang/nbt/ShortTag.java | 53 +++ src/main/java/com/mojang/nbt/StringTag.java | 54 +++ src/main/java/com/mojang/nbt/Tag.java | 180 +++++++++ .../world/level/biome/BiomeSource.java | 8 + .../world/level/chunk/DataLayer.java | 55 +++ .../world/level/chunk/OldDataLayer.java | 55 +++ .../level/chunk/storage/OldChunkStorage.java | 150 +++++++ .../world/level/chunk/storage/RegionFile.java | 379 ++++++++++++++++++ .../world/level/storage/AnvilConverter.java | 80 ++++ .../level/storage/AnvilLevelStorage.java | 16 + .../storage/AnvilLevelStorageSource.java | 222 ++++++++++ .../world/level/storage/LevelStorage.java | 12 + .../world/level/storage/ProgressListener.java | 18 + 26 files changed, 2337 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/mojang/nbt/ByteArrayTag.java create mode 100644 src/main/java/com/mojang/nbt/ByteTag.java create mode 100644 src/main/java/com/mojang/nbt/CompoundTag.java create mode 100644 src/main/java/com/mojang/nbt/DoubleTag.java create mode 100644 src/main/java/com/mojang/nbt/EndTag.java create mode 100644 src/main/java/com/mojang/nbt/FloatTag.java create mode 100644 src/main/java/com/mojang/nbt/IntArrayTag.java create mode 100644 src/main/java/com/mojang/nbt/IntTag.java create mode 100644 src/main/java/com/mojang/nbt/ListTag.java create mode 100644 src/main/java/com/mojang/nbt/LongTag.java create mode 100644 src/main/java/com/mojang/nbt/NbtIo.java create mode 100644 src/main/java/com/mojang/nbt/ShortTag.java create mode 100644 src/main/java/com/mojang/nbt/StringTag.java create mode 100644 src/main/java/com/mojang/nbt/Tag.java create mode 100644 src/main/java/net/minecraft/world/level/biome/BiomeSource.java create mode 100644 src/main/java/net/minecraft/world/level/chunk/DataLayer.java create mode 100644 src/main/java/net/minecraft/world/level/chunk/OldDataLayer.java create mode 100644 src/main/java/net/minecraft/world/level/chunk/storage/OldChunkStorage.java create mode 100644 src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java create mode 100644 src/main/java/net/minecraft/world/level/storage/AnvilConverter.java create mode 100644 src/main/java/net/minecraft/world/level/storage/AnvilLevelStorage.java create mode 100644 src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java create mode 100644 src/main/java/net/minecraft/world/level/storage/LevelStorage.java create mode 100644 src/main/java/net/minecraft/world/level/storage/ProgressListener.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a0543a --- /dev/null +++ b/.gitignore @@ -0,0 +1,222 @@ + +# Created by https://www.gitignore.io/api/java,intellij,intellij+all,intellij+iml +# Edit at https://www.gitignore.io/?templates=java,intellij,intellij+all,intellij+iml + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Intellij+iml ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### Intellij+iml Patch ### +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# End of https://www.gitignore.io/api/java,intellij,intellij+all,intellij+iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5551716 --- /dev/null +++ b/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.example + Server + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/src/main/java/com/mojang/nbt/ByteArrayTag.java b/src/main/java/com/mojang/nbt/ByteArrayTag.java new file mode 100644 index 0000000..e03c843 --- /dev/null +++ b/src/main/java/com/mojang/nbt/ByteArrayTag.java @@ -0,0 +1,57 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class ByteArrayTag extends Tag { + public byte[] data; + + public ByteArrayTag(String name) { + super(name); + } + + public ByteArrayTag(String name, byte[] data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeInt(data.length); + dos.write(data); + } + + void load(DataInput dis) throws IOException { + int length = dis.readInt(); + data = new byte[length]; + dis.readFully(data); + } + + public byte getId() { + return TAG_Byte_Array; + } + + public String toString() { + return "[" + data.length + " bytes]"; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + ByteArrayTag o = (ByteArrayTag) obj; + return ((data == null && o.data == null) || (data != null && data.equals(o.data))); + } + return false; + } + + @Override + public Tag copy() { + byte[] cp = new byte[data.length]; + System.arraycopy(data, 0, cp, 0, data.length); + return new ByteArrayTag(getName(), cp); + } +} diff --git a/src/main/java/com/mojang/nbt/ByteTag.java b/src/main/java/com/mojang/nbt/ByteTag.java new file mode 100644 index 0000000..217c173 --- /dev/null +++ b/src/main/java/com/mojang/nbt/ByteTag.java @@ -0,0 +1,52 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class ByteTag extends Tag { + public byte data; + + public ByteTag(String name) { + super(name); + } + + public ByteTag(String name, byte data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeByte(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readByte(); + } + + public byte getId() { + return TAG_Byte; + } + + public String toString() { + return "" + data; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + ByteTag o = (ByteTag) obj; + return data == o.data; + } + return false; + } + + @Override + public Tag copy() { + return new ByteTag(getName(), data); + } +} diff --git a/src/main/java/com/mojang/nbt/CompoundTag.java b/src/main/java/com/mojang/nbt/CompoundTag.java new file mode 100644 index 0000000..400e3e5 --- /dev/null +++ b/src/main/java/com/mojang/nbt/CompoundTag.java @@ -0,0 +1,197 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; +import java.util.*; + +public class CompoundTag extends Tag { + private Map tags = new HashMap(); + + public CompoundTag() { + super(""); + } + + public CompoundTag(String name) { + super(name); + } + + void write(DataOutput dos) throws IOException { + for (Tag tag : tags.values()) { + Tag.writeNamedTag(tag, dos); + } + dos.writeByte(Tag.TAG_End); + } + + void load(DataInput dis) throws IOException { + tags.clear(); + Tag tag; + while ((tag = Tag.readNamedTag(dis)).getId() != Tag.TAG_End) { + tags.put(tag.getName(), tag); + } + } + + public Collection getAllTags() { + return tags.values(); + } + + public byte getId() { + return TAG_Compound; + } + + public void put(String name, Tag tag) { + tags.put(name, tag.setName(name)); + } + + public void putByte(String name, byte value) { + tags.put(name, new ByteTag(name, value)); + } + + public void putShort(String name, short value) { + tags.put(name, new ShortTag(name, value)); + } + + public void putInt(String name, int value) { + tags.put(name, new IntTag(name, value)); + } + + public void putLong(String name, long value) { + tags.put(name, new LongTag(name, value)); + } + + public void putFloat(String name, float value) { + tags.put(name, new FloatTag(name, value)); + } + + public void putDouble(String name, double value) { + tags.put(name, new DoubleTag(name, value)); + } + + public void putString(String name, String value) { + tags.put(name, new StringTag(name, value)); + } + + public void putByteArray(String name, byte[] value) { + tags.put(name, new ByteArrayTag(name, value)); + } + + public void putIntArray(String name, int[] value) { + tags.put(name, new IntArrayTag(name, value)); + } + + public void putCompound(String name, CompoundTag value) { + tags.put(name, value.setName(name)); + } + + public void putBoolean(String string, boolean val) { + putByte(string, val ? (byte) 1 : 0); + } + + public Tag get(String name) { + return tags.get(name); + } + + public boolean contains(String name) { + return tags.containsKey(name); + } + + public byte getByte(String name) { + if (!tags.containsKey(name)) return (byte) 0; + return ((ByteTag) tags.get(name)).data; + } + + public short getShort(String name) { + if (!tags.containsKey(name)) return (short) 0; + return ((ShortTag) tags.get(name)).data; + } + + public int getInt(String name) { + if (!tags.containsKey(name)) return (int) 0; + return ((IntTag) tags.get(name)).data; + } + + public long getLong(String name) { + if (!tags.containsKey(name)) return (long) 0; + return ((LongTag) tags.get(name)).data; + } + + public float getFloat(String name) { + if (!tags.containsKey(name)) return (float) 0; + return ((FloatTag) tags.get(name)).data; + } + + public double getDouble(String name) { + if (!tags.containsKey(name)) return (double) 0; + return ((DoubleTag) tags.get(name)).data; + } + + public String getString(String name) { + if (!tags.containsKey(name)) return ""; + return ((StringTag) tags.get(name)).data; + } + + public byte[] getByteArray(String name) { + if (!tags.containsKey(name)) return new byte[0]; + return ((ByteArrayTag) tags.get(name)).data; + } + + public int[] getIntArray(String name) { + if (!tags.containsKey(name)) return new int[0]; + return ((IntArrayTag) tags.get(name)).data; + } + + public CompoundTag getCompound(String name) { + if (!tags.containsKey(name)) return new CompoundTag(name); + return (CompoundTag) tags.get(name); + } + + @SuppressWarnings("unchecked") + public ListTag getList(String name) { + if (!tags.containsKey(name)) return new ListTag(name); + return (ListTag) tags.get(name); + } + + public boolean getBoolean(String string) { + return getByte(string) != 0; + } + + public String toString() { + return "" + tags.size() + " entries"; + } + + public void print(String prefix, PrintStream out) { + super.print(prefix, out); + out.println(prefix + "{"); + String orgPrefix = prefix; + prefix += " "; + for (Tag tag : tags.values()) { + tag.print(prefix, out); + } + out.println(orgPrefix + "}"); + } + + public boolean isEmpty() { + return tags.isEmpty(); + } + + public Tag copy() { + CompoundTag tag = new CompoundTag(getName()); + for (String key : tags.keySet()) { + tag.put(key, tags.get(key).copy()); + } + return tag; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + CompoundTag o = (CompoundTag) obj; + return tags.entrySet().equals(o.tags.entrySet()); + } + return false; + } +} diff --git a/src/main/java/com/mojang/nbt/DoubleTag.java b/src/main/java/com/mojang/nbt/DoubleTag.java new file mode 100644 index 0000000..2b0c2ea --- /dev/null +++ b/src/main/java/com/mojang/nbt/DoubleTag.java @@ -0,0 +1,53 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class DoubleTag extends Tag { + public double data; + + public DoubleTag(String name) { + super(name); + } + + public DoubleTag(String name, double data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeDouble(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readDouble(); + } + + public byte getId() { + return TAG_Double; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new DoubleTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + DoubleTag o = (DoubleTag) obj; + return data == o.data; + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/EndTag.java b/src/main/java/com/mojang/nbt/EndTag.java new file mode 100644 index 0000000..9535939 --- /dev/null +++ b/src/main/java/com/mojang/nbt/EndTag.java @@ -0,0 +1,41 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class EndTag extends Tag { + + public EndTag() { + super(null); + } + + void load(DataInput dis) throws IOException { + } + + void write(DataOutput dos) throws IOException { + } + + public byte getId() { + return TAG_End; + } + + public String toString() { + return "END"; + } + + @Override + public Tag copy() { + return new EndTag(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + +} diff --git a/src/main/java/com/mojang/nbt/FloatTag.java b/src/main/java/com/mojang/nbt/FloatTag.java new file mode 100644 index 0000000..aa00692 --- /dev/null +++ b/src/main/java/com/mojang/nbt/FloatTag.java @@ -0,0 +1,53 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class FloatTag extends Tag { + public float data; + + public FloatTag(String name) { + super(name); + } + + public FloatTag(String name, float data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeFloat(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readFloat(); + } + + public byte getId() { + return TAG_Float; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new FloatTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + FloatTag o = (FloatTag) obj; + return data == o.data; + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/IntArrayTag.java b/src/main/java/com/mojang/nbt/IntArrayTag.java new file mode 100644 index 0000000..5ddb51e --- /dev/null +++ b/src/main/java/com/mojang/nbt/IntArrayTag.java @@ -0,0 +1,61 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class IntArrayTag extends Tag { + public int[] data; + + public IntArrayTag(String name) { + super(name); + } + + public IntArrayTag(String name, int[] data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeInt(data.length); + for (int i = 0; i < data.length; i++) { + dos.writeInt(data[i]); + } + } + + void load(DataInput dis) throws IOException { + int length = dis.readInt(); + data = new int[length]; + for (int i = 0; i < length; i++) { + data[i] = dis.readInt(); + } + } + + public byte getId() { + return TAG_Int_Array; + } + + public String toString() { + return "[" + data.length + " bytes]"; + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + IntArrayTag o = (IntArrayTag) obj; + return ((data == null && o.data == null) || (data != null && data.equals(o.data))); + } + return false; + } + + @Override + public Tag copy() { + int[] cp = new int[data.length]; + System.arraycopy(data, 0, cp, 0, data.length); + return new IntArrayTag(getName(), cp); + } +} diff --git a/src/main/java/com/mojang/nbt/IntTag.java b/src/main/java/com/mojang/nbt/IntTag.java new file mode 100644 index 0000000..ebbabb4 --- /dev/null +++ b/src/main/java/com/mojang/nbt/IntTag.java @@ -0,0 +1,53 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class IntTag extends Tag { + public int data; + + public IntTag(String name) { + super(name); + } + + public IntTag(String name, int data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeInt(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readInt(); + } + + public byte getId() { + return TAG_Int; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new IntTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + IntTag o = (IntTag) obj; + return data == o.data; + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/ListTag.java b/src/main/java/com/mojang/nbt/ListTag.java new file mode 100644 index 0000000..cd85e3f --- /dev/null +++ b/src/main/java/com/mojang/nbt/ListTag.java @@ -0,0 +1,103 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; +import java.util.*; + +public class ListTag extends Tag { + private List list = new ArrayList(); + private byte type; + + public ListTag() { + super(""); + } + + public ListTag(String name) { + super(name); + } + + void write(DataOutput dos) throws IOException { + if (list.size() > 0) type = list.get(0).getId(); + else type = 1; + + dos.writeByte(type); + dos.writeInt(list.size()); + for (int i = 0; i < list.size(); i++) + list.get(i).write(dos); + } + + @SuppressWarnings("unchecked") + void load(DataInput dis) throws IOException { + type = dis.readByte(); + int size = dis.readInt(); + + list = new ArrayList(); + for (int i = 0; i < size; i++) { + Tag tag = Tag.newTag(type, null); + tag.load(dis); + list.add((T) tag); + } + } + + public byte getId() { + return TAG_List; + } + + public String toString() { + return "" + list.size() + " entries of type " + Tag.getTagName(type); + } + + public void print(String prefix, PrintStream out) { + super.print(prefix, out); + + out.println(prefix + "{"); + String orgPrefix = prefix; + prefix += " "; + for (int i = 0; i < list.size(); i++) + list.get(i).print(prefix, out); + out.println(orgPrefix + "}"); + } + + public void add(T tag) { + type = tag.getId(); + list.add(tag); + } + + public T get(int index) { + return list.get(index); + } + + public int size() { + return list.size(); + } + + @Override + public Tag copy() { + ListTag res = new ListTag(getName()); + res.type = type; + for (T t : list) { + @SuppressWarnings("unchecked") + T copy = (T) t.copy(); + res.list.add(copy); + } + return res; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + ListTag o = (ListTag) obj; + if (type == o.type) { + return list.equals(o.list); + } + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/LongTag.java b/src/main/java/com/mojang/nbt/LongTag.java new file mode 100644 index 0000000..96e0a05 --- /dev/null +++ b/src/main/java/com/mojang/nbt/LongTag.java @@ -0,0 +1,53 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class LongTag extends Tag { + public long data; + + public LongTag(String name) { + super(name); + } + + public LongTag(String name, long data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeLong(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readLong(); + } + + public byte getId() { + return TAG_Long; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new LongTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + LongTag o = (LongTag) obj; + return data == o.data; + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/NbtIo.java b/src/main/java/com/mojang/nbt/NbtIo.java new file mode 100644 index 0000000..cca912c --- /dev/null +++ b/src/main/java/com/mojang/nbt/NbtIo.java @@ -0,0 +1,88 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; +import java.util.zip.*; + +public class NbtIo { + public static CompoundTag readCompressed(InputStream in) throws IOException { + DataInputStream dis = new DataInputStream(new BufferedInputStream(new GZIPInputStream(in))); + try { + return read(dis); + } finally { + dis.close(); + } + } + + public static void writeCompressed(CompoundTag tag, OutputStream out) throws IOException { + DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(out)); + try { + write(tag, dos); + } finally { + dos.close(); + } + } + + public static CompoundTag decompress(byte[] buffer) throws IOException { + DataInputStream dis = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(buffer)))); + try { + return read(dis); + } finally { + dis.close(); + } + } + + public static byte[] compress(CompoundTag tag) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(baos)); + try { + write(tag, dos); + } finally { + dos.close(); + } + return baos.toByteArray(); + } + + public static void safeWrite(CompoundTag tag, File file) throws IOException { + File file2 = new File(file.getAbsolutePath() + "_tmp"); + if (file2.exists()) file2.delete(); + write(tag, file2); + if (file.exists()) file.delete(); + if (file.exists()) throw new IOException("Failed to delete " + file); + file2.renameTo(file); + } + + public static void write(CompoundTag tag, File file) throws IOException { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); + try { + write(tag, dos); + } finally { + dos.close(); + } + } + + public static CompoundTag read(File file) throws IOException { + if (!file.exists()) return null; + DataInputStream dis = new DataInputStream(new FileInputStream(file)); + try { + return read(dis); + } finally { + dis.close(); + } + } + + public static CompoundTag read(DataInput dis) throws IOException { + Tag tag = Tag.readNamedTag(dis); + if (tag instanceof CompoundTag) return (CompoundTag) tag; + throw new IOException("Root tag must be a named compound tag"); + } + + public static void write(CompoundTag tag, DataOutput dos) throws IOException { + Tag.writeNamedTag(tag, dos); + } +} diff --git a/src/main/java/com/mojang/nbt/ShortTag.java b/src/main/java/com/mojang/nbt/ShortTag.java new file mode 100644 index 0000000..b738b91 --- /dev/null +++ b/src/main/java/com/mojang/nbt/ShortTag.java @@ -0,0 +1,53 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class ShortTag extends Tag { + public short data; + + public ShortTag(String name) { + super(name); + } + + public ShortTag(String name, short data) { + super(name); + this.data = data; + } + + void write(DataOutput dos) throws IOException { + dos.writeShort(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readShort(); + } + + public byte getId() { + return TAG_Short; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new ShortTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + ShortTag o = (ShortTag) obj; + return data == o.data; + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/StringTag.java b/src/main/java/com/mojang/nbt/StringTag.java new file mode 100644 index 0000000..8bc64b9 --- /dev/null +++ b/src/main/java/com/mojang/nbt/StringTag.java @@ -0,0 +1,54 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public class StringTag extends Tag { + public String data; + + public StringTag(String name) { + super(name); + } + + public StringTag(String name, String data) { + super(name); + this.data = data; + if (data == null) throw new IllegalArgumentException("Empty string not allowed"); + } + + void write(DataOutput dos) throws IOException { + dos.writeUTF(data); + } + + void load(DataInput dis) throws IOException { + data = dis.readUTF(); + } + + public byte getId() { + return TAG_String; + } + + public String toString() { + return "" + data; + } + + @Override + public Tag copy() { + return new StringTag(getName(), data); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj)) { + StringTag o = (StringTag) obj; + return ((data == null && o.data == null) || (data != null && data.equals(o.data))); + } + return false; + } + +} diff --git a/src/main/java/com/mojang/nbt/Tag.java b/src/main/java/com/mojang/nbt/Tag.java new file mode 100644 index 0000000..3d5d9c0 --- /dev/null +++ b/src/main/java/com/mojang/nbt/Tag.java @@ -0,0 +1,180 @@ +package com.mojang.nbt; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; + +public abstract class Tag { + public static final byte TAG_End = 0; + public static final byte TAG_Byte = 1; + public static final byte TAG_Short = 2; + public static final byte TAG_Int = 3; + public static final byte TAG_Long = 4; + public static final byte TAG_Float = 5; + public static final byte TAG_Double = 6; + public static final byte TAG_Byte_Array = 7; + public static final byte TAG_String = 8; + public static final byte TAG_List = 9; + public static final byte TAG_Compound = 10; + public static final byte TAG_Int_Array = 11; + + private String name; + + abstract void write(DataOutput dos) throws IOException; + + abstract void load(DataInput dis) throws IOException; + + public abstract String toString(); + + public abstract byte getId(); + + protected Tag(String name) { + if (name == null) { + this.name = ""; + } else { + this.name = name; + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Tag)) { + return false; + } + Tag o = (Tag) obj; + if (getId() != o.getId()) { + return false; + } + if (name == null && o.name != null || name != null && o.name == null) { + return false; + } + if (name != null && !name.equals(o.name)) { + return false; + } + return true; + } + + public void print(PrintStream out) { + print("", out); + } + + public void print(String prefix, PrintStream out) { + String name = getName(); + + out.print(prefix); + out.print(getTagName(getId())); + if (name.length() > 0) { + out.print("(\"" + name + "\")"); + } + out.print(": "); + out.println(toString()); + } + + public Tag setName(String name) { + if (name == null) { + this.name = ""; + } else { + this.name = name; + } + return this; + } + + public String getName() { + if (name == null) return ""; + return name; + } + + public static Tag readNamedTag(DataInput dis) throws IOException { + byte type = dis.readByte(); + if (type == 0) return new EndTag(); + + String name = dis.readUTF();// new String(bytes, "UTF-8"); + + Tag tag = newTag(type, name); +// short length = dis.readShort(); +// byte[] bytes = new byte[length]; +// dis.readFully(bytes); + + tag.load(dis); + return tag; + } + + public static void writeNamedTag(Tag tag, DataOutput dos) throws IOException { + dos.writeByte(tag.getId()); + if (tag.getId() == Tag.TAG_End) return; + +// byte[] bytes = tag.getName().getBytes("UTF-8"); +// dos.writeShort(bytes.length); +// dos.write(bytes); + dos.writeUTF(tag.getName()); + + tag.write(dos); + } + + public static Tag newTag(byte type, String name) { + switch (type) { + case TAG_End: + return new EndTag(); + case TAG_Byte: + return new ByteTag(name); + case TAG_Short: + return new ShortTag(name); + case TAG_Int: + return new IntTag(name); + case TAG_Long: + return new LongTag(name); + case TAG_Float: + return new FloatTag(name); + case TAG_Double: + return new DoubleTag(name); + case TAG_Byte_Array: + return new ByteArrayTag(name); + case TAG_Int_Array: + return new IntArrayTag(name); + case TAG_String: + return new StringTag(name); + case TAG_List: + return new ListTag(name); + case TAG_Compound: + return new CompoundTag(name); + } + return null; + } + + public static String getTagName(byte type) { + switch (type) { + case TAG_End: + return "TAG_End"; + case TAG_Byte: + return "TAG_Byte"; + case TAG_Short: + return "TAG_Short"; + case TAG_Int: + return "TAG_Int"; + case TAG_Long: + return "TAG_Long"; + case TAG_Float: + return "TAG_Float"; + case TAG_Double: + return "TAG_Double"; + case TAG_Byte_Array: + return "TAG_Byte_Array"; + case TAG_Int_Array: + return "TAG_Int_Array"; + case TAG_String: + return "TAG_String"; + case TAG_List: + return "TAG_List"; + case TAG_Compound: + return "TAG_Compound"; + } + return "UNKNOWN"; + } + + public abstract Tag copy(); + +} diff --git a/src/main/java/net/minecraft/world/level/biome/BiomeSource.java b/src/main/java/net/minecraft/world/level/biome/BiomeSource.java new file mode 100644 index 0000000..dd3bd93 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/biome/BiomeSource.java @@ -0,0 +1,8 @@ +package net.minecraft.world.level.biome; + + +public interface BiomeSource { + + public int getBiomeId(int x, int z); + +} diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java new file mode 100644 index 0000000..97ec71e --- /dev/null +++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java @@ -0,0 +1,55 @@ +package net.minecraft.world.level.chunk; + +public class DataLayer { + public final byte[] data; + private final int depthBits; + private final int depthBitsPlusFour; + + public DataLayer(int length, int depthBits) { + data = new byte[length >> 1]; + this.depthBits = depthBits; + depthBitsPlusFour = depthBits + 4; + } + + public DataLayer(byte[] data, int depthBits) { + this.data = data; + this.depthBits = depthBits; + depthBitsPlusFour = depthBits + 4; + } + + public int get(int x, int y, int z) { + int pos = (y << depthBitsPlusFour | z << depthBits | x); + int slot = pos >> 1; + int part = pos & 1; + + if (part == 0) { + return data[slot] & 0xf; + } else { + return (data[slot] >> 4) & 0xf; + } + } + + public void set(int x, int y, int z, int val) { + int pos = (y << depthBitsPlusFour | z << depthBits | x); + + int slot = pos >> 1; + int part = pos & 1; + + if (part == 0) { + data[slot] = (byte) ((data[slot] & 0xf0) | (val & 0xf)); + } else { + data[slot] = (byte) ((data[slot] & 0x0f) | ((val & 0xf) << 4)); + } + } + + public boolean isValid() { + return data != null; + } + + public void setAll(int br) { + byte val = (byte) ((br | (br << 4)) & 0xff); + for (int i = 0; i < data.length; i++) { + data[i] = val; + } + } +} diff --git a/src/main/java/net/minecraft/world/level/chunk/OldDataLayer.java b/src/main/java/net/minecraft/world/level/chunk/OldDataLayer.java new file mode 100644 index 0000000..b6e63f2 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/chunk/OldDataLayer.java @@ -0,0 +1,55 @@ +package net.minecraft.world.level.chunk; + +public class OldDataLayer { + public final byte[] data; + private final int depthBits; + private final int depthBitsPlusFour; + + public OldDataLayer(int length, int depthBits) { + data = new byte[length >> 1]; + this.depthBits = depthBits; + depthBitsPlusFour = depthBits + 4; + } + + public OldDataLayer(byte[] data, int depthBits) { + this.data = data; + this.depthBits = depthBits; + depthBitsPlusFour = depthBits + 4; + } + + public int get(int x, int y, int z) { + int pos = (x << depthBitsPlusFour | z << depthBits | y); + int slot = pos >> 1; + int part = pos & 1; + + if (part == 0) { + return data[slot] & 0xf; + } else { + return (data[slot] >> 4) & 0xf; + } + } + + public void set(int x, int y, int z, int val) { + int pos = (x << depthBitsPlusFour | z << depthBits | y); + + int slot = pos >> 1; + int part = pos & 1; + + if (part == 0) { + data[slot] = (byte) ((data[slot] & 0xf0) | (val & 0xf)); + } else { + data[slot] = (byte) ((data[slot] & 0x0f) | ((val & 0xf) << 4)); + } + } + + public boolean isValid() { + return data != null; + } + + public void setAll(int br) { + byte val = (byte) ((br | (br << 4)) & 0xff); + for (int i = 0; i < data.length; i++) { + data[i] = val; + } + } +} diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/OldChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/OldChunkStorage.java new file mode 100644 index 0000000..bde3881 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/chunk/storage/OldChunkStorage.java @@ -0,0 +1,150 @@ +package net.minecraft.world.level.chunk.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.*; + +import com.mojang.nbt.*; + +public class OldChunkStorage { + + private final static int DATALAYER_BITS = 7; + + @SuppressWarnings("unchecked") + public static OldLevelChunk load(CompoundTag tag) { + int x = tag.getInt("xPos"); + int z = tag.getInt("zPos"); + + OldLevelChunk levelChunk = new OldLevelChunk(x, z); + levelChunk.blocks = tag.getByteArray("Blocks"); + levelChunk.data = new OldDataLayer(tag.getByteArray("Data"), DATALAYER_BITS); + levelChunk.skyLight = new OldDataLayer(tag.getByteArray("SkyLight"), DATALAYER_BITS); + levelChunk.blockLight = new OldDataLayer(tag.getByteArray("BlockLight"), DATALAYER_BITS); + levelChunk.heightmap = tag.getByteArray("HeightMap"); + levelChunk.terrainPopulated = tag.getBoolean("TerrainPopulated"); + levelChunk.entities = (ListTag) tag.getList("Entities"); + levelChunk.tileEntities = (ListTag) tag.getList("TileEntities"); + levelChunk.tileTicks = (ListTag) tag.getList("TileTicks"); + levelChunk.lastUpdated = tag.getLong("LastUpdate"); + + return levelChunk; + } + + public static void convertToAnvilFormat(OldLevelChunk data, CompoundTag tag, BiomeSource biomeSource) { + + tag.putInt("xPos", data.x); + tag.putInt("zPos", data.z); + tag.putLong("LastUpdate", data.lastUpdated); + int[] newHeight = new int[data.heightmap.length]; + for (int i = 0; i < data.heightmap.length; i++) { + newHeight[i] = data.heightmap[i]; + } + tag.putIntArray("HeightMap", newHeight); + tag.putBoolean("TerrainPopulated", data.terrainPopulated); + + ListTag sectionTags = new ListTag("Sections"); + for (int yBase = 0; yBase < (128 / 16); yBase++) { + + // find non-air + boolean allAir = true; + for (int x = 0; x < 16 && allAir; x++) { + for (int y = 0; y < 16 && allAir; y++) { + for (int z = 0; z < 16; z++) { + int pos = (x << 11) | (z << 7) | (y + (yBase << 4)); + int block = data.blocks[pos]; + if (block != 0) { + allAir = false; + break; + } + } + } + } + + if (allAir) { + continue; + } + + // build section + byte[] blocks = new byte[16 * 16 * 16]; + DataLayer dataValues = new DataLayer(blocks.length, 4); + DataLayer skyLight = new DataLayer(blocks.length, 4); + DataLayer blockLight = new DataLayer(blocks.length, 4); + + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + int pos = (x << 11) | (z << 7) | (y + (yBase << 4)); + int block = data.blocks[pos]; + + blocks[(y << 8) | (z << 4) | x] = (byte) (block & 0xff); + dataValues.set(x, y, z, data.data.get(x, y + (yBase << 4), z)); + skyLight.set(x, y, z, data.skyLight.get(x, y + (yBase << 4), z)); + blockLight.set(x, y, z, data.blockLight.get(x, y + (yBase << 4), z)); + } + } + } + + CompoundTag sectionTag = new CompoundTag(); + + sectionTag.putByte("Y", (byte) (yBase & 0xff)); + sectionTag.putByteArray("Blocks", blocks); + sectionTag.putByteArray("Data", dataValues.data); + sectionTag.putByteArray("SkyLight", skyLight.data); + sectionTag.putByteArray("BlockLight", blockLight.data); + + sectionTags.add(sectionTag); + } + tag.put("Sections", sectionTags); + + // create biome array + if (biomeSource != null) { + byte[] biomes = new byte[16 * 16]; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + biomes[(z << 4) | x] = (byte) (biomeSource.getBiomeId((data.x << 4) | x, (data.z << 4) | z) & 0xff); + } + } + tag.putByteArray("Biomes", biomes); + } + + tag.put("Entities", data.entities); + + tag.put("TileEntities", data.tileEntities); + + if (data.tileTicks != null) { + tag.put("TileTicks", data.tileTicks); + } + } + + public static class OldLevelChunk { + + public long lastUpdated; + public boolean lastSaveHadEntities; + public boolean terrainPopulated; + public byte[] heightmap; + public OldDataLayer blockLight; + public OldDataLayer skyLight; + public OldDataLayer data; + public byte[] blocks; + + public ListTag entities; + public ListTag tileEntities; + public ListTag tileTicks; + + public final int x; + public final int z; + + public OldLevelChunk(int x, int z) { + this.x = x; + this.z = z; + } + + + } + +} diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java new file mode 100644 index 0000000..18cba7e --- /dev/null +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java @@ -0,0 +1,379 @@ +package net.minecraft.world.level.chunk.storage; + +/* + ** 2011 January 5 + ** + ** The author disclaims copyright to this source code. In place of + ** a legal notice, here is a blessing: + ** + ** May you do good and not evil. + ** May you find forgiveness for yourself and forgive others. + ** May you share freely, never taking more than you give. + **/ + +/* + * 2011 February 16 + * + * This source code is based on the work of Scaevolus (see notice above). + * It has been slightly modified by Mojang AB (constants instead of magic + * numbers, a chunk timestamp header, and auto-formatted according to our + * formatter template). + * + */ + +// Interfaces with region files on the disk + +/* + + Region File Format + + Concept: The minimum unit of storage on hard drives is 4KB. 90% of Minecraft + chunks are smaller than 4KB. 99% are smaller than 8KB. Write a simple + container to store chunks in single files in runs of 4KB sectors. + + Each region file represents a 32x32 group of chunks. The conversion from + chunk number to region number is floor(coord / 32): a chunk at (30, -3) + would be in region (0, -1), and one at (70, -30) would be at (3, -1). + Region files are named "r.x.z.data", where x and z are the region coordinates. + + A region file begins with a 4KB header that describes where chunks are stored + in the file. A 4-byte big-endian integer represents sector offsets and sector + counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the + file. The bottom byte of the chunk offset indicates the number of sectors the + chunk takes up, and the top 3 bytes represent the sector number of the chunk. + Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up + at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk + offset is 0, the corresponding chunk is not stored in the region file. + + Chunk data begins with a 4-byte big-endian integer representing the chunk data + length in bytes, not counting the length field. The length must be smaller than + 4096 times the number of sectors. The next byte is a version field, to allow + backwards-compatible updates to how chunks are encoded. + + A version of 1 represents a gzipped NBT file. The gzipped data is the chunk + length - 1. + + A version of 2 represents a deflated (zlib compressed) NBT file. The deflated + data is the chunk length - 1. + + */ + +import java.io.*; +import java.util.ArrayList; +import java.util.zip.*; + +public class RegionFile { + + public static final String ANVIL_EXTENSION = ".mca"; + public static final String MCREGION_EXTENSION = ".mcr"; + + private static final int VERSION_GZIP = 1; + private static final int VERSION_DEFLATE = 2; + + private static final int SECTOR_BYTES = 4096; + private static final int SECTOR_INTS = SECTOR_BYTES / 4; + + static final int CHUNK_HEADER_SIZE = 5; + private static final byte emptySector[] = new byte[4096]; + + private final File fileName; + private RandomAccessFile file; + private final int offsets[]; + private final int chunkTimestamps[]; + private ArrayList sectorFree; + private int sizeDelta; + private long lastModified = 0; + + public RegionFile(File path) { + offsets = new int[SECTOR_INTS]; + chunkTimestamps = new int[SECTOR_INTS]; + + fileName = path; + debugln("REGION LOAD " + fileName); + + sizeDelta = 0; + + try { + if (path.exists()) { + lastModified = path.lastModified(); + } + + file = new RandomAccessFile(path, "rw"); + + if (file.length() < SECTOR_BYTES) { + /* we need to write the chunk offset table */ + for (int i = 0; i < SECTOR_INTS; ++i) { + file.writeInt(0); + } + // write another sector for the timestamp info + for (int i = 0; i < SECTOR_INTS; ++i) { + file.writeInt(0); + } + + sizeDelta += SECTOR_BYTES * 2; + } + + if ((file.length() & 0xfff) != 0) { + /* the file size is not a multiple of 4KB, grow it */ + for (int i = 0; i < (file.length() & 0xfff); ++i) { + file.write((byte) 0); + } + } + + /* set up the available sector map */ + int nSectors = (int) file.length() / SECTOR_BYTES; + sectorFree = new ArrayList(nSectors); + + for (int i = 0; i < nSectors; ++i) { + sectorFree.add(true); + } + + sectorFree.set(0, false); // chunk offset table + sectorFree.set(1, false); // for the last modified info + + file.seek(0); + for (int i = 0; i < SECTOR_INTS; ++i) { + int offset = file.readInt(); + offsets[i] = offset; + if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.size()) { + for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) { + sectorFree.set((offset >> 8) + sectorNum, false); + } + } + } + for (int i = 0; i < SECTOR_INTS; ++i) { + int lastModValue = file.readInt(); + chunkTimestamps[i] = lastModValue; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /* the modification date of the region file when it was first opened */ + public long lastModified() { + return lastModified; + } + + /* gets how much the region file has grown since it was last checked */ + public synchronized int getSizeDelta() { + int ret = sizeDelta; + sizeDelta = 0; + return ret; + } + + // various small debug printing helpers + private void debug(String in) { +// System.out.print(in); + } + + private void debugln(String in) { + debug(in + "\n"); + } + + private void debug(String mode, int x, int z, String in) { + debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] = " + in); + } + + private void debug(String mode, int x, int z, int count, String in) { + debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] " + count + "B = " + in); + } + + private void debugln(String mode, int x, int z, String in) { + debug(mode, x, z, in + "\n"); + } + + /* + * gets an (uncompressed) stream representing the chunk data returns null if + * the chunk is not found or an error occurs + */ + public synchronized DataInputStream getChunkDataInputStream(int x, int z) { + if (outOfBounds(x, z)) { + debugln("READ", x, z, "out of bounds"); + return null; + } + + try { + int offset = getOffset(x, z); + if (offset == 0) { + // debugln("READ", x, z, "miss"); + return null; + } + + int sectorNumber = offset >> 8; + int numSectors = offset & 0xFF; + + if (sectorNumber + numSectors > sectorFree.size()) { + debugln("READ", x, z, "invalid sector"); + return null; + } + + file.seek(sectorNumber * SECTOR_BYTES); + int length = file.readInt(); + + if (length > SECTOR_BYTES * numSectors) { + debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors); + return null; + } + + byte version = file.readByte(); + if (version == VERSION_GZIP) { + byte[] data = new byte[length - 1]; + file.read(data); + DataInputStream ret = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(data)))); + // debug("READ", x, z, " = found"); + return ret; + } else if (version == VERSION_DEFLATE) { + byte[] data = new byte[length - 1]; + file.read(data); + DataInputStream ret = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(data)))); + // debug("READ", x, z, " = found"); + return ret; + } + + debugln("READ", x, z, "unknown version " + version); + return null; + } catch (IOException e) { + debugln("READ", x, z, "exception"); + return null; + } + } + + public DataOutputStream getChunkDataOutputStream(int x, int z) { + if (outOfBounds(x, z)) return null; + + return new DataOutputStream(new DeflaterOutputStream(new ChunkBuffer(x, z))); + } + + /* + * lets chunk writing be multithreaded by not locking the whole file as a + * chunk is serializing -- only writes when serialization is over + */ + class ChunkBuffer extends ByteArrayOutputStream { + private int x, z; + + public ChunkBuffer(int x, int z) { + super(8096); // initialize to 8KB + this.x = x; + this.z = z; + } + + public void close() { + RegionFile.this.write(x, z, buf, count); + } + } + + /* write a chunk at (x,z) with length bytes of data to disk */ + protected synchronized void write(int x, int z, byte[] data, int length) { + try { + int offset = getOffset(x, z); + int sectorNumber = offset >> 8; + int sectorsAllocated = offset & 0xFF; + int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1; + + // maximum chunk size is 1MB + if (sectorsNeeded >= 256) { + return; + } + + if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded) { + /* we can simply overwrite the old sectors */ + debug("SAVE", x, z, length, "rewrite"); + write(sectorNumber, data, length); + } else { + /* we need to allocate new sectors */ + + /* mark the sectors previously used for this chunk as free */ + for (int i = 0; i < sectorsAllocated; ++i) { + sectorFree.set(sectorNumber + i, true); + } + + /* scan for a free space large enough to store this chunk */ + int runStart = sectorFree.indexOf(true); + int runLength = 0; + if (runStart != -1) { + for (int i = runStart; i < sectorFree.size(); ++i) { + if (runLength != 0) { + if (sectorFree.get(i)) runLength++; + else runLength = 0; + } else if (sectorFree.get(i)) { + runStart = i; + runLength = 1; + } + if (runLength >= sectorsNeeded) { + break; + } + } + } + + if (runLength >= sectorsNeeded) { + /* we found a free space large enough */ + debug("SAVE", x, z, length, "reuse"); + sectorNumber = runStart; + setOffset(x, z, (sectorNumber << 8) | sectorsNeeded); + for (int i = 0; i < sectorsNeeded; ++i) { + sectorFree.set(sectorNumber + i, false); + } + write(sectorNumber, data, length); + } else { + /* + * no free space large enough found -- we need to grow the + * file + */ + debug("SAVE", x, z, length, "grow"); + file.seek(file.length()); + sectorNumber = sectorFree.size(); + for (int i = 0; i < sectorsNeeded; ++i) { + file.write(emptySector); + sectorFree.add(false); + } + sizeDelta += SECTOR_BYTES * sectorsNeeded; + + write(sectorNumber, data, length); + setOffset(x, z, (sectorNumber << 8) | sectorsNeeded); + } + } + setTimestamp(x, z, (int) (System.currentTimeMillis() / 1000L)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /* write a chunk data to the region file at specified sector number */ + private void write(int sectorNumber, byte[] data, int length) throws IOException { + debugln(" " + sectorNumber); + file.seek(sectorNumber * SECTOR_BYTES); + file.writeInt(length + 1); // chunk length + file.writeByte(VERSION_DEFLATE); // chunk version number + file.write(data, 0, length); // chunk data + } + + /* is this an invalid chunk coordinate? */ + private boolean outOfBounds(int x, int z) { + return x < 0 || x >= 32 || z < 0 || z >= 32; + } + + private int getOffset(int x, int z) { + return offsets[x + z * 32]; + } + + public boolean hasChunk(int x, int z) { + return getOffset(x, z) != 0; + } + + private void setOffset(int x, int z, int offset) throws IOException { + offsets[x + z * 32] = offset; + file.seek((x + z * 32) * 4); + file.writeInt(offset); + } + + private void setTimestamp(int x, int z, int value) throws IOException { + chunkTimestamps[x + z * 32] = value; + file.seek(SECTOR_BYTES + (x + z * 32) * 4); + file.writeInt(value); + } + + public void close() throws IOException { + file.close(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java b/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java new file mode 100644 index 0000000..eebb5f7 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java @@ -0,0 +1,80 @@ +package net.minecraft.world.level.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + + +import java.io.File; + +public class AnvilConverter { + + public static void main(String[] args) { + + if (args.length != 2) { + printUsageAndExit(); + } + + File baseFolder; + try { + baseFolder = new File(args[0]); + if (!baseFolder.exists()) { + throw new RuntimeException(args[0] + " doesn't exist"); + } else if (!baseFolder.isDirectory()) { + throw new RuntimeException(args[0] + " is not a folder"); + } + } catch (Exception e) { + System.err.println("Base folder problem: " + e.getMessage()); + System.out.println(""); + printUsageAndExit(); + return; + } + + AnvilLevelStorageSource storage = new AnvilLevelStorageSource(baseFolder); + if (!storage.isConvertible(args[1])) { + System.err.println("World called " + args[1] + " is not convertible to the Anvil format"); + System.out.println(""); + printUsageAndExit(); + return; + } + + System.out.println("Converting map!"); + storage.convertLevel(args[1], new ProgressListener() { + private long timeStamp = System.currentTimeMillis(); + + public void progressStartNoAbort(String string) { + } + + public void progressStart(String string) { + } + + public void progressStagePercentage(int i) { + if ((System.currentTimeMillis() - timeStamp) >= 1000L) { + timeStamp = System.currentTimeMillis(); + System.out.println("Converting... " + i + "%"); + } + } + + public void progressStage(String string) { + } + }); + System.out.println("Done!"); + System.out.println("To revert, replace level.dat with level.dat_mcr. Old mcr region files have not been modified."); + } + + private static void printUsageAndExit() { + System.out.println("Map converter for Minecraft, from format \"McRegion\" to \"Anvil\". (c) Mojang AB 2012"); + System.out.println(""); + System.out.println("Usage:"); + System.out.println("\tjava -jar AnvilConverter.jar "); + System.out.println("Where:"); + System.out.println("\t\tThe full path to the folder containing Minecraft world folders"); + System.out.println("\t\tThe folder name of the Minecraft world to be converted"); + System.out.println("Example:"); + System.out.println("\tjava -jar AnvilConverter.jar /home/jeb_/minecraft world"); + System.exit(1); + } + +} diff --git a/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorage.java b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorage.java new file mode 100644 index 0000000..208e2e1 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorage.java @@ -0,0 +1,16 @@ +package net.minecraft.world.level.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +public class AnvilLevelStorage { + + protected static final int MCREGION_VERSION_ID = 0x4abc; + protected static final int ANVIL_VERSION_ID = 0x4abd; + + + +} diff --git a/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java new file mode 100644 index 0000000..3a42ca3 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java @@ -0,0 +1,222 @@ +package net.minecraft.world.level.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +import java.io.*; +import java.util.ArrayList; + +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.storage.*; +import net.minecraft.world.level.chunk.storage.OldChunkStorage.OldLevelChunk; + +import com.mojang.nbt.*; + +public class AnvilLevelStorageSource { + + private File baseDir; + + public AnvilLevelStorageSource(File dir) { + baseDir = dir; + } + + public boolean isConvertible(String levelId) { + + // check if there is old file format level data + CompoundTag levelData = getDataTagFor(levelId); + if (levelData == null || levelData.getInt("version") != AnvilLevelStorage.MCREGION_VERSION_ID) { + return false; + } + + return true; + } + + private CompoundTag getDataTagFor(String levelId) { + File dir = new File(baseDir, levelId); + if (!dir.exists()) return null; + + File dataFile = new File(dir, "level.dat"); + if (dataFile.exists()) { + try { + CompoundTag root = NbtIo.readCompressed(new FileInputStream(dataFile)); + CompoundTag tag = root.getCompound("Data"); + return tag; + } catch (Exception e) { + e.printStackTrace(); + } + } + + dataFile = new File(dir, "level.dat_old"); + if (dataFile.exists()) { + try { + CompoundTag root = NbtIo.readCompressed(new FileInputStream(dataFile)); + CompoundTag tag = root.getCompound("Data"); + return tag; + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + private void saveDataTag(String levelId, CompoundTag dataTag) { + File dir = new File(baseDir, levelId); + if (!dir.exists()) return; + + File dataFile = new File(dir, "level.dat"); + if (dataFile.exists()) { + try { + CompoundTag root = new CompoundTag(); + root.put("Data", dataTag); + + NbtIo.writeCompressed(root, new FileOutputStream(dataFile)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public boolean convertLevel(String levelId, ProgressListener progress) { + + progress.progressStagePercentage(0); + + ArrayList normalRegions = new ArrayList(); + ArrayList netherRegions = new ArrayList(); + ArrayList enderRegions = new ArrayList(); +// + File baseFolder = new File(baseDir, levelId); + File netherFolder = new File(baseFolder, LevelStorage.NETHER_FOLDER); + File enderFolder = new File(baseFolder, LevelStorage.ENDER_FOLDER); + + System.out.println("Scanning folders..."); + + // find normal world + addRegionFiles(baseFolder, normalRegions); + + // find hell world + if (netherFolder.exists()) { + addRegionFiles(netherFolder, netherRegions); + } + if (enderFolder.exists()) { + addRegionFiles(enderFolder, enderRegions); + } + + int totalCount = normalRegions.size() + netherRegions.size() + enderRegions.size(); + System.out.println("Total conversion count is " + totalCount); + + CompoundTag levelData = getDataTagFor(levelId); + + // convert normal world + convertRegions(new File(baseFolder, "region"), normalRegions, null, 0, totalCount, progress); + // convert hell world + convertRegions(new File(netherFolder, "region"), netherRegions, null, normalRegions.size(), totalCount, progress); + // convert end world + convertRegions(new File(enderFolder, "region"), enderRegions, null, normalRegions.size() + netherRegions.size(), totalCount, progress); + + makeMcrLevelDatBackup(levelId); + + levelData.putInt("version", AnvilLevelStorage.ANVIL_VERSION_ID); + saveDataTag(levelId, levelData); + + return true; + } + + private void makeMcrLevelDatBackup(String levelId) { + File dir = new File(baseDir, levelId); + if (!dir.exists()) { + System.out.println("Warning: Unable to create level.dat_mcr backup"); + return; + } + + File dataFile = new File(dir, "level.dat"); + if (!dataFile.exists()) { + System.out.println("Warning: Unable to create level.dat_mcr backup"); + return; + } + + File newName = new File(dir, "level.dat_mcr"); + if (!dataFile.renameTo(newName)) { + System.out.println("Warning: Unable to create level.dat_mcr backup"); + } + } + + private void convertRegions(File baseFolder, ArrayList regionFiles, BiomeSource biomeSource, int currentCount, int totalCount, ProgressListener progress) { + + for (File regionFile : regionFiles) { + convertRegion(baseFolder, regionFile, biomeSource, currentCount, totalCount, progress); + + currentCount++; + int percent = (int) Math.round(100.0d * (double) currentCount / (double) totalCount); + progress.progressStagePercentage(percent); + } + + } + + private void convertRegion(File baseFolder, File regionFile, BiomeSource biomeSource, int currentCount, int totalCount, ProgressListener progress) { + + try { + String name = regionFile.getName(); + + RegionFile regionSource = new RegionFile(regionFile); + RegionFile regionDest = new RegionFile(new File(baseFolder, name.substring(0, name.length() - RegionFile.MCREGION_EXTENSION.length()) + RegionFile.ANVIL_EXTENSION)); + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (regionSource.hasChunk(x, z) && !regionDest.hasChunk(x, z)) { + DataInputStream regionChunkInputStream = regionSource.getChunkDataInputStream(x, z); + if (regionChunkInputStream == null) { + System.out.println("Failed to fetch input stream"); + continue; + } + CompoundTag chunkData = NbtIo.read(regionChunkInputStream); + regionChunkInputStream.close(); + + CompoundTag compound = chunkData.getCompound("Level"); + { + OldLevelChunk oldChunk = OldChunkStorage.load(compound); + + CompoundTag tag = new CompoundTag(); + CompoundTag levelData = new CompoundTag(); + tag.put("Level", levelData); + OldChunkStorage.convertToAnvilFormat(oldChunk, levelData, biomeSource); + + DataOutputStream chunkDataOutputStream = regionDest.getChunkDataOutputStream(x, z); + NbtIo.write(tag, chunkDataOutputStream); + chunkDataOutputStream.close(); + } + } + } + int basePercent = (int) Math.round(100.0d * (double) (currentCount * 1024) / (double) (totalCount * 1024)); + int newPercent = (int) Math.round(100.0d * (double) ((x + 1) * 32 + currentCount * 1024) / (double) (totalCount * 1024)); + if (newPercent > basePercent) { + progress.progressStagePercentage(newPercent); + } + } + + regionSource.close(); + regionDest.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void addRegionFiles(File baseFolder, ArrayList regionFiles) { + + File regionFolder = new File(baseFolder, "region"); + File[] list = regionFolder.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(RegionFile.MCREGION_EXTENSION); + } + }); + + if (list != null) { + for (File file : list) { + regionFiles.add(file); + } + } + } + +} diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorage.java b/src/main/java/net/minecraft/world/level/storage/LevelStorage.java new file mode 100644 index 0000000..b811550 --- /dev/null +++ b/src/main/java/net/minecraft/world/level/storage/LevelStorage.java @@ -0,0 +1,12 @@ +package net.minecraft.world.level.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +public interface LevelStorage { + public static final String NETHER_FOLDER = "DIM-1"; + public static final String ENDER_FOLDER = "DIM1"; +} diff --git a/src/main/java/net/minecraft/world/level/storage/ProgressListener.java b/src/main/java/net/minecraft/world/level/storage/ProgressListener.java new file mode 100644 index 0000000..b1b418d --- /dev/null +++ b/src/main/java/net/minecraft/world/level/storage/ProgressListener.java @@ -0,0 +1,18 @@ +package net.minecraft.world.level.storage; + +/** + * Copyright Mojang AB. + * + * Don't do evil. + */ + +public interface ProgressListener { + public void progressStartNoAbort(String string); + + public void progressStart(String string); + + public void progressStage(String string); + + public void progressStagePercentage(int i); + +}