diff --git a/Spigot-Server-Patches/0321-Provide-option-to-use-a-versioned-world-folder-for-t.patch b/Spigot-Server-Patches/0321-Provide-option-to-use-a-versioned-world-folder-for-t.patch new file mode 100644 index 00000000000..5f27321bfad --- /dev/null +++ b/Spigot-Server-Patches/0321-Provide-option-to-use-a-versioned-world-folder-for-t.patch @@ -0,0 +1,231 @@ +From b33d52f15d20e9c6ba4457eb799b67e32792e595 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 29 Jul 2018 15:48:50 -0400 +Subject: [PATCH] Provide option to use a versioned world folder for testing + +This should not ever be used in production!! + +This setting is intended for testing so you can try out converting your world +without actually modifying the world files. + +This will add some additional overhead to your world, but you're +just testing anyways so that's not a big deal :) + +Will store in a folder named after the current version. + +PlayerData and Data folders are copied on server start, so there +may be some delay there, but region files are only copied on demand. + +This is highly experiemental so backup your world before relying on this to not modify it + +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 5a17ce3d22..f7ece47ed9 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -13,6 +13,7 @@ import java.util.List; + import java.util.Map; + import java.util.concurrent.TimeUnit; + import java.util.logging.Level; ++import java.util.logging.Logger; + import java.util.regex.Pattern; + + import com.google.common.collect.Lists; +@@ -303,4 +304,27 @@ public class PaperConfig { + Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs"); + } + } ++ ++ public static boolean useVersionedWorld = false; ++ private static void useVersionedWorld() { ++ useVersionedWorld = getBoolean("settings.use-versioned-world", false); ++ if (useVersionedWorld) { ++ Logger logger = Bukkit.getLogger(); ++ String ver = MinecraftServer.getServer().getVersion(); ++ logger.log(Level.INFO, "******************************************************"); ++ logger.log(Level.INFO, "*** Using a versioned world folder. Your world will be saved"); ++ logger.log(Level.INFO, "*** to into the " + ver + " folder, but copied from your current world."); ++ logger.log(Level.INFO, "*** "); ++ logger.log(Level.INFO, "*** This setting should not be used in your real world!!!"); ++ logger.log(Level.INFO, "*** If you want to retain the new world, you need to move "); ++ logger.log(Level.INFO, "*** the folders out of the " + ver + " folder and overwrite existing"); ++ logger.log(Level.INFO, "*** "); ++ logger.log(Level.INFO, "*** Deleting the " + ver + " folder will cause it to recreate again"); ++ logger.log(Level.INFO, "*** from your unversioned world files."); ++ logger.log(Level.INFO, "*** "); ++ logger.log(Level.INFO, "*** You should backup your original world files incase something goes"); ++ logger.log(Level.INFO, "*** wrong with this system! This is not a backup system."); ++ logger.log(Level.INFO, "******************************************************"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index bd52bf6561..54840851ba 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -55,8 +55,56 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { + private PersistentStructureLegacy e; + // private boolean f; // CraftBukkit + private static final double SAVE_QUEUE_TARGET_SIZE = 625; // Spigot ++ // Paper start - support saving to an alternate directory ++ private final File templateWorld; ++ private final File actualWorld; ++ private final boolean useAltWorld; ++ ++ private boolean chunkExists0(int x, int z) { ++ copyIfNeeded(x, z); ++ return RegionFileCache.chunkExists(this.actualWorld, x, z); ++ } ++ private synchronized void copyIfNeeded(int x, int z) { ++ if (!useAltWorld) { ++ return; ++ } ++ if (RegionFileCache.hasRegionFile(this.actualWorld, x, z)) { ++ return; ++ } ++ File actual = RegionFileCache.getRegionFileName(this.actualWorld, x, z); ++ File template = RegionFileCache.getRegionFileName(this.templateWorld, x, z); ++ if (!actual.exists() && template.exists()) { ++ try { ++ //a.info("Copying" + template + " to " + actual); ++ java.nio.file.Files.copy(template.toPath(), actual.toPath(), java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); ++ } catch (IOException e1) { ++ LogManager.getLogger().error("Error copying " + template + " to " + actual, e1); ++ MinecraftServer.getServer().safeShutdown(); ++ org.spigotmc.SneakyThrow.sneaky(e1); ++ } ++ } + ++ } + public ChunkRegionLoader(File file, DataFixer datafixer) { ++ // Paper ++ this.actualWorld = file; ++ if (com.destroystokyo.paper.PaperConfig.useVersionedWorld) { ++ this.useAltWorld = true; ++ String name = file.getName(); ++ File container = file.getParentFile().getParentFile(); ++ if (name.equals("DIM-1") || name.equals("DIM1")) { ++ container = container.getParentFile(); ++ } ++ this.templateWorld = new File(container, name); ++ File region = new File(file, "region"); ++ if (!region.exists()) { ++ region.mkdirs(); ++ } ++ } else { ++ this.useAltWorld = false; ++ this.templateWorld = file; ++ } ++ // Paper end + this.c = file; + this.d = datafixer; + } +@@ -77,7 +125,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { + } + } + +- if (RegionFileCache.chunkExists(this.c, x, z)) { ++ if (chunkExists0(x, z)) { // Paper + NBTTagCompound nbt = RegionFileCache.read(this.c, x, z); + if (nbt != null) { + NBTTagCompound level = nbt.getCompound("Level"); +@@ -97,6 +145,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { + + @Nullable + private NBTTagCompound a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection, int i, int j, @Nullable GeneratorAccess generatoraccess) throws IOException { ++ copyIfNeeded(i, j); // Paper + NBTTagCompound nbttagcompound = RegionFileCache.read(this.c, i, j); + + if (nbttagcompound == null) { +@@ -205,7 +254,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { + ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j); + Supplier nbttagcompound = this.b.get(chunkcoordintpair); // Spigot + +- return nbttagcompound != null ? true : RegionFileCache.chunkExists(this.c, i, j); ++ return nbttagcompound != null ? true : chunkExists0(i, j); // Paper + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java +index 609d6c3550..f1fa7c7177 100644 +--- a/src/main/java/net/minecraft/server/RegionFileCache.java ++++ b/src/main/java/net/minecraft/server/RegionFileCache.java +@@ -74,6 +74,13 @@ public class RegionFileCache { + itr.remove(); + } + } ++ public static synchronized File getRegionFileName(File file, int i, int j) { ++ File file1 = new File(file, "region"); ++ return new File(file1, "r." + (i >> 5) + "." + (j >> 5) + ".mca"); ++ } ++ public static synchronized boolean hasRegionFile(File file, int i, int j) { ++ return RegionFileCache.a.containsKey(getRegionFileName(file, i, j)); ++ } + // Paper End + + public static synchronized void a() { +diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java +index 9a243010d0..3fabbe94f9 100644 +--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java ++++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java +@@ -33,6 +33,58 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData { + + public WorldNBTStorage(File file, String s, @Nullable MinecraftServer minecraftserver, DataFixer datafixer) { + this.a = datafixer; ++ // Paper start ++ if (com.destroystokyo.paper.PaperConfig.useVersionedWorld) { ++ File origBaseDir = new File(file, s); ++ final String currentVersion = MinecraftServer.getServer().getVersion(); ++ file = new File(file, currentVersion); ++ File baseDir = new File(file, s); ++ ++ if (!baseDir.exists() && origBaseDir.exists() && !baseDir.mkdirs()) { ++ LogManager.getLogger().error("Could not create world directory for " + file); ++ System.exit(1); ++ } ++ ++ try { ++ boolean printedHeader = false; ++ String[] dirs = {"advancements", "data", "datapacks", "playerdata", "stats"}; ++ for (String dir : dirs) { ++ File origPlayerData = new File(origBaseDir, dir); ++ File targetPlayerData = new File(baseDir, dir); ++ if (origPlayerData.exists() && !targetPlayerData.exists()) { ++ if (!printedHeader) { ++ LogManager.getLogger().info("**** VERSIONED WORLD - Copying files"); ++ printedHeader = true; ++ } ++ LogManager.getLogger().info("- Copying: " + dir); ++ org.apache.commons.io.FileUtils.copyDirectory(origPlayerData, targetPlayerData); ++ } ++ } ++ ++ String[] files = {"level.dat", "level.dat_old", "session.lock", "uid.dat"}; ++ for (String fileName : files) { ++ File origPlayerData = new File(origBaseDir, fileName); ++ File targetPlayerData = new File(baseDir, fileName); ++ if (origPlayerData.exists() && !targetPlayerData.exists()) { ++ if (!printedHeader) { ++ LogManager.getLogger().info("- Copying files"); ++ printedHeader = true; ++ } ++ LogManager.getLogger().info("- Copying: " + fileName); ++ org.apache.commons.io.FileUtils.copyFile(origPlayerData, targetPlayerData); ++ ++ } ++ } ++ if (printedHeader) { ++ LogManager.getLogger().info("**** VERSIONED WORLD - Copying DONE"); ++ } ++ } catch (IOException e) { ++ LogManager.getLogger().error("Error copying versioned world data for " + origBaseDir + " to " + baseDir, e); ++ org.spigotmc.SneakyThrow.sneaky(e); ++ } ++ ++ } ++ // Paper end + this.baseDir = new File(file, s); + this.baseDir.mkdirs(); + this.playerDir = new File(this.baseDir, "playerdata"); +-- +2.18.0 +