--- a/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -3,15 +3,19 @@
 import com.google.common.collect.Maps;
 import com.mojang.logging.LogUtils;
 import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
 import com.mojang.serialization.Dynamic;
 import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
 import it.unimi.dsi.fastutil.longs.LongSet;
 import it.unimi.dsi.fastutil.shorts.ShortList;
+import it.unimi.dsi.fastutil.shorts.ShortListIterator;
 import java.util.Arrays;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Holder;
@@ -27,8 +31,11 @@
 import net.minecraft.nbt.NbtOps;
 import net.minecraft.nbt.NbtUtils;
 import net.minecraft.nbt.ShortTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerChunkCache;
 import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.ai.village.poi.PoiManager;
 import net.minecraft.world.level.ChunkPos;
@@ -41,7 +48,6 @@
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.chunk.CarvingMask;
 import net.minecraft.world.level.chunk.ChunkAccess;
-import net.minecraft.world.level.chunk.ChunkSource;
 import net.minecraft.world.level.chunk.DataLayer;
 import net.minecraft.world.level.chunk.ImposterProtoChunk;
 import net.minecraft.world.level.chunk.LevelChunk;
@@ -66,9 +72,20 @@
 import org.slf4j.Logger;
 
 public class ChunkSerializer {
-    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(
-        Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()
-    );
+    // Paper start - Attempt to recalculate regionfile header if it is corrupt
+    // TODO: Check on update
+    public static long getLastWorldSaveTime(CompoundTag chunkData) {
+        final int dataVersion = ChunkStorage.getVersion(chunkData);
+        if (dataVersion < 2842) { // Level tag is removed after this version
+            final CompoundTag levelData = chunkData.getCompound("Level");
+            return levelData.getLong("LastUpdate");
+        } else {
+            return chunkData.getLong("LastUpdate");
+        }
+    }
+    // Paper end - Attempt to recalculate regionfile header if it is corrupt
+
+    public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final String TAG_UPGRADE_DATA = "UpgradeData";
     private static final String BLOCK_TICKS_TAG = "block_ticks";
@@ -81,313 +98,466 @@
     public static final String BLOCK_LIGHT_TAG = "BlockLight";
     public static final String SKY_LIGHT_TAG = "SkyLight";
 
+    // Paper start - Do not let the server load chunks from newer versions
+    private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+    private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
+    // Paper end - Do not let the server load chunks from newer versions
+    public ChunkSerializer() {}
+
+    // Paper start - guard against serializing mismatching coordinates
+    // TODO Note: This needs to be re-checked each update
+    public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) {
+        final int dataVersion = ChunkStorage.getVersion(chunkData);
+        if (dataVersion < 2842) { // Level tag is removed after this version
+            final CompoundTag levelData = chunkData.getCompound("Level");
+            return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
+        } else {
+            return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
+        }
+    }
+    // Paper end - guard against serializing mismatching coordinates
+
     public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, RegionStorageInfo key, ChunkPos chunkPos, CompoundTag nbt) {
-        ChunkPos chunkPos2 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
-        if (!Objects.equals(chunkPos, chunkPos2)) {
-            LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", chunkPos, chunkPos, chunkPos2);
-            world.getServer().reportMisplacedChunk(chunkPos2, chunkPos, key);
+        // Paper start - Do not let the server load chunks from newer versions
+        if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
+            final int dataVersion = nbt.getInt("DataVersion");
+            if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
+                new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
+                System.exit(1);
+            }
         }
+        // Paper end - Do not let the server load chunks from newer versions
+        ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate
 
-        UpgradeData upgradeData = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
-        boolean bl = nbt.getBoolean("isLightOn");
-        ListTag listTag = nbt.getList("sections", 10);
+        if (!Objects.equals(chunkPos, chunkcoordintpair1)) {
+            ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1});
+            world.getServer().reportMisplacedChunk(chunkcoordintpair1, chunkPos, key);
+        }
+
+        UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY;
+        boolean flag = nbt.getBoolean("isLightOn");
+        ListTag nbttaglist = nbt.getList("sections", 10);
         int i = world.getSectionsCount();
-        LevelChunkSection[] levelChunkSections = new LevelChunkSection[i];
-        boolean bl2 = world.dimensionType().hasSkyLight();
-        ChunkSource chunkSource = world.getChunkSource();
-        LevelLightEngine levelLightEngine = chunkSource.getLightEngine();
-        Registry<Biome> registry = world.registryAccess().registryOrThrow(Registries.BIOME);
-        Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
-        boolean bl3 = false;
+        LevelChunkSection[] achunksection = new LevelChunkSection[i];
+        boolean flag1 = world.dimensionType().hasSkyLight();
+        ServerChunkCache chunkproviderserver = world.getChunkSource();
+        LevelLightEngine levellightengine = chunkproviderserver.getLightEngine();
+        Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registries.BIOME);
+        Codec<PalettedContainer<Holder<Biome>>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
+        boolean flag2 = false;
 
