aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch')
-rw-r--r--patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch202
1 files changed, 202 insertions, 0 deletions
diff --git a/patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch b/patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch
new file mode 100644
index 0000000000..0b77f00d70
--- /dev/null
+++ b/patches/server/1037-Allow-Saving-of-Oversized-Chunks.patch
@@ -0,0 +1,202 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Fri, 15 Feb 2019 01:08:19 -0500
+Subject: [PATCH] Allow Saving of Oversized Chunks
+
+The Minecraft World Region File format has a hard cap of 1MB per chunk.
+This is due to the fact that the header of the file format only allocates
+a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
+
+This limit can be reached fairly easily with books, resulting in the chunk being unable
+to save to the world. Worse off, is that nothing printed when this occured, and silently
+performed a chunk rollback on next load.
+
+This leads to security risk with duplication and is being actively exploited.
+
+This patch catches the too large scenario, falls back and moves any large Entity
+or Tile Entity into a new compound, and this compound is saved into a different file.
+
+On Chunk Load, we check for oversized status, and if so, we load the extra file and
+merge the Entities and Tile Entities from the oversized chunk back into the level to
+then be loaded as normal.
+
+Once a chunk is returned back to normal size, the oversized flag will clear, and no
+extra data file will exist.
+
+This fix maintains compatability with all existing Anvil Region Format tools as it
+does not alter the save format. They will just not know about the extra entities.
+
+This fix also maintains compatability if someone switches server jars to one without
+this fix, as the data will remain in the oversized file. Once the server returns
+to a jar with this fix, the data will be restored.
+
+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
+index 0028f4fd331b6f94be2a2b4d90e56dcdd881178d..863960ead8deaa0553be1c98e4fa09f07fcb8ef0 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+@@ -18,10 +18,13 @@ import java.nio.file.LinkOption;
+ import java.nio.file.Path;
+ import java.nio.file.StandardCopyOption;
+ import java.nio.file.StandardOpenOption;
++import java.util.zip.InflaterInputStream; // Paper
+ import javax.annotation.Nullable;
+ import net.minecraft.Util;
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.util.profiling.jfr.JvmProfiler;
++import net.minecraft.nbt.CompoundTag; // Paper
++import net.minecraft.nbt.NbtIo; // Paper
+ import net.minecraft.world.level.ChunkPos;
+ import org.slf4j.Logger;
+
+@@ -58,6 +61,7 @@ public class RegionFile implements AutoCloseable {
+ this.usedSectors = new RegionBitmap();
+ this.info = storageKey;
+ this.path = path;
++ initOversizedState(); // Paper
+ this.version = compressionFormat;
+ if (!Files.isDirectory(directory, new LinkOption[0])) {
+ throw new IllegalArgumentException("Expected directory, got " + String.valueOf(directory.toAbsolutePath()));
+@@ -442,6 +446,74 @@ public class RegionFile implements AutoCloseable {
+
+ }
+
++ // Paper start
++ private final byte[] oversized = new byte[1024];
++ private int oversizedCount;
++
++ private synchronized void initOversizedState() throws IOException {
++ Path metaFile = getOversizedMetaFile();
++ if (Files.exists(metaFile)) {
++ final byte[] read = java.nio.file.Files.readAllBytes(metaFile);
++ System.arraycopy(read, 0, oversized, 0, oversized.length);
++ for (byte temp : oversized) {
++ oversizedCount += temp;
++ }
++ }
++ }
++
++ private static int getChunkIndex(int x, int z) {
++ return (x & 31) + (z & 31) * 32;
++ }
++ synchronized boolean isOversized(int x, int z) {
++ return this.oversized[getChunkIndex(x, z)] == 1;
++ }
++ synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
++ final int offset = getChunkIndex(x, z);
++ boolean previous = this.oversized[offset] == 1;
++ this.oversized[offset] = (byte) (oversized ? 1 : 0);
++ if (!previous && oversized) {
++ oversizedCount++;
++ } else if (!oversized && previous) {
++ oversizedCount--;
++ }
++ if (previous && !oversized) {
++ Path oversizedFile = getOversizedFile(x, z);
++ if (Files.exists(oversizedFile)) {
++ Files.delete(oversizedFile);
++ }
++ }
++ if (oversizedCount > 0) {
++ if (previous != oversized) {
++ writeOversizedMeta();
++ }
++ } else if (previous) {
++ Path oversizedMetaFile = getOversizedMetaFile();
++ if (Files.exists(oversizedMetaFile)) {
++ Files.delete(oversizedMetaFile);
++ }
++ }
++ }
++
++ private void writeOversizedMeta() throws IOException {
++ java.nio.file.Files.write(getOversizedMetaFile(), oversized);
++ }
++
++ private Path getOversizedMetaFile() {
++ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + ".oversized.nbt");
++ }
++
++ private Path getOversizedFile(int x, int z) {
++ return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
++ }
++
++ synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
++ Path file = getOversizedFile(x, z);
++ try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) {
++ return NbtIo.read((java.io.DataInput) out);
++ }
++
++ }
++ // Paper end
+ private class ChunkBuffer extends ByteArrayOutputStream {
+
+ private final ChunkPos pos;
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+index 54803d561d98b0d3fd8aa2060a7a184a00dd9da6..e6abe35d6c43b7f76cf3da129ec9552e7b82453e 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -55,6 +55,43 @@ public final class RegionFileStorage implements AutoCloseable {
+ }
+ }
+
++ // Paper start
++ private static void printOversizedLog(String msg, Path file, int x, int z) {
++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.");
++ }
++
++ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
++ synchronized (regionfile) {
++ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
++ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
++ CompoundTag chunk = NbtIo.read((DataInput) datainputstream);
++ if (oversizedData == null) {
++ return chunk;
++ }
++ CompoundTag oversizedLevel = oversizedData.getCompound("Level");
++
++ mergeChunkList(chunk.getCompound("Level"), oversizedLevel, "Entities", "Entities");
++ mergeChunkList(chunk.getCompound("Level"), oversizedLevel, "TileEntities", "TileEntities");
++
++ return chunk;
++ } catch (Throwable throwable) {
++ throwable.printStackTrace();
++ throw throwable;
++ }
++ }
++ }
++
++ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key, String oversizedKey) {
++ net.minecraft.nbt.ListTag levelList = level.getList(key, net.minecraft.nbt.Tag.TAG_COMPOUND);
++ net.minecraft.nbt.ListTag oversizedList = oversizedLevel.getList(oversizedKey, net.minecraft.nbt.Tag.TAG_COMPOUND);
++
++ if (!oversizedList.isEmpty()) {
++ levelList.addAll(oversizedList);
++ level.put(key, levelList);
++ }
++ }
++ // Paper end
++
+ @Nullable
+ public CompoundTag read(ChunkPos pos) throws IOException {
+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
+@@ -65,6 +102,12 @@ public final class RegionFileStorage implements AutoCloseable {
+ // CraftBukkit end
+ DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
+
++ // Paper start
++ if (regionfile.isOversized(pos.x, pos.z)) {
++ printOversizedLog("Loading Oversized Chunk!", regionfile.getPath(), pos.x, pos.z);
++ return readOversizedChunk(regionfile, pos);
++ }
++ // Paper end
+ CompoundTag nbttagcompound;
+ label43:
+ {
+@@ -142,6 +185,7 @@ public final class RegionFileStorage implements AutoCloseable {
+
+ try {
+ NbtIo.write(nbt, (DataOutput) dataoutputstream);
++ regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
+ } catch (Throwable throwable) {
+ if (dataoutputstream != null) {
+ try {