From 0d1e32efcb83622f9c295f556c1141f605aaa222 Mon Sep 17 00:00:00 2001
From: MattBDev <4009945+MattBDev@users.noreply.github.com>
Date: Thu, 2 Jan 2020 16:30:44 -0500
Subject: [PATCH] Upstream and debugging changes.

---
 .../cli/schematic/ClipboardWorld.java         |  18 +--
 .../src/main/java/com/boydti/fawe/Fawe.java   |   1 -
 .../com/boydti/fawe/beta/IQueueChunk.java     |   2 +-
 .../plotquared/PlotSquaredFeature.java        |   7 +-
 .../java/com/boydti/fawe/util/WEManager.java  |   4 +
 .../worldedit/function/factory/Deform.java    |  20 +--
 .../function/operation/Operation.java         |  12 +-
 .../com/sk89q/worldedit/util/io/Closer.java   |  21 ++++
 .../util/translation/TranslationManager.java  |  12 +-
 .../worldedit/world/chunk/AnvilChunk.java     |   6 +-
 .../worldedit/world/chunk/AnvilChunk13.java   |   2 +-
 .../sk89q/worldedit/world/chunk/OldChunk.java |   7 +-
 .../worldedit/world/storage/ChunkStore.java   |  56 +--------
 .../world/storage/ChunkStoreHelper.java       | 117 ++++++++++++++++++
 .../world/storage/LegacyChunkStore.java       |  18 +--
 .../src/main/resources/lang/strings.json      |  10 +-
 16 files changed, 196 insertions(+), 117 deletions(-)
 create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStoreHelper.java

diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java
index 5e1a26b96..69d1cb1c9 100644
--- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java
+++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java
@@ -108,11 +108,6 @@ public class ClipboardWorld extends AbstractWorld implements Clipboard, CLIWorld
         return false;
     }
 
-    @Override
-    public List<? extends Entity> getEntities(Region region) {
-        return clipboard.getEntities(region);
-    }
-
     @Override
     public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position)
             throws MaxChangedBlocksException {
@@ -120,13 +115,18 @@ public class ClipboardWorld extends AbstractWorld implements Clipboard, CLIWorld
     }
 
     @Override