-        for (int j = 0; j < listTag.size(); j++) {
-            CompoundTag compoundTag = listTag.getCompound(j);
-            int k = compoundTag.getByte("Y");
-            int l = world.getSectionIndexFromSectionY(k);
-            if (l >= 0 && l < levelChunkSections.length) {
-                PalettedContainer<BlockState> palettedContainer;
-                if (compoundTag.contains("block_states", 10)) {
-                    palettedContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, compoundTag.getCompound("block_states"))
-                        .promotePartial(errorMessage -> logErrors(chunkPos, k, errorMessage))
-                        .getOrThrow(ChunkSerializer.ChunkReadException::new);
+        for (int j = 0; j < nbttaglist.size(); ++j) {
+            CompoundTag nbttagcompound1 = nbttaglist.getCompound(j);
+            byte b0 = nbttagcompound1.getByte("Y");
+            int k = world.getSectionIndexFromSectionY(b0);
+
+            if (k >= 0 && k < achunksection.length) {
+                PalettedContainer datapaletteblock;
+                // Paper start - Anti-Xray - Add preset block states
+                BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0);
+
+                if (nbttagcompound1.contains("block_states", 10)) {
+                    Codec<PalettedContainer<BlockState>> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates);
+                    datapaletteblock = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> {
+                        ChunkSerializer.logErrors(chunkPos, b0, s);
+                    }).getOrThrow(ChunkSerializer.ChunkReadException::new);
                 } else {
-                    palettedContainer = new PalettedContainer<>(
-                        Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES
-                    );
+                    datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates);
+                    // Paper end
                 }
 
-                PalettedContainerRO<Holder<Biome>> palettedContainerRO;
-                if (compoundTag.contains("biomes", 10)) {
-                    palettedContainerRO = codec.parse(NbtOps.INSTANCE, compoundTag.getCompound("biomes"))
-                        .promotePartial(errorMessage -> logErrors(chunkPos, k, errorMessage))
-                        .getOrThrow(ChunkSerializer.ChunkReadException::new);
+                PalettedContainer object; // CraftBukkit - read/write
+
+                if (nbttagcompound1.contains("biomes", 10)) {
+                    object = codec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("biomes")).promotePartial((s) -> { // CraftBukkit - decompile error
+                        ChunkSerializer.logErrors(chunkPos, b0, s);
+                    }).getOrThrow(ChunkSerializer.ChunkReadException::new);
                 } else {
-                    palettedContainerRO = new PalettedContainer<>(
-                        registry.asHolderIdMap(), registry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES
-                    );
+                    object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
                 }
 
-                LevelChunkSection levelChunkSection = new LevelChunkSection(palettedContainer, palettedContainerRO);
-                levelChunkSections[l] = levelChunkSection;
-                SectionPos sectionPos = SectionPos.of(chunkPos, k);
-                poiStorage.checkConsistencyWithBlocks(sectionPos, levelChunkSection);
+                LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
+
+                achunksection[k] = chunksection;
+                SectionPos sectionposition = SectionPos.of(chunkPos, b0);
+
+                // Paper - rewrite chunk system - moved to final load stage
             }
 
-            boolean bl4 = compoundTag.contains("BlockLight", 7);
-            boolean bl5 = bl2 && compoundTag.contains("SkyLight", 7);
-            if (bl4 || bl5) {
-                if (!bl3) {
-                    levelLightEngine.retainData(chunkPos, true);
-                    bl3 = true;
+            boolean flag3 = nbttagcompound1.contains("BlockLight", 7);
+            boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7);
+
+            if (flag3 || flag4) {
+                if (!flag2) {
+                    levellightengine.retainData(chunkPos, true);
+                    flag2 = true;
                 }
 
-                if (bl4) {
-                    levelLightEngine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, k), new DataLayer(compoundTag.getByteArray("BlockLight")));
+                if (flag3) {
+                    levellightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight")));
                 }
 
-                if (bl5) {
-                    levelLightEngine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, k), new DataLayer(compoundTag.getByteArray("SkyLight")));
+                if (flag4) {
+                    levellightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight")));
                 }
             }
         }
 
