diff options
Diffstat (limited to 'patches/server/0992-Allow-Saving-of-Oversized-Chunks.patch')
-rw-r--r-- | patches/server/0992-Allow-Saving-of-Oversized-Chunks.patch | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/patches/server/0992-Allow-Saving-of-Oversized-Chunks.patch b/patches/server/0992-Allow-Saving-of-Oversized-Chunks.patch new file mode 100644 index 0000000000..9e1c0cc311 --- /dev/null +++ b/patches/server/0992-Allow-Saving-of-Oversized-Chunks.patch @@ -0,0 +1,213 @@ +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 + +Note 1.17 update: With 1.17, Entities are no longer stored in chunk slices, so this needs updating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +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 307196b2a58d4f8db3e6e3c3517a8004d4908b13..69d7a7c2dcee40552d229288c52ed7dd6ad0c2a8 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; + +@@ -49,6 +52,7 @@ public class RegionFile implements AutoCloseable { + @VisibleForTesting + protected final RegionBitmap usedSectors; + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper ++ public final Path regionFile; // Paper + + public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { + this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format +@@ -56,6 +60,8 @@ public class RegionFile implements AutoCloseable { + + public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { + this.header = ByteBuffer.allocateDirect(8192); ++ this.regionFile = file; // Paper ++ initOversizedState(); // Paper + this.usedSectors = new RegionBitmap(); + this.info = storageKey; + this.path = path; +@@ -453,6 +459,74 @@ public class RegionFile implements AutoCloseable { + } + + public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails ++ // 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.regionFile.getParent().resolve(this.regionFile.getFileName().toString().replaceAll("\\.mca$", "") + ".oversized.nbt"); ++ } ++ ++ private Path getOversizedFile(int x, int z) { ++ return this.regionFile.getParent().resolve(this.regionFile.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 3f6955be976064eb542b5c50a9d6d74457c1833c..3b5849f51ff56c6582c8bd451c5c3398827df6db 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 +@@ -123,6 +123,43 @@ public 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 +@@ -134,6 +171,12 @@ public class RegionFileStorage implements AutoCloseable { + try { // Paper + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); + ++ // Paper start ++ if (regionfile.isOversized(pos.x, pos.z)) { ++ printOversizedLog("Loading Oversized Chunk!", regionfile.regionFile, pos.x, pos.z); ++ return readOversizedChunk(regionfile, pos); ++ } ++ // Paper end + CompoundTag nbttagcompound; + label43: + { +@@ -225,6 +268,7 @@ public 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 + // Paper start - don't write garbage data to disk if writing serialization fails + dataoutputstream.close(); // Only write if successful + } catch (final RegionFileSizeException e) { |