-    public List<? extends Entity> getEntities() {
-        return clipboard.getEntities();
+    public BlockVector3 getSpawnPosition() {
+        return clipboard.getOrigin();
     }
 
     @Override
-    public BlockVector3 getSpawnPosition() {
-        return clipboard.getOrigin();
+    public List<? extends Entity> getEntities(Region region) {
+        return clipboard.getEntities(region);
+    }
+
+    @Override
+    public List<? extends Entity> getEntities() {
+        return clipboard.getEntities();
     }
 
     @Nullable
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
index 8e8ba17c6..492c2362a 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java
@@ -1,7 +1,6 @@
 package com.boydti.fawe;
 
 import com.boydti.fawe.beta.implementation.queue.QueueHandler;
-import com.boydti.fawe.config.Caption;
 import com.boydti.fawe.config.Settings;
 import com.boydti.fawe.object.brush.visualization.VisualQueue;
 import com.boydti.fawe.regions.general.integrations.plotquared.PlotSquaredFeature;
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IQueueChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IQueueChunk.java
index f60556029..c512edd9e 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IQueueChunk.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IQueueChunk.java
@@ -10,7 +10,7 @@ public interface IQueueChunk<T extends Future<T>> extends IChunk, Callable<T> {
      * @return
      */
     @Override
-    default IQueueChunk reset() {
+    default IQueueChunk<T> reset() {
         init(null, getX(), getZ());
         return this;
     }
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/regions/general/integrations/plotquared/PlotSquaredFeature.java b/worldedit-core/src/main/java/com/boydti/fawe/regions/general/integrations/plotquared/PlotSquaredFeature.java
index 0d3f11a0b..c0d24887c 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/regions/general/integrations/plotquared/PlotSquaredFeature.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/regions/general/integrations/plotquared/PlotSquaredFeature.java
@@ -97,7 +97,12 @@ public class PlotSquaredFeature extends FaweMaskManager {
             return false;
         }
         UUID uid = player.getUniqueId();
-        return !Flags.NO_WORLDEDIT.isTrue(plot) && ((plot.isOwner(uid) || (type == MaskType.MEMBER && (plot.getTrusted().contains(uid) || plot.getTrusted().contains(DBFunc.EVERYONE)  || ((plot.getMembers().contains(uid) || plot.getMembers().contains(DBFunc.EVERYONE)) && player.hasPermission("fawe.plotsquared.member"))))) || player.hasPermission("fawe.plotsquared.admin"));
+        return !Flags.NO_WORLDEDIT.isTrue(plot) && (plot.isOwner(uid)
+            || type == MaskType.MEMBER && (plot.getTrusted().contains(uid) || plot.getTrusted()
+            .contains(DBFunc.EVERYONE)
+            || (plot.getMembers().contains(uid) || plot.getMembers().contains(DBFunc.EVERYONE))
+            && player.hasPermission("fawe.plotsquared.member")) || player
+            .hasPermission("fawe.plotsquared.admin"));
     }
 
     @Override
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java b/worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java
index 6183518f3..82f01dcfc 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/util/WEManager.java
@@ -13,6 +13,7 @@ import com.sk89q.worldedit.extent.Extent;
 import com.sk89q.worldedit.math.BlockVector3;
 import com.sk89q.worldedit.regions.Region;
 import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.util.formatting.text.TextComponent;
 import java.lang.reflect.Field;
 import java.util.ArrayDeque;
 import java.util.HashSet;
@@ -92,6 +93,7 @@ public class WEManager {
                                 backupRegions.add(region);
                             }
                         } else {
+                            player.printDebug(TextComponent.of("Invalid Mask"));
                             removed = true;
                             iterator.remove();
                         }
@@ -115,6 +117,8 @@ public class WEManager {
                 } catch (Throwable e) {
                     e.printStackTrace();
                 }
+            } else {
+                player.printError(TextComponent.of("Missing permission " +  "fawe." + manager.getKey()));
             }
         }
         regions.addAll(backupRegions);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java
index 9bb608bba..aab9adb8d 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/factory/Deform.java
@@ -19,6 +19,9 @@
 
 package com.sk89q.worldedit.function.factory;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
+
 import com.google.common.collect.ImmutableList;
 import com.sk89q.worldedit.EditSession;
 import com.sk89q.worldedit.LocalSession;
@@ -39,9 +42,6 @@ import com.sk89q.worldedit.util.formatting.text.TextComponent;
 import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
 import com.sk89q.worldedit.util.formatting.text.format.TextColor;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
-
 public class Deform implements Contextual<Operation> {
 
     private Extent destination;
@@ -157,12 +157,6 @@ public class Deform implements Contextual<Operation> {
                 session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout());
     }
 
-    public enum Mode {
-        RAW_COORD,
-        OFFSET,
-        UNIT_CUBE
-    }
-
     private static final class DeformOperation implements Operation {
         private final Extent destination;
         private final Region region;
@@ -199,9 +193,15 @@ public class Deform implements Contextual<Operation> {
         @Override
         public Iterable<Component> getStatusMessages() {
             return ImmutableList.of(TranslatableComponent.of("worldedit.operation.deform.expression",
-                    TextComponent.of(expression).color(TextColor.GRAY)));
+                    TextComponent.of(expression).color(TextColor.LIGHT_PURPLE)));
         }
 
     }
 
+    public enum Mode {
+        RAW_COORD,
+        OFFSET,
+        UNIT_CUBE
+    }
+
 }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/Operation.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/Operation.java