-        long m = nbt.getLong("InhabitedTime");
-        ChunkType chunkType = getChunkTypeFromTag(nbt);
-        BlendingData blendingData;
+        long l = nbt.getLong("InhabitedTime");
+        ChunkType chunktype = ChunkSerializer.getChunkTypeFromTag(nbt);
+        DataResult dataresult;
+        Logger logger;
+        BlendingData blendingdata;
+
         if (nbt.contains("blending_data", 10)) {
-            blendingData = BlendingData.CODEC
-                .parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getCompound("blending_data")))
-                .resultOrPartial(LOGGER::error)
-                .orElse(null);
+            dataresult = BlendingData.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.getCompound("blending_data")));
+            logger = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger);
+            blendingdata = (BlendingData) ((DataResult<BlendingData>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
         } else {
-            blendingData = null;
+            blendingdata = null;
         }
 
-        ChunkAccess chunkAccess;
-        if (chunkType == ChunkType.LEVELCHUNK) {
-            LevelChunkTicks<Block> levelChunkTicks = LevelChunkTicks.load(
-                nbt.getList("block_ticks", 10), id -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(id)), chunkPos
-            );
-            LevelChunkTicks<Fluid> levelChunkTicks2 = LevelChunkTicks.load(
-                nbt.getList("fluid_ticks", 10), id -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(id)), chunkPos
-            );
-            chunkAccess = new LevelChunk(
-                world.getLevel(), chunkPos, upgradeData, levelChunkTicks, levelChunkTicks2, m, levelChunkSections, postLoadChunk(world, nbt), blendingData
-            );
+        Object object1;
+
+        if (chunktype == ChunkType.LEVELCHUNK) {
+            LevelChunkTicks<Block> levelchunkticks = LevelChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> {
+                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s));
+            }, chunkPos);
+            LevelChunkTicks<Fluid> levelchunkticks1 = LevelChunkTicks.load(nbt.getList("fluid_ticks", 10), (s) -> {
+                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s));
+            }, chunkPos);
+
+            object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata);
         } else {
-            ProtoChunkTicks<Block> protoChunkTicks = ProtoChunkTicks.load(
-                nbt.getList("block_ticks", 10), id -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(id)), chunkPos
-            );
-            ProtoChunkTicks<Fluid> protoChunkTicks2 = ProtoChunkTicks.load(
-                nbt.getList("fluid_ticks", 10), id -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(id)), chunkPos
-            );
-            ProtoChunk protoChunk = new ProtoChunk(chunkPos, upgradeData, levelChunkSections, protoChunkTicks, protoChunkTicks2, world, registry, blendingData);
-            chunkAccess = protoChunk;
-            protoChunk.setInhabitedTime(m);
+            ProtoChunkTicks<Block> protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> {
+                return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s));
+            }, chunkPos);
+            ProtoChunkTicks<Fluid> protochunkticklist1 = ProtoChunkTicks.load(nbt.getList("fluid_ticks", 10), (s) -> {
+                return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s));
+            }, chunkPos);
+            ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata);
+
+            object1 = protochunk;
+            protochunk.setInhabitedTime(l);
             if (nbt.contains("below_zero_retrogen", 10)) {
-                BelowZeroRetrogen.CODEC
-                    .parse(new Dynamic<>(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen")))
-                    .resultOrPartial(LOGGER::error)
-                    .ifPresent(protoChunk::setBelowZeroRetrogen);
+                dataresult = BelowZeroRetrogen.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen")));
+                logger = ChunkSerializer.LOGGER;
+                Objects.requireNonNull(logger);
+                Optional<BelowZeroRetrogen> optional = ((DataResult<BelowZeroRetrogen>) dataresult).resultOrPartial(logger::error); // CraftBukkit - decompile error
+
+                Objects.requireNonNull(protochunk);
+                optional.ifPresent(protochunk::setBelowZeroRetrogen);
             }
 
-            ChunkStatus chunkStatus = ChunkStatus.byName(nbt.getString("Status"));
-            protoChunk.setPersistedStatus(chunkStatus);
-            if (chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
-                protoChunk.setLightEngine(levelLightEngine);
+            ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
+
+            protochunk.setPersistedStatus(chunkstatus);
+            if (chunkstatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
+                protochunk.setLightEngine(levellightengine);
             }
         }
 
-        chunkAccess.setLightCorrect(bl);
-        CompoundTag compoundTag2 = nbt.getCompound("Heightmaps");
-        EnumSet<Heightmap.Types> enumSet = EnumSet.noneOf(Heightmap.Types.class);
+        // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
+        net.minecraft.nbt.Tag persistentBase = nbt.get("ChunkBukkitValues");
+        if (persistentBase instanceof CompoundTag) {
+            ((ChunkAccess) object1).persistentDataContainer.putAll((CompoundTag) persistentBase);
+        }
+        // CraftBukkit end
 
-        for (Heightmap.Types types : chunkAccess.getPersistedStatus().heightmapsAfter()) {
-            String string = types.getSerializationKey();
-            if (compoundTag2.contains(string, 12)) {
-                chunkAccess.setHeightmap(types, compoundTag2.getLongArray(string));
+        ((ChunkAccess) object1).setLightCorrect(flag);
+        CompoundTag nbttagcompound2 = nbt.getCompound("Heightmaps");
+        EnumSet<Heightmap.Types> enumset = EnumSet.noneOf(Heightmap.Types.class);
+        Iterator iterator = ((ChunkAccess) object1).getPersistedStatus().heightmapsAfter().iterator();
+
+        while (iterator.hasNext()) {
+            Heightmap.Types heightmap_type = (Heightmap.Types) iterator.next();
+            String s = heightmap_type.getSerializationKey();
+
+            if (nbttagcompound2.contains(s, 12)) {
+                ((ChunkAccess) object1).setHeightmap(heightmap_type, nbttagcompound2.getLongArray(s));
             } else {
-                enumSet.add(types);
+                enumset.add(heightmap_type);
             }
         }
 
-        Heightmap.primeHeightmaps(chunkAccess, enumSet);
-        CompoundTag compoundTag3 = nbt.getCompound("structures");
-        chunkAccess.setAllStarts(unpackStructureStart(StructurePieceSerializationContext.fromLevel(world), compoundTag3, world.getSeed()));
-        chunkAccess.setAllReferences(unpackStructureReferences(world.registryAccess(), chunkPos, compoundTag3));
+        Heightmap.primeHeightmaps((ChunkAccess) object1, enumset);
+        CompoundTag nbttagcompound3 = nbt.getCompound("structures");
+
+        ((ChunkAccess) object1).setAllStarts(ChunkSerializer.unpackStructureStart(StructurePieceSerializationContext.fromLevel(world), nbttagcompound3, world.getSeed()));
+        ((ChunkAccess) object1).setAllReferences(ChunkSerializer.unpackStructureReferences(world.registryAccess(), chunkPos, nbttagcompound3));
         if (nbt.getBoolean("shouldSave")) {
-            chunkAccess.setUnsaved(true);
+            ((ChunkAccess) object1).setUnsaved(true);
         }
 
-        ListTag listTag2 = nbt.getList("PostProcessing", 9);
+        ListTag nbttaglist1 = nbt.getList("PostProcessing", 9);
 
-        for (int n = 0; n < listTag2.size(); n++) {
-            ListTag listTag3 = listTag2.getList(n);
+        ListTag nbttaglist2;
+        int i1;
 
-            for (int o = 0; o < listTag3.size(); o++) {
-                chunkAccess.addPackedPostProcess(listTag3.getShort(o), n);
+        for (int j1 = 0; j1 < nbttaglist1.size(); ++j1) {
+            nbttaglist2 = nbttaglist1.getList(j1);
+
+            for (i1 = 0; i1 < nbttaglist2.size(); ++i1) {
+                ((ChunkAccess) object1).addPackedPostProcess(nbttaglist2.getShort(i1), j1);
             }
         }
 
-        if (chunkType == ChunkType.LEVELCHUNK) {
-            return new ImposterProtoChunk((LevelChunk)chunkAccess, false);
+        ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.loadLightHook(world, chunkPos, nbt, (ChunkAccess)object1); // Paper - rewrite chunk system - note: it's ok to pass the raw value instead of wrapped
+
+        if (chunktype == ChunkType.LEVELCHUNK) {
+            return new ImposterProtoChunk((LevelChunk) object1, false);
         } else {
-            ProtoChunk protoChunk2 = (ProtoChunk)chunkAccess;
-            ListTag listTag4 = nbt.getList("entities", 10);
+            ProtoChunk protochunk1 = (ProtoChunk) object1;
 
-            for (int p = 0; p < listTag4.size(); p++) {
-                protoChunk2.addEntity(listTag4.getCompound(p));
+            nbttaglist2 = nbt.getList("entities", 10);
+
+            for (i1 = 0; i1 < nbttaglist2.size(); ++i1) {
+                protochunk1.addEntity(nbttaglist2.getCompound(i1));
             }
 
-            ListTag listTag5 = nbt.getList("block_entities", 10);
+            ListTag nbttaglist3 = nbt.getList("block_entities", 10);
 
-            for (int q = 0; q < listTag5.size(); q++) {
-                CompoundTag compoundTag4 = listTag5.getCompound(q);
-                chunkAccess.setBlockEntityNbt(compoundTag4);
+            for (int k1 = 0; k1 < nbttaglist3.size(); ++k1) {
+                CompoundTag nbttagcompound4 = nbttaglist3.getCompound(k1);
+
+                // Paper start - do not read tile entities positioned outside the chunk
+                BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound4);
+                if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+                    LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
+                    continue;
+                }
+                // Paper end - do not read tile entities positioned outside the chunk
+                ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4);
             }
 
-            CompoundTag compoundTag5 = nbt.getCompound("CarvingMasks");
+            CompoundTag nbttagcompound5 = nbt.getCompound("CarvingMasks");
+            Iterator iterator1 = nbttagcompound5.getAllKeys().iterator();
 
-            for (String string2 : compoundTag5.getAllKeys()) {
-                GenerationStep.Carving carving = GenerationStep.Carving.valueOf(string2);
-                protoChunk2.setCarvingMask(carving, new CarvingMask(compoundTag5.getLongArray(string2), chunkAccess.getMinBuildHeight()));
+            while (iterator1.hasNext()) {
+                String s1 = (String) iterator1.next();
+                GenerationStep.Carving worldgenstage_features = GenerationStep.Carving.valueOf(s1);
+
+                protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight()));
             }
 
-            return protoChunk2;
+            return protochunk1;
         }
     }
 
     private static void logErrors(ChunkPos chunkPos, int y, String message) {
-        LOGGER.error("Recoverable errors when loading section [{}, {}, {}]: {}", chunkPos.x, y, chunkPos.z, message);
+        ChunkSerializer.LOGGER.error("Recoverable errors when loading section [{}, {}, {}]: {}", new Object[]{chunkPos.x, y, chunkPos.z, message});
     }
 
     private static Codec<PalettedContainerRO<Holder<Biome>>> makeBiomeCodec(Registry<Biome> biomeRegistry) {
-        return PalettedContainer.codecRO(
-            biomeRegistry.asHolderIdMap(),
-            biomeRegistry.holderByNameCodec(),
-            PalettedContainer.Strategy.SECTION_BIOMES,
-            biomeRegistry.getHolderOrThrow(Biomes.PLAINS)
-        );
+        return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
     }
 
+    // CraftBukkit start - read/write
+    private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
+        return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes
+    }
+    // CraftBukkit end
+
+    // Paper start - async chunk saving
+    // must be called sync
+    public static ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) {
+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world, chunk.locX, chunk.locZ, "Preparing async chunk save data");
+
+        final CompoundTag tickLists = new CompoundTag();
+        ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization());
+
+        ListTag blockEntitiesSerialized = new ListTag();
+        for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) {
+            final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos, world.registryAccess());
+            if (blockEntityNbt != null) {
+                blockEntitiesSerialized.add(blockEntityNbt);
+            }
+        }
+
+        return new ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData(
+            tickLists.get(BLOCK_TICKS_TAG),
+            tickLists.get(FLUID_TICKS_TAG),
+            blockEntitiesSerialized,
+            world.getGameTime()
+        );
+     }
+    // Paper end - async chunk saving
+
     public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
-        ChunkPos chunkPos = chunk.getPos();
-        CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag());
-        compoundTag.putInt("xPos", chunkPos.x);
-        compoundTag.putInt("yPos", chunk.getMinSection());
-        compoundTag.putInt("zPos", chunkPos.z);
-        compoundTag.putLong("LastUpdate", world.getGameTime());
-        compoundTag.putLong("InhabitedTime", chunk.getInhabitedTime());
-        compoundTag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString());
-        BlendingData blendingData = chunk.getBlendingData();
-        if (blendingData != null) {
-            BlendingData.CODEC
-                .encodeStart(NbtOps.INSTANCE, blendingData)
-                .resultOrPartial(LOGGER::error)
-                .ifPresent(tag -> compoundTag.put("blending_data", tag));
+        // Paper start - async chunk saving
+        return saveChunk(world, chunk, null);
+    }
+    public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData asyncsavedata) {
+        // Paper end - async chunk saving
+        ChunkPos chunkcoordintpair = chunk.getPos();
+        CompoundTag nbttagcompound = NbtUtils.addCurrentDataVersion(new CompoundTag());
+
+        nbttagcompound.putInt("xPos", chunkcoordintpair.x);
+        nbttagcompound.putInt("yPos", chunk.getMinSection());
+        nbttagcompound.putInt("zPos", chunkcoordintpair.z);
+        nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime() : world.getGameTime()); // Paper - async chunk saving // Paper - diff on change
+        nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
+        nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString());
+        BlendingData blendingdata = chunk.getBlendingData();
+        DataResult<Tag> dataresult; // CraftBukkit - decompile error
+        Logger logger;
+
+        if (blendingdata != null) {
+            dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
+            logger = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger);
+            dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
+                nbttagcompound.put("blending_data", nbtbase);
+            });
         }
 