index c21f2a6f7..85b0631f3 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/Operation.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/Operation.java
@@ -38,11 +38,6 @@ import java.util.stream.Collectors;
  */
 public interface Operation {
 
-    /**
-     * This is an internal field, and should not be touched.
-     */
-    Set<String> warnedDeprecatedClasses = new HashSet<>();
-
     /**
      * Complete the next step. If this method returns true, then the method may
      * be called again in the future, or possibly never. If this method
@@ -74,6 +69,11 @@ public interface Operation {
     default void addStatusMessages(List<String> messages) {
     }
 
+    /**
+     * This is an internal field, and should not be touched.
+     */
+    Set<String> warnedDeprecatedClasses = new HashSet<>();
+
     /**
      * Gets an iterable of messages that describe the current status of the
      * operation.
@@ -87,7 +87,7 @@ public interface Operation {
         if (oldMessages.size() > 0) {
             String className = getClass().getName();
             if (!warnedDeprecatedClasses.contains(className)) {
-                WorldEdit.logger.warn("An operation is using the old status message API. This will be removed in further versions. Class: " + className);
+                WorldEdit.logger.warn("An operation is using the old status message API. This will be removed in WorldEdit 8. Class: " + className);
                 warnedDeprecatedClasses.add(className);
             }
         }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/io/Closer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/io/Closer.java
index 1b7aceda3..85e3f10a5 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/io/Closer.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/io/Closer.java
@@ -86,6 +86,27 @@ public final class Closer implements Closeable {
         return zipFile;
     }
 
+    /**
+     * Call {@link #rethrow(Throwable)} with the given exception, but before throwing the exception,
+     * also close this Closer. Exceptions from closing are added to {@code t} as suppressed
+     * exceptions.
+     *
+     * @param t the throwable that should be re-thrown
+     * @throws IOException if {@code t} is an IOException, or one occurs
+     */
+    public RuntimeException rethrowAndClose(Throwable t) throws IOException {
+        // bit of a hack here
+        try {
+            throw rethrow(t);
+        } finally {
+            try {
+                close();
+            } catch (Throwable closeThrown) {
+                t.addSuppressed(closeThrown);
+            }
+        }
+    }
+
     /**
      * Stores the given throwable and rethrows it. It will be rethrown as is if it is an
      * {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/translation/TranslationManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/translation/TranslationManager.java
index 46a582736..79ad5d6b2 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/translation/TranslationManager.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/translation/TranslationManager.java
@@ -19,6 +19,9 @@
 
 package com.sk89q.worldedit.util.translation;
 
+import static java.util.stream.Collectors.toMap;
+
+import com.google.common.collect.Maps;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
@@ -26,7 +29,6 @@ import com.sk89q.worldedit.WorldEdit;
 import com.sk89q.worldedit.util.formatting.text.Component;
 import com.sk89q.worldedit.util.formatting.text.renderer.FriendlyComponentRenderer;
 import com.sk89q.worldedit.util.io.ResourceLoader;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -76,8 +78,10 @@ public class TranslationManager {
     }
 
     private Map<String, String> filterTranslations(Map<String, String> translations) {
-        translations.entrySet().removeIf(entry -> entry.getValue().isEmpty());
-        return translations;
+        return translations.entrySet().stream()
+            .filter(e -> !e.getValue().isEmpty())
+            .map(e -> Maps.immutableEntry(e.getKey(), e.getValue().replace("'", "''")))
+            .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
     }
 
     private Map<String, String> parseTranslationFile(InputStream inputStream) {
@@ -156,4 +160,4 @@ public class TranslationManager {
     public Locale getDefaultLocale() {
         return defaultLocale;
     }
-}
\ No newline at end of file
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java
index 3a2336754..f42997187 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk.java
@@ -29,7 +29,6 @@ import com.sk89q.jnbt.Tag;
 import com.sk89q.worldedit.WorldEdit;
 import com.sk89q.worldedit.math.BlockVector3;
 import com.sk89q.worldedit.world.DataException;
-import com.sk89q.worldedit.world.World;
 import com.sk89q.worldedit.world.block.BaseBlock;
 import com.sk89q.worldedit.world.block.BlockState;
 import com.sk89q.worldedit.world.block.BlockTypes;
@@ -55,11 +54,10 @@ public class AnvilChunk implements Chunk {
     /**
      * Construct the chunk with a compound tag.
      *
-     * @param world the world to construct the chunk for
      * @param tag the tag to read
      * @throws DataException on a data error
      */
-    public AnvilChunk(World world, CompoundTag tag) throws DataException {
+    public AnvilChunk(CompoundTag tag) throws DataException {
         rootTag = tag;
 
         rootX = NBTUtils.getChildTag(rootTag.getValue(), "xPos", IntTag.class).getValue();
@@ -261,13 +259,11 @@ public class AnvilChunk implements Chunk {
             WorldEdit.logger.warn("Unknown legacy block " + id + ":" + data + " found when loading legacy anvil chunk.");
             return BlockTypes.AIR.getDefaultState().toBaseBlock();
         }
-        if (state.getMaterial().hasContainer()) {
             CompoundTag tileEntity = getBlockTileEntity(position);
 
             if (tileEntity != null) {
                 return state.toBaseBlock(tileEntity);
             }
-        }
 
         return state.toBaseBlock();
     }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java
index daf291e8c..e0717c0e2 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java
@@ -226,9 +226,9 @@ public class AnvilChunk13 implements Chunk {
         BlockState[] sectionBlocks = blocks[section];
         BlockState state = sectionBlocks != null ? sectionBlocks[(yIndex << 8) | (z << 4) | x] : BlockTypes.AIR.getDefaultState();
 
-        if (state.getMaterial().hasContainer()) {
             CompoundTag tileEntity = getBlockTileEntity(position);
 
+        if (tileEntity != null) {
             return state.toBaseBlock(tileEntity);
         }
 
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java
index 3cc7e002d..540197cee 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/OldChunk.java
@@ -28,7 +28,6 @@ import com.sk89q.jnbt.Tag;
 import com.sk89q.worldedit.WorldEdit;
 import com.sk89q.worldedit.math.BlockVector3;
 import com.sk89q.worldedit.world.DataException;
-import com.sk89q.worldedit.world.World;
 import com.sk89q.worldedit.world.block.BaseBlock;
 import com.sk89q.worldedit.world.block.BlockState;
 import com.sk89q.worldedit.world.block.BlockTypes;
@@ -55,11 +54,10 @@ public class OldChunk implements Chunk {
     /**
      * Construct the chunk with a compound tag.
      *
-     * @param world the world
      * @param tag the tag
      * @throws DataException
      */
-    public OldChunk(World world, CompoundTag tag) throws DataException {
+    public OldChunk(CompoundTag tag) throws DataException {
         rootTag = tag;
 
         blocks = NBTUtils.getChildTag(rootTag.getValue(), "Blocks", ByteArrayTag.class).getValue();
@@ -185,13 +183,12 @@ public class OldChunk implements Chunk {
             WorldEdit.logger.warn("Unknown legacy block " + id + ":" + dataVal + " found when loading legacy anvil chunk.");
             return BlockTypes.AIR.getDefaultState().toBaseBlock();
         }
-        if (state.getBlockType().getMaterial().hasContainer()) {
+
             CompoundTag tileEntity = getBlockTileEntity(position);
 
             if (tileEntity != null) {
                 return state.toBaseBlock(tileEntity);
             }
-        }
 
         return state.toBaseBlock();
     }
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java
index 1b89c8b09..e975a200f 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStore.java
@@ -20,34 +20,19 @@
 package com.sk89q.worldedit.world.storage;
 
 import com.sk89q.jnbt.CompoundTag;
-import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.WorldEdit;
-import com.sk89q.worldedit.extension.platform.Capability;
-import com.sk89q.worldedit.extension.platform.Platform;
 import com.sk89q.worldedit.math.BlockVector2;
 import com.sk89q.worldedit.math.BlockVector3;
 import com.sk89q.worldedit.world.DataException;
-import com.sk89q.worldedit.world.DataFixer;
 import com.sk89q.worldedit.world.World;
-import com.sk89q.worldedit.world.chunk.AnvilChunk;
-import com.sk89q.worldedit.world.chunk.AnvilChunk13;
 import com.sk89q.worldedit.world.chunk.Chunk;
-import com.sk89q.worldedit.world.chunk.OldChunk;
-
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.Map;
 
 /**
  * Represents chunk storage mechanisms.
  */
 public abstract class ChunkStore implements Closeable {
 
-    /**
-     * The DataVersion for Minecraft 1.13
-     */
-    private static final int DATA_VERSION_MC_1_13 = 1519;
-
     /**
      * {@code >>} - to chunk
      * {@code <<} - from chunk
@@ -85,46 +70,7 @@ public abstract class ChunkStore implements Closeable {
      */
     public Chunk getChunk(BlockVector2 position, World world) throws DataException, IOException {
         CompoundTag rootTag = getChunkTag(position, world);
-
-        Map<String, Tag> children = rootTag.getValue();
-        CompoundTag tag = null;
-
-        // Find Level tag
-        for (Map.Entry<String, Tag> entry : children.entrySet()) {
-            if (entry.getKey().equals("Level")) {
-                if (entry.getValue() instanceof CompoundTag) {
-                    tag = (CompoundTag) entry.getValue();
-                    break;
-                } else {
-                    throw new ChunkStoreException("CompoundTag expected for 'Level'; got " + entry.getValue().getClass().getName());
-                }
-            }
-        }
-
-        if (tag == null) {
-            throw new ChunkStoreException("Missing root 'Level' tag");
-        }
-
-        int dataVersion = rootTag.getInt("DataVersion");
-        if (dataVersion == 0) dataVersion = -1;
-        final Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING);
-        final int currentDataVersion = platform.getDataVersion();
-        if (tag.getValue().containsKey("Sections") &&  dataVersion < currentDataVersion) { // only fix up MCA format, DFU doesn't support MCR chunks
-            final DataFixer dataFixer = platform.getDataFixer();
-            if (dataFixer != null) {
-                return new AnvilChunk13((CompoundTag) dataFixer.fixUp(DataFixer.FixTypes.CHUNK, rootTag, dataVersion).getValue().get("Level"));
-            }
-        }
-        if (dataVersion >= DATA_VERSION_MC_1_13) {
-            return new AnvilChunk13(tag);
-        }
-
-        Map<String, Tag> tags = tag.getValue();
-        if (tags.containsKey("Sections")) {
-            return new AnvilChunk(world, tag);
-        }
-
-        return new OldChunk(world, tag);
+        return ChunkStoreHelper.getChunk(rootTag);
     }
 
     @Override
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStoreHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStoreHelper.java
new file mode 100644
index 000000000..083f868b2
--- /dev/null
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/ChunkStoreHelper.java
@@ -0,0 +1,117 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q <http://www.sk89q.com>
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.sk89q.worldedit.world.storage;
+
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.NBTInputStream;
+import com.sk89q.jnbt.Tag;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.extension.platform.Capability;
+import com.sk89q.worldedit.extension.platform.Platform;
+import com.sk89q.worldedit.world.DataException;
+import com.sk89q.worldedit.world.DataFixer;
+import com.sk89q.worldedit.world.chunk.AnvilChunk;
+import com.sk89q.worldedit.world.chunk.AnvilChunk13;
+import com.sk89q.worldedit.world.chunk.Chunk;
+import com.sk89q.worldedit.world.chunk.OldChunk;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+public class ChunkStoreHelper {
+
+    @FunctionalInterface
+    public interface ChunkDataInputSupplier {
+
+        InputStream openInputStream() throws DataException, IOException;
+
+    }
+
+    public static CompoundTag readCompoundTag(ChunkDataInputSupplier input) throws DataException, IOException {
+        try (InputStream stream = input.openInputStream();
+            NBTInputStream nbt = new NBTInputStream(stream)) {
+            Tag tag = nbt.readNamedTag().getTag();
+            if (!(tag instanceof CompoundTag)) {
+                throw new ChunkStoreException("CompoundTag expected for chunk; got "
+                    + tag.getClass().getName());
+            }
+
+            return (CompoundTag) tag;
+        }
+    }
+
+    /**
+     * The DataVersion for Minecraft 1.13
+     */
+    private static final int DATA_VERSION_MC_1_13 = 1519;
+
+    /**
+     * Convert a chunk NBT tag into a {@link Chunk} implementation.
+     *
+     * @param rootTag the root tag of the chunk
+     * @return a Chunk implementation
+     * @throws DataException if the rootTag is not valid chunk data
+     */
+    public static Chunk getChunk(CompoundTag rootTag) throws DataException {
+        Map<String, Tag> children = rootTag.getValue();
+        CompoundTag tag = null;
+
+        // Find Level tag
+        for (Map.Entry<String, Tag> entry : children.entrySet()) {
+            if (entry.getKey().equals("Level")) {
+                if (entry.getValue() instanceof CompoundTag) {
+                    tag = (CompoundTag) entry.getValue();
+                    break;
+                } else {
+                    throw new ChunkStoreException("CompoundTag expected for 'Level'; got " + entry.getValue().getClass().getName());
+                }
+            }
+        }
+
+        if (tag == null) {
+            throw new ChunkStoreException("Missing root 'Level' tag");
+        }
+
+        int dataVersion = rootTag.getInt("DataVersion");
+        if (dataVersion == 0) dataVersion = -1;
+        final Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING);
+        final int currentDataVersion = platform.getDataVersion();
+        if (tag.getValue().containsKey("Sections") &&  dataVersion < currentDataVersion) { // only fix up MCA format, DFU doesn't support MCR chunks
+            final DataFixer dataFixer = platform.getDataFixer();
+            if (dataFixer != null) {
+                return new AnvilChunk13((CompoundTag) dataFixer.fixUp(DataFixer.FixTypes.CHUNK, rootTag, dataVersion).getValue().get("Level"));
+            }
+        }
+        if (dataVersion >= DATA_VERSION_MC_1_13) {
+            return new AnvilChunk13(tag);
+        }
+
+        Map<String, Tag> tags = tag.getValue();
+        if (tags.containsKey("Sections")) {
+            return new AnvilChunk(tag);
+        }
+
+        return new OldChunk(tag);
+    }
+
+    private ChunkStoreHelper() {
+    }
+}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/LegacyChunkStore.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/LegacyChunkStore.java
index b3b5728fe..af302ff62 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/LegacyChunkStore.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/LegacyChunkStore.java
@@ -20,12 +20,9 @@
 package com.sk89q.worldedit.world.storage;
 
 import com.sk89q.jnbt.CompoundTag;
-import com.sk89q.jnbt.NBTInputStream;
-import com.sk89q.jnbt.Tag;
 import com.sk89q.worldedit.math.BlockVector2;
 import com.sk89q.worldedit.world.DataException;
 import com.sk89q.worldedit.world.World;
-
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -78,18 +75,9 @@ public abstract class LegacyChunkStore extends ChunkStore {
         String filename = "c." + Integer.toString(x, 36)
                 + "." + Integer.toString(z, 36) + ".dat";
 
-        InputStream stream = getInputStream(folder1, folder2, filename);
-        Tag tag;
-
-        try (NBTInputStream nbt = new NBTInputStream(new GZIPInputStream(stream))) {
-            tag = nbt.readNamedTag().getTag();
-            if (!(tag instanceof CompoundTag)) {
-                throw new ChunkStoreException("CompoundTag expected for chunk; got "
-                        + tag.getClass().getName());
-            }
-
-            return (CompoundTag) tag;
-        }
+        return ChunkStoreHelper.readCompoundTag(() ->
+            new GZIPInputStream(getInputStream(folder1, folder2, filename))
+        );
     }
 
     private static int divisorMod(int a, int n) {
diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json
index deb5d268a..85a9d8dbf 100644
--- a/worldedit-core/src/main/resources/lang/strings.json
+++ b/worldedit-core/src/main/resources/lang/strings.json
@@ -6,11 +6,11 @@
 
 	"fawe.worldedit.history.find.element": "&8 - &2{0}: {1} &7ago &3{2}m &6{3} &c/{4}",
 	"fawe.worldedit.history.find.hover": "{0} blocks changed, click for more info",
-	
+
 	"fawe.info.lighting.propagate.selection": "Lighting has been propogated in {0} chunks. (Note: To remove light use //removelight)",
 	"fawe.info.updated.lighting.selection": "Lighting has been updated in {0} chunks. (It may take a second for the packets to send)",
 	"fawe.info.set.region": "Selection set to your current allowed region",
-	
+
 	"fawe.info.worldedit.command.limit": "Please wait until your current action completes",
 	"fawe.info.worldedit.delayed": "Please wait while we process your FAWE action...",
 	"fawe.info.worldedit.run": "Apologies for the delay. Now executing: {0}",
@@ -331,7 +331,7 @@
 	"fawe.tips.tip.regen.1": "Tip: Use a seed with /regen [biome] [seed]",
 	"fawe.tips.tip.biome.pattern": "Tip: The #biome[forest] pattern can be used in any command",
 	"fawe.tips.tip.biome.mask": "Tip: Restrict to a biome with the `$jungle` mask",
-	
+
     "worldedit.expand.description.vert": "Vertically expand the selection to world limits.",
     "worldedit.expand.expanded": "Region expanded {0} blocks",
     "worldedit.expand.expanded.vert": "Region expanded {0} blocks (top-to-bottom).",
@@ -408,6 +408,7 @@
     "worldedit.restore.failed": "Failed to load snapshot: {0}",
     "worldedit.restore.loaded": "Snapshot '{0}' loaded; now restoring...",
     "worldedit.restore.restored": "Restored; {0} missing chunks and {1} other errors.",
+    "worldedit.restore.none-for-specific-world": "No snapshots were found for world '{0}'.",
     "worldedit.restore.none-for-world": "No snapshots were found for this world.",
     "worldedit.restore.none-found": "No snapshots were found.",
     "worldedit.restore.none-found-console": "No snapshots were found. See console for details.",
@@ -502,7 +503,7 @@
 
     "worldedit.paste.pasted": "The clipboard has been pasted at {0}",
     "worldedit.paste.selected": "Selected clipboard paste region.",
-	
+
     "worldedit.rotate.no-interpolation": "Note: Interpolation is not yet supported, so angles that are multiples of 90 is recommended.",
     "worldedit.rotate.rotated": "The clipboard copy has been rotated.",
     "worldedit.flip.flipped": "The clipboard copy has been flipped.",
@@ -517,6 +518,7 @@
     "worldedit.replace.replaced": "{0} blocks have been replaced.",
     "worldedit.stack.changed": "{0} blocks changed. Undo with //undo",
     "worldedit.regen.regenerated": "Region regenerated.",
+    "worldedit.regen.failed": "Unable to regenerate chunks. Check console for details.",
     "worldedit.walls.changed": "{0} blocks have been changed.",
     "worldedit.faces.changed": "{0} blocks have been changed.",
     "worldedit.overlay.overlaid": "{0} blocks have been overlaid.",