-        BelowZeroRetrogen belowZeroRetrogen = chunk.getBelowZeroRetrogen();
-        if (belowZeroRetrogen != null) {
-            BelowZeroRetrogen.CODEC
-                .encodeStart(NbtOps.INSTANCE, belowZeroRetrogen)
-                .resultOrPartial(LOGGER::error)
-                .ifPresent(tag -> compoundTag.put("below_zero_retrogen", tag));
+        BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen();
+
+        if (belowzeroretrogen != null) {
+            dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen);
+            logger = ChunkSerializer.LOGGER;
+            Objects.requireNonNull(logger);
+            dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
+                nbttagcompound.put("below_zero_retrogen", nbtbase);
+            });
         }
 
-        UpgradeData upgradeData = chunk.getUpgradeData();
-        if (!upgradeData.isEmpty()) {
-            compoundTag.put("UpgradeData", upgradeData.write());
+        UpgradeData chunkconverter = chunk.getUpgradeData();
+
+        if (!chunkconverter.isEmpty()) {
+            nbttagcompound.put("UpgradeData", chunkconverter.write());
         }
 
-        LevelChunkSection[] levelChunkSections = chunk.getSections();
-        ListTag listTag = new ListTag();
-        LevelLightEngine levelLightEngine = world.getChunkSource().getLightEngine();
-        Registry<Biome> registry = world.registryAccess().registryOrThrow(Registries.BIOME);
-        Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
-        boolean bl = chunk.isLightCorrect();
+        LevelChunkSection[] achunksection = chunk.getSections();
+        ListTag nbttaglist = new ListTag();
+        ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
+        Registry<Biome> iregistry = world.registryAccess().registryOrThrow(Registries.BIOME);
+        Codec<PalettedContainerRO<Holder<Biome>>> codec = ChunkSerializer.makeBiomeCodec(iregistry);
+        boolean flag = chunk.isLightCorrect();
 
-        for (int i = levelLightEngine.getMinLightSection(); i < levelLightEngine.getMaxLightSection(); i++) {
+        for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
             int j = chunk.getSectionIndexFromSectionY(i);
-            boolean bl2 = j >= 0 && j < levelChunkSections.length;
-            DataLayer dataLayer = levelLightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i));
-            DataLayer dataLayer2 = levelLightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i));
-            if (bl2 || dataLayer != null || dataLayer2 != null) {
-                CompoundTag compoundTag2 = new CompoundTag();
-                if (bl2) {
-                    LevelChunkSection levelChunkSection = levelChunkSections[j];
-                    compoundTag2.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, levelChunkSection.getStates()).getOrThrow());
-                    compoundTag2.put("biomes", codec.encodeStart(NbtOps.INSTANCE, levelChunkSection.getBiomes()).getOrThrow());
+            boolean flag1 = j >= 0 && j < achunksection.length;
+            DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
+            DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
+
+            if (flag1 || nibblearray != null || nibblearray1 != null) {
+                CompoundTag nbttagcompound1 = new CompoundTag();
+
+                if (flag1) {
+                    LevelChunkSection chunksection = achunksection[j];
+
+                    nbttagcompound1.put("block_states", (Tag) ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunksection.getStates()).getOrThrow());
+                    nbttagcompound1.put("biomes", (Tag) codec.encodeStart(NbtOps.INSTANCE, chunksection.getBiomes()).getOrThrow());
                 }
 
-                if (dataLayer != null && !dataLayer.isEmpty()) {
-                    compoundTag2.putByteArray("BlockLight", dataLayer.getData());
+                if (nibblearray != null && !nibblearray.isEmpty()) {
+                    nbttagcompound1.putByteArray("BlockLight", nibblearray.getData());
                 }
 
-                if (dataLayer2 != null && !dataLayer2.isEmpty()) {
-                    compoundTag2.putByteArray("SkyLight", dataLayer2.getData());
+                if (nibblearray1 != null && !nibblearray1.isEmpty()) {
+                    nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData());
                 }
 
-                if (!compoundTag2.isEmpty()) {
-                    compoundTag2.putByte("Y", (byte)i);
-                    listTag.add(compoundTag2);
+                if (!nbttagcompound1.isEmpty()) {
+                    nbttagcompound1.putByte("Y", (byte) i);
+                    nbttaglist.add(nbttagcompound1);
                 }
             }
         }
 
-        compoundTag.put("sections", listTag);
-        if (bl) {
-            compoundTag.putBoolean("isLightOn", true);
+        nbttagcompound.put("sections", nbttaglist);
+        if (flag) {
+            nbttagcompound.putBoolean("isLightOn", true);
         }
 
-        ListTag listTag2 = new ListTag();
+        // Paper start - async chunk saving
+        ListTag nbttaglist1;
+        Iterator<BlockPos> iterator;
+        if (asyncsavedata != null) {
+            nbttaglist1 = asyncsavedata.blockEntities();
+            iterator = java.util.Collections.emptyIterator();
+        } else {
+            nbttaglist1 = new ListTag();
+            iterator = chunk.getBlockEntitiesPos().iterator();
+        }
+        // Paper end - async chunk saving
 
-        for (BlockPos blockPos : chunk.getBlockEntitiesPos()) {
-            CompoundTag compoundTag3 = chunk.getBlockEntityNbtForSaving(blockPos, world.registryAccess());
-            if (compoundTag3 != null) {
-                listTag2.add(compoundTag3);
+        CompoundTag nbttagcompound2;
+
+        while (iterator.hasNext()) {
+            BlockPos blockposition = (BlockPos) iterator.next();
+
+            nbttagcompound2 = chunk.getBlockEntityNbtForSaving(blockposition, world.registryAccess());
+            if (nbttagcompound2 != null) {
+                nbttaglist1.add(nbttagcompound2);
             }
         }
 
-        compoundTag.put("block_entities", listTag2);
+        nbttagcompound.put("block_entities", nbttaglist1);
         if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) {
-            ProtoChunk protoChunk = (ProtoChunk)chunk;
-            ListTag listTag3 = new ListTag();
-            listTag3.addAll(protoChunk.getEntities());
-            compoundTag.put("entities", listTag3);
-            CompoundTag compoundTag4 = new CompoundTag();
+            ProtoChunk protochunk = (ProtoChunk) chunk;
+            ListTag nbttaglist2 = new ListTag();
 
-            for (GenerationStep.Carving carving : GenerationStep.Carving.values()) {
-                CarvingMask carvingMask = protoChunk.getCarvingMask(carving);
-                if (carvingMask != null) {
-                    compoundTag4.putLongArray(carving.toString(), carvingMask.toArray());
+            nbttaglist2.addAll(protochunk.getEntities());
+            nbttagcompound.put("entities", nbttaglist2);
+            nbttagcompound2 = new CompoundTag();
+            GenerationStep.Carving[] aworldgenstage_features = GenerationStep.Carving.values();
+            int k = aworldgenstage_features.length;
+
+            for (int l = 0; l < k; ++l) {
+                GenerationStep.Carving worldgenstage_features = aworldgenstage_features[l];
+                CarvingMask carvingmask = protochunk.getCarvingMask(worldgenstage_features);
+
+                if (carvingmask != null) {
+                    nbttagcompound2.putLongArray(worldgenstage_features.toString(), carvingmask.toArray());
                 }
             }
 
-            compoundTag.put("CarvingMasks", compoundTag4);
+            nbttagcompound.put("CarvingMasks", nbttagcompound2);
         }
 
-        saveTicks(world, compoundTag, chunk.getTicksForSerialization());
-        compoundTag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
-        CompoundTag compoundTag5 = new CompoundTag();
+        // Paper start
+        if (asyncsavedata != null) {
+            nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList());
+            nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList());
+        } else {
+        ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization());
+        }
+        // Paper end
+        nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing()));
+        CompoundTag nbttagcompound3 = new CompoundTag();
+        Iterator iterator1 = chunk.getHeightmaps().iterator();
+
+        while (iterator1.hasNext()) {
+            Entry<Heightmap.Types, Heightmap> entry = (Entry) iterator1.next();
 
-        for (Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
             if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
-                compoundTag5.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
+                nbttagcompound3.put(((Heightmap.Types) entry.getKey()).getSerializationKey(), new LongArrayTag(((Heightmap) entry.getValue()).getRawData()));
             }
         }
 
-        compoundTag.put("Heightmaps", compoundTag5);
-        compoundTag.put(
-            "structures", packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkPos, chunk.getAllStarts(), chunk.getAllReferences())
-        );
-        return compoundTag;
+        nbttagcompound.put("Heightmaps", nbttagcompound3);
+        nbttagcompound.put("structures", ChunkSerializer.packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()));
+        // CraftBukkit start - store chunk persistent data in nbt
+        if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
+            nbttagcompound.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
+        }
+        // CraftBukkit end
+        ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.saveLightHook(world, chunk, nbttagcompound); // Paper - rewrite chunk system
+        return nbttagcompound;
     }
 
     private static void saveTicks(ServerLevel world, CompoundTag nbt, ChunkAccess.TicksToSave tickSchedulers) {
-        long l = world.getLevelData().getGameTime();
-        nbt.put("block_ticks", tickSchedulers.blocks().save(l, block -> BuiltInRegistries.BLOCK.getKey(block).toString()));
-        nbt.put("fluid_ticks", tickSchedulers.fluids().save(l, fluid -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
+        long i = world.getLevelData().getGameTime();
+
+        nbt.put("block_ticks", tickSchedulers.blocks().save(i, (block) -> {
+            return BuiltInRegistries.BLOCK.getKey(block).toString();
+        }));
+        nbt.put("fluid_ticks", tickSchedulers.fluids().save(i, (fluidtype) -> {
+            return BuiltInRegistries.FLUID.getKey(fluidtype).toString();
+        }));
     }
 
     public static ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
@@ -396,77 +566,106 @@
 
     @Nullable
     private static LevelChunk.PostLoadProcessor postLoadChunk(ServerLevel world, CompoundTag nbt) {
-        ListTag listTag = getListOfCompoundsOrNull(nbt, "entities");
-        ListTag listTag2 = getListOfCompoundsOrNull(nbt, "block_entities");
-        return listTag == null && listTag2 == null ? null : chunk -> {
-            if (listTag != null) {
-                world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(listTag, world));
+        ListTag nbttaglist = ChunkSerializer.getListOfCompoundsOrNull(nbt, "entities");
+        ListTag nbttaglist1 = ChunkSerializer.getListOfCompoundsOrNull(nbt, "block_entities");
+
+        return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> {
+            if (nbttaglist != null) {
+                world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world), chunk.getPos()); // Paper - rewrite chunk system
             }
 
-            if (listTag2 != null) {
-                for (int i = 0; i < listTag2.size(); i++) {
-                    CompoundTag compoundTag = listTag2.getCompound(i);
-                    boolean bl = compoundTag.getBoolean("keepPacked");
-                    if (bl) {
-                        chunk.setBlockEntityNbt(compoundTag);
+            if (nbttaglist1 != null) {
+                for (int i = 0; i < nbttaglist1.size(); ++i) {
+                    CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i);
+                    boolean flag = nbttagcompound1.getBoolean("keepPacked");
+
+                    // Paper start - do not read tile entities positioned outside the chunk
+                    BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); // moved up
+                    ChunkPos chunkPos = chunk.getPos();
+                    if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+                        LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
+                        continue;
+                    }
+                    // Paper end - do not read tile entities positioned outside the chunk
+
+                    if (flag) {
+                        chunk.setBlockEntityNbt(nbttagcompound1);
                     } else {
-                        BlockPos blockPos = BlockEntity.getPosFromTag(compoundTag);
-                        BlockEntity blockEntity = BlockEntity.loadStatic(blockPos, chunk.getBlockState(blockPos), compoundTag, world.registryAccess());
-                        if (blockEntity != null) {
-                            chunk.setBlockEntity(blockEntity);
+                        // Paper - do not read tile entities positioned outside the chunk; move up
+                        BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound1, world.registryAccess());
+
+                        if (tileentity != null) {
+                            chunk.setBlockEntity(tileentity);
                         }
                     }
                 }
             }
+
         };
     }
 
     @Nullable
     private static ListTag getListOfCompoundsOrNull(CompoundTag nbt, String key) {
-        ListTag listTag = nbt.getList(key, 10);
-        return listTag.isEmpty() ? null : listTag;
+        ListTag nbttaglist = nbt.getList(key, 10);
+
+        return nbttaglist.isEmpty() ? null : nbttaglist;
     }
 
-    private static CompoundTag packStructureData(
-        StructurePieceSerializationContext context, ChunkPos pos, Map<Structure, StructureStart> starts, Map<Structure, LongSet> references
-    ) {
-        CompoundTag compoundTag = new CompoundTag();
-        CompoundTag compoundTag2 = new CompoundTag();
-        Registry<Structure> registry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+    private static CompoundTag packStructureData(StructurePieceSerializationContext context, ChunkPos pos, Map<Structure, StructureStart> starts, Map<Structure, LongSet> references) {
+        CompoundTag nbttagcompound = new CompoundTag();
+        CompoundTag nbttagcompound1 = new CompoundTag();
+        Registry<Structure> iregistry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+        Iterator iterator = starts.entrySet().iterator();
 
-        for (Entry<Structure, StructureStart> entry : starts.entrySet()) {
-            ResourceLocation resourceLocation = registry.getKey(entry.getKey());
-            compoundTag2.put(resourceLocation.toString(), entry.getValue().createTag(context, pos));
+        while (iterator.hasNext()) {
+            Entry<Structure, StructureStart> entry = (Entry) iterator.next();
+            ResourceLocation minecraftkey = iregistry.getKey((Structure) entry.getKey());
+
+            nbttagcompound1.put(minecraftkey.toString(), ((StructureStart) entry.getValue()).createTag(context, pos));
         }
 
-        compoundTag.put("starts", compoundTag2);
-        CompoundTag compoundTag3 = new CompoundTag();
+        nbttagcompound.put("starts", nbttagcompound1);
+        CompoundTag nbttagcompound2 = new CompoundTag();
+        Iterator iterator1 = references.entrySet().iterator();
 
-        for (Entry<Structure, LongSet> entry2 : references.entrySet()) {
-            if (!entry2.getValue().isEmpty()) {
-                ResourceLocation resourceLocation2 = registry.getKey(entry2.getKey());
-                compoundTag3.put(resourceLocation2.toString(), new LongArrayTag(entry2.getValue()));
+        while (iterator1.hasNext()) {
+            Entry<Structure, LongSet> entry1 = (Entry) iterator1.next();
+
+            if (!((LongSet) entry1.getValue()).isEmpty()) {
+                ResourceLocation minecraftkey1 = iregistry.getKey((Structure) entry1.getKey());
+
+                nbttagcompound2.put(minecraftkey1.toString(), new LongArrayTag((LongSet) entry1.getValue()));
             }
         }
 
-        compoundTag.put("References", compoundTag3);
-        return compoundTag;
+        nbttagcompound.put("References", nbttagcompound2);
+        return nbttagcompound;
     }
 
     private static Map<Structure, StructureStart> unpackStructureStart(StructurePieceSerializationContext context, CompoundTag nbt, long worldSeed) {
         Map<Structure, StructureStart> map = Maps.newHashMap();
-        Registry<Structure> registry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
-        CompoundTag compoundTag = nbt.getCompound("starts");
+        Registry<Structure> iregistry = context.registryAccess().registryOrThrow(Registries.STRUCTURE);
+        CompoundTag nbttagcompound1 = nbt.getCompound("starts");
+        Iterator iterator = nbttagcompound1.getAllKeys().iterator();
+
+        while (iterator.hasNext()) {
+            String s = (String) iterator.next();
+            ResourceLocation minecraftkey = ResourceLocation.tryParse(s);
+            Structure structure = (Structure) iregistry.get(minecraftkey);
 
-        for (String string : compoundTag.getAllKeys()) {
-            ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
-            Structure structure = registry.get(resourceLocation);
             if (structure == null) {
-                LOGGER.error("Unknown structure start: {}", resourceLocation);
+                ChunkSerializer.LOGGER.error("Unknown structure start: {}", minecraftkey);
             } else {
-                StructureStart structureStart = StructureStart.loadStaticStart(context, compoundTag.getCompound(string), worldSeed);
-                if (structureStart != null) {
-                    map.put(structure, structureStart);
+                StructureStart structurestart = StructureStart.loadStaticStart(context, nbttagcompound1.getCompound(s), worldSeed);
+
+                if (structurestart != null) {
+                    // CraftBukkit start - load persistent data for structure start
+                    net.minecraft.nbt.Tag persistentBase = nbttagcompound1.getCompound(s).get("StructureBukkitValues");
+                    if (persistentBase instanceof CompoundTag) {
+                        structurestart.persistentDataContainer.putAll((CompoundTag) persistentBase);
+                    }
+                    // CraftBukkit end
+                    map.put(structure, structurestart);
                 }
             }
         }
@@ -476,21 +675,26 @@
 
     private static Map<Structure, LongSet> unpackStructureReferences(RegistryAccess registryManager, ChunkPos pos, CompoundTag nbt) {
         Map<Structure, LongSet> map = Maps.newHashMap();
-        Registry<Structure> registry = registryManager.registryOrThrow(Registries.STRUCTURE);
-        CompoundTag compoundTag = nbt.getCompound("References");
+        Registry<Structure> iregistry = registryManager.registryOrThrow(Registries.STRUCTURE);
+        CompoundTag nbttagcompound1 = nbt.getCompound("References");
+        Iterator iterator = nbttagcompound1.getAllKeys().iterator();
+
+        while (iterator.hasNext()) {
+            String s = (String) iterator.next();
+            ResourceLocation minecraftkey = ResourceLocation.tryParse(s);
+            Structure structure = (Structure) iregistry.get(minecraftkey);
 
-        for (String string : compoundTag.getAllKeys()) {
-            ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
-            Structure structure = registry.get(resourceLocation);
             if (structure == null) {
-                LOGGER.warn("Found reference to unknown structure '{}' in chunk {}, discarding", resourceLocation, pos);
+                ChunkSerializer.LOGGER.warn("Found reference to unknown structure '{}' in chunk {}, discarding", minecraftkey, pos);
             } else {
-                long[] ls = compoundTag.getLongArray(string);
-                if (ls.length != 0) {
-                    map.put(structure, new LongOpenHashSet(Arrays.stream(ls).filter(packedPos -> {
-                        ChunkPos chunkPos2 = new ChunkPos(packedPos);
-                        if (chunkPos2.getChessboardDistance(pos) > 8) {
-                            LOGGER.warn("Found invalid structure reference [ {} @ {} ] for chunk {}.", resourceLocation, chunkPos2, pos);
+                long[] along = nbttagcompound1.getLongArray(s);
+
+                if (along.length != 0) {
+                    map.put(structure, new LongOpenHashSet(Arrays.stream(along).filter((i) -> {
+                        ChunkPos chunkcoordintpair1 = new ChunkPos(i);
+
+                        if (chunkcoordintpair1.getChessboardDistance(pos) > 8) {
+                            ChunkSerializer.LOGGER.warn("Found invalid structure reference [ {} @ {} ] for chunk {}.", new Object[]{minecraftkey, chunkcoordintpair1, pos});
                             return false;
                         } else {
                             return true;
@@ -504,23 +708,32 @@
     }
 
     public static ListTag packOffsets(ShortList[] lists) {
-        ListTag listTag = new ListTag();
+        ListTag nbttaglist = new ListTag();
+        ShortList[] ashortlist1 = lists;
+        int i = lists.length;
 
-        for (ShortList shortList : lists) {
-            ListTag listTag2 = new ListTag();
-            if (shortList != null) {
-                for (Short short_ : shortList) {
-                    listTag2.add(ShortTag.valueOf(short_));
+        for (int j = 0; j < i; ++j) {
+            ShortList shortlist = ashortlist1[j];
+            ListTag nbttaglist1 = new ListTag();
+
+            if (shortlist != null) {
+                ShortListIterator shortlistiterator = shortlist.iterator();
+
+                while (shortlistiterator.hasNext()) {
+                    Short oshort = (Short) shortlistiterator.next();
+
+                    nbttaglist1.add(ShortTag.valueOf(oshort));
                 }
             }
 
-            listTag.add(listTag2);
+            nbttaglist.add(nbttaglist1);
         }
 
-        return listTag;
+        return nbttaglist;
     }
 
     public static class ChunkReadException extends NbtException {
+
         public ChunkReadException(String message) {
             super(message);
         }
