From 2546348b9d769c491c0770f3b7f5e443e459831b Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sat, 14 Dec 2024 14:36:48 -0800 Subject: [PATCH] net.minecraft.world.level.chunk.storage --- .../chunk/storage/ChunkStorage.java.patch | 148 ++++++++++++ .../level/chunk/storage/RegionFile.java.patch | 73 ++++++ .../storage/RegionFileStorage.java.patch | 61 +++++ .../storage/RegionFileVersion.java.patch | 4 +- .../storage/SerializableChunkData.java.patch | 177 ++++++++++++++ .../chunk/storage/ChunkStorage.java.patch | 158 ------------- .../level/chunk/storage/RegionFile.java.patch | 127 ---------- .../storage/RegionFileStorage.java.patch | 67 ------ .../storage/SerializableChunkData.java.patch | 216 ------------------ 9 files changed, 461 insertions(+), 570 deletions(-) create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch (93%) create mode 100644 paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch new file mode 100644 index 0000000000..f7f4038d0e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch @@ -0,0 +1,148 @@ +--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -15,10 +_,16 @@ + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtUtils; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.level.ChunkPos; +-import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.chunk.ChunkGenerator; ++// CraftBukkit start ++import java.util.concurrent.ExecutionException; ++import net.minecraft.world.level.chunk.status.ChunkStatus; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler; + import net.minecraft.world.level.storage.DimensionDataStorage; + +@@ -38,17 +_,63 @@ + return this.worker.isOldChunkAround(pos, radius); + } + ++ // CraftBukkit start ++ private boolean check(ServerChunkCache cps, int x, int z) { ++ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" ++ ChunkPos pos = new ChunkPos(x, z); ++ if (cps != null) { ++ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); ++ if (cps.hasChunk(x, z)) { ++ return true; ++ } ++ } ++ ++ CompoundTag nbt; ++ try { ++ nbt = this.read(pos).get().orElse(null); ++ } catch (InterruptedException | ExecutionException ex) { ++ throw new RuntimeException(ex); ++ } ++ if (nbt != null) { ++ CompoundTag level = nbt.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated")) { ++ return true; ++ } ++ ++ ChunkStatus status = ChunkStatus.byName(level.getString("Status")); ++ if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ + public CompoundTag upgradeChunkTag( +- ResourceKey levelKey, ++ ResourceKey levelKey, + Supplier storage, + CompoundTag chunkData, +- Optional>> chunkGeneratorKey ++ Optional>> chunkGeneratorKey, ++ ChunkPos pos, ++ @Nullable LevelAccessor generatoraccess ++ // CraftBukkit end + ) { + int version = getVersion(chunkData); + if (version == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { + return chunkData; + } else { + try { ++ // CraftBukkit start ++ if (version < 1466) { ++ CompoundTag level = chunkData.getCompound("Level"); ++ if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { ++ ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); ++ if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) { ++ level.putBoolean("LightPopulated", true); ++ } ++ } ++ } ++ // CraftBukkit end + if (version < 1493) { + chunkData = DataFixTypes.CHUNK.update(this.fixerUpper, chunkData, version, 1493); + if (chunkData.getCompound("Level").getBoolean("hasLegacyStructureData")) { +@@ -57,8 +_,22 @@ + } + } + ++ // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty ++ boolean stopBelowZero = false; ++ boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks; ++ ++ if (version <= 2730 && !belowZeroGenerationInExistingChunks) { ++ stopBelowZero = "full".equals(chunkData.getCompound("Level").getString("Status")); ++ } ++ // Spigot end ++ + injectDatafixingContext(chunkData, levelKey, chunkGeneratorKey); + chunkData = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, chunkData, Math.max(1493, version)); ++ // Spigot start ++ if (stopBelowZero) { ++ chunkData.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString()); ++ } ++ // Spigot end + removeDatafixingContext(chunkData); + NbtUtils.addCurrentDataVersion(chunkData); + return chunkData; +@@ -71,7 +_,7 @@ + } + } + +- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey level, Supplier storage) { ++ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey level, Supplier storage) { // CraftBukkit + LegacyStructureDataHandler legacyStructureDataHandler = this.legacyStructureHandler; + if (legacyStructureDataHandler == null) { + synchronized (this) { +@@ -86,7 +_,7 @@ + } + + public static void injectDatafixingContext( +- CompoundTag chunkData, ResourceKey levelKey, Optional>> chunkGeneratorKey ++ CompoundTag chunkData, ResourceKey levelKey, Optional>> chunkGeneratorKey // CraftBukkit + ) { + CompoundTag compoundTag = new CompoundTag(); + compoundTag.putString("dimension", levelKey.location().toString()); +@@ -107,8 +_,19 @@ + } + + public CompletableFuture write(ChunkPos pos, Supplier tagSupplier) { ++ // Paper start - guard against possible chunk pos desync ++ final Supplier guardedPosCheck = () -> { ++ CompoundTag nbt = tagSupplier.get(); ++ if (nbt != null && !pos.equals(SerializableChunkData.getChunkCoordinate(nbt))) { ++ final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos ++ + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ return nbt; ++ }; ++ // Paper end - guard against possible chunk pos desync + this.handleLegacyStructureIndex(pos); +- return this.worker.store(pos, tagSupplier); ++ return this.worker.store(pos, guardedPosCheck); // Paper - guard against possible chunk pos desync + } + + protected void handleLegacyStructureIndex(ChunkPos chunkPos) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch new file mode 100644 index 0000000000..0a25ddead5 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFile.java.patch @@ -0,0 +1,73 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -1,3 +_,4 @@ ++// mc-dev import + package net.minecraft.world.level.chunk.storage; + + import com.google.common.annotations.VisibleForTesting; +@@ -46,7 +_,7 @@ + protected final RegionBitmap usedSectors = new RegionBitmap(); + + public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, boolean sync) throws IOException { +- this(info, path, externalFileDir, RegionFileVersion.getSelected(), sync); ++ this(info, path, externalFileDir, RegionFileVersion.getCompressionFormat(), sync); // Paper - Configurable region compression format + } + + public RegionFile(RegionStorageInfo info, Path path, Path externalFileDir, RegionFileVersion version, boolean sync) throws IOException { +@@ -82,6 +_,14 @@ + if (i2 != 0) { + int sectorNumber = getSectorNumber(i2); + int numSectors = getNumSectors(i2); ++ // Spigot start ++ if (numSectors == 255) { ++ // We're maxed out, so we need to read the proper length from the section ++ ByteBuffer realLen = ByteBuffer.allocate(4); ++ this.file.read(realLen, sectorNumber * 4096); ++ numSectors = (realLen.getInt(0) + 4) / 4096 + 1; ++ } ++ // Spigot end + if (sectorNumber < 2) { + LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i1, sectorNumber); + this.offsets.put(i1, 0); +@@ -117,6 +_,13 @@ + } else { + int sectorNumber = getSectorNumber(offset); + int numSectors = getNumSectors(offset); ++ // Spigot start ++ if (numSectors == 255) { ++ ByteBuffer realLen = ByteBuffer.allocate(4); ++ this.file.read(realLen, sectorNumber * 4096); ++ numSectors = (realLen.getInt(0) + 4) / 4096 + 1; ++ } ++ // Spigot end + int i = numSectors * 4096; + ByteBuffer byteBuffer = ByteBuffer.allocate(i); + this.file.read(byteBuffer, sectorNumber * 4096); +@@ -260,6 +_,7 @@ + return true; + } + } catch (IOException var9) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(var9); // Paper - ServerExceptionEvent + return false; + } + } +@@ -331,13 +_,18 @@ + try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { + chunkData.position(5); + fileChannel.write(chunkData); ++ // Paper start - ServerExceptionEvent ++ } catch (Throwable throwable) { ++ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); ++ throw throwable; ++ // Paper end - ServerExceptionEvent + } + + return () -> Files.move(path, externalChunkFile, StandardCopyOption.REPLACE_EXISTING); + } + + private void writeHeader() throws IOException { +- this.header.position(0); ++ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error + this.file.write(this.header, 0L); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch new file mode 100644 index 0000000000..fadb8b6cdb --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch @@ -0,0 +1,61 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -28,18 +_,19 @@ + this.info = info; + } + +- private RegionFile getRegionFile(ChunkPos chunkPos) throws IOException { ++ @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit + long packedChunkPos = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()); + RegionFile regionFile = this.regionCache.getAndMoveToFirst(packedChunkPos); + if (regionFile != null) { + return regionFile; + } else { +- if (this.regionCache.size() >= 256) { ++ if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable + this.regionCache.removeLast().close(); + } + + FileUtil.createDirectoriesSafe(this.folder); + Path path = this.folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca"); ++ if (existingOnly && !java.nio.file.Files.exists(path)) return null; // CraftBukkit + RegionFile regionFile1 = new RegionFile(this.info, path, this.folder, this.sync); + this.regionCache.putAndMoveToFirst(packedChunkPos, regionFile1); + return regionFile1; +@@ -48,7 +_,12 @@ + + @Nullable + public CompoundTag read(ChunkPos chunkPos) throws IOException { +- RegionFile regionFile = this.getRegionFile(chunkPos); ++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing ++ RegionFile regionFile = this.getRegionFile(chunkPos, true); ++ if (regionFile == null) { ++ return null; ++ } ++ // CraftBukkit end + + CompoundTag var4; + try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) { +@@ -63,7 +_,12 @@ + } + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException { +- RegionFile regionFile = this.getRegionFile(chunkPos); ++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing ++ RegionFile regionFile = this.getRegionFile(chunkPos, true); ++ if (regionFile == null) { ++ return; ++ } ++ // CraftBukkit end + + try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) { + if (chunkDataInputStream != null) { +@@ -73,7 +_,7 @@ + } + + protected void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException { +- RegionFile regionFile = this.getRegionFile(chunkPos); ++ RegionFile regionFile = this.getRegionFile(chunkPos, false); // CraftBukkit + if (chunkData == null) { + regionFile.clear(chunkPos); + } else { diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch similarity index 93% rename from paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch rename to paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch index 65bf070d12..ce1088103a 100644 --- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/RegionFileVersion.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -@@ -58,6 +58,15 @@ +@@ -61,6 +_,15 @@ private final RegionFileVersion.StreamWrapper inputWrapper; private final RegionFileVersion.StreamWrapper outputWrapper; @@ -15,4 +15,4 @@ + // Paper end - Configurable region compression format private RegionFileVersion( int id, - @Nullable String name, + @Nullable String optionName, diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch new file mode 100644 index 0000000000..56133882ab --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch @@ -0,0 +1,177 @@ +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -91,6 +_,7 @@ + List entities, + List blockEntities, + CompoundTag structureData ++ , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer + ) { + public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW( + Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState() +@@ -107,12 +_,39 @@ + public static final String BLOCK_LIGHT_TAG = "BlockLight"; + public static final String SKY_LIGHT_TAG = "SkyLight"; + ++ // 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 ++ ++ // 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 ++ + @Nullable + public static SerializableChunkData parse(LevelHeightAccessor levelHeightAccessor, RegistryAccess registries, CompoundTag tag) { + if (!tag.contains("Status", 8)) { + return null; + } else { +- ChunkPos chunkPos = new ChunkPos(tag.getInt("xPos"), tag.getInt("zPos")); ++ // Paper start - Do not let the server load chunks from newer versions ++ if (tag.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ final int dataVersion = tag.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 chunkPos = new ChunkPos(tag.getInt("xPos"), tag.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + long _long = tag.getLong("LastUpdate"); + long _long1 = tag.getLong("InhabitedTime"); + ChunkStatus chunkStatus = ChunkStatus.byName(tag.getString("Status")); +@@ -181,7 +_,7 @@ + ListTag list7 = tag.getList("sections", 10); + List list8 = new ArrayList<>(list7.size()); + Registry registry = registries.lookupOrThrow(Registries.BIOME); +- Codec>> codec = makeBiomeCodec(registry); ++ Codec>> codec = makeBiomeCodecRW(registry); // CraftBukkit - read/write + + for (int i2 = 0; i2 < list7.size(); i2++) { + CompoundTag compound2 = list7.getCompound(i2); +@@ -199,7 +_,7 @@ + ); + } + +- PalettedContainerRO> palettedContainerRo; ++ PalettedContainer> palettedContainerRo; // CraftBukkit - read/write + if (compound2.contains("biomes", 10)) { + palettedContainerRo = codec.parse(NbtOps.INSTANCE, compound2.getCompound("biomes")) + .promotePartial(string -> logErrors(chunkPos, _byte, string)) +@@ -239,6 +_,7 @@ + list5, + list6, + compound1 ++ , tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues + ); + } + } +@@ -316,6 +_,12 @@ + } + } + ++ // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading. ++ if (this.persistentDataContainer instanceof CompoundTag) { ++ chunkAccess.persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer); ++ } ++ // CraftBukkit end ++ + chunkAccess.setLightCorrect(this.lightCorrect); + EnumSet set = EnumSet.noneOf(Heightmap.Types.class); + +@@ -346,6 +_,13 @@ + } + + for (CompoundTag compoundTag : this.blockEntities) { ++ // Paper start - do not read tile entities positioned outside the chunk ++ final BlockPos blockposition = BlockEntity.getPosFromTag(compoundTag); ++ if ((blockposition.getX() >> 4) != this.chunkPos.x || (blockposition.getZ() >> 4) != this.chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", this.chunkPos, level.getWorld().getName(), blockposition); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + protoChunk1.setBlockEntityNbt(compoundTag); + } + +@@ -370,6 +_,12 @@ + ); + } + ++ // CraftBukkit start - read/write ++ private static Codec>> makeBiomeCodecRW(Registry iregistry) { ++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS)); ++ } ++ // CraftBukkit end ++ + public static SerializableChunkData copyOf(ServerLevel level, ChunkAccess chunk) { + if (!chunk.canBeSerialized()) { + throw new IllegalArgumentException("Chunk can't be serialized: " + chunk); +@@ -428,6 +_,12 @@ + CompoundTag compoundTag = packStructureData( + StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences() + ); ++ // CraftBukkit start - store chunk persistent data in nbt ++ CompoundTag persistentDataContainer = null; ++ if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. ++ persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); ++ } ++ // CraftBukkit end + return new SerializableChunkData( + level.registryAccess().lookupOrThrow(Registries.BIOME), + pos, +@@ -447,6 +_,7 @@ + list2, + list1, + compoundTag ++ , persistentDataContainer // CraftBukkit - persistentDataContainer + ); + } + } +@@ -525,6 +_,11 @@ + this.heightmaps.forEach((types, longs) -> compoundTag2.put(types.getSerializationKey(), new LongArrayTag(longs))); + compoundTag.put("Heightmaps", compoundTag2); + compoundTag.put("structures", this.structureData); ++ // CraftBukkit start - store chunk persistent data in nbt ++ if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. ++ compoundTag.put("ChunkBukkitValues", this.persistentDataContainer); ++ } ++ // CraftBukkit end + return compoundTag; + } + +@@ -562,6 +_,13 @@ + chunk.setBlockEntityNbt(compoundTag); + } else { + BlockPos posFromTag = BlockEntity.getPosFromTag(compoundTag); ++ // Paper start - do not read tile entities positioned outside the chunk ++ ChunkPos chunkPos = chunk.getPos(); ++ if ((posFromTag.getX() >> 4) != chunkPos.x || (posFromTag.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + level.getWorld().getName() + "' positioned at " + posFromTag + " is located outside of the chunk"); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + BlockEntity blockEntity = BlockEntity.loadStatic(posFromTag, chunk.getBlockState(posFromTag), compoundTag, level.registryAccess()); + if (blockEntity != null) { + chunk.setBlockEntity(blockEntity); +@@ -610,6 +_,12 @@ + } else { + StructureStart structureStart = StructureStart.loadStaticStart(context, compound.getCompound(string), seed); + if (structureStart != null) { ++ // CraftBukkit start - load persistent data for structure start ++ net.minecraft.nbt.Tag persistentBase = compound.getCompound(string).get("StructureBukkitValues"); ++ if (persistentBase instanceof CompoundTag) { ++ structureStart.persistentDataContainer.putAll((CompoundTag) persistentBase); ++ } ++ // CraftBukkit end + map.put(structure, structureStart); + } + } diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch deleted file mode 100644 index b82d242d2c..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch +++ /dev/null @@ -1,158 +0,0 @@ ---- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -15,10 +15,16 @@ - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.NbtUtils; - import net.minecraft.resources.ResourceKey; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.util.datafix.DataFixTypes; - import net.minecraft.world.level.ChunkPos; --import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.chunk.ChunkGenerator; -+// CraftBukkit start -+import java.util.concurrent.ExecutionException; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import net.minecraft.world.level.dimension.LevelStem; - import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler; - import net.minecraft.world.level.storage.DimensionDataStorage; - -@@ -39,27 +45,86 @@ - return this.worker.isOldChunkAround(chunkPos, checkRadius); - } - -- public CompoundTag upgradeChunkTag(ResourceKey worldKey, Supplier persistentStateManagerFactory, CompoundTag nbt, Optional>> generatorCodecKey) { -- int i = ChunkStorage.getVersion(nbt); -+ // CraftBukkit start -+ private boolean check(ServerChunkCache cps, int x, int z) { -+ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" -+ ChunkPos pos = new ChunkPos(x, z); -+ if (cps != null) { -+ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); -+ if (cps.hasChunk(x, z)) { -+ return true; -+ } -+ } - -+ CompoundTag nbt; -+ try { -+ nbt = this.read(pos).get().orElse(null); -+ } catch (InterruptedException | ExecutionException ex) { -+ throw new RuntimeException(ex); -+ } -+ if (nbt != null) { -+ CompoundTag level = nbt.getCompound("Level"); -+ if (level.getBoolean("TerrainPopulated")) { -+ return true; -+ } -+ -+ ChunkStatus status = ChunkStatus.byName(level.getString("Status")); -+ if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { -+ // CraftBukkit end -+ int i = ChunkStorage.getVersion(nbttagcompound); -+ - if (i == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { -- return nbt; -+ return nbttagcompound; - } else { - try { -+ // CraftBukkit start -+ if (i < 1466) { -+ CompoundTag level = nbttagcompound.getCompound("Level"); -+ if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { -+ ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); -+ if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) { -+ level.putBoolean("LightPopulated", true); -+ } -+ } -+ } -+ // CraftBukkit end -+ - if (i < 1493) { -- nbt = DataFixTypes.CHUNK.update(this.fixerUpper, nbt, i, 1493); -- if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) { -- LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(worldKey, persistentStateManagerFactory); -+ nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493); -+ if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { -+ LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); - -- nbt = persistentstructurelegacy.updateFromLegacy(nbt); -+ nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); - } - } - -- ChunkStorage.injectDatafixingContext(nbt, worldKey, generatorCodecKey); -- nbt = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbt, Math.max(1493, i)); -- ChunkStorage.removeDatafixingContext(nbt); -- NbtUtils.addCurrentDataVersion(nbt); -- return nbt; -+ // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty -+ boolean stopBelowZero = false; -+ boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks; -+ -+ if (i <= 2730 && !belowZeroGenerationInExistingChunks) { -+ stopBelowZero = "full".equals(nbttagcompound.getCompound("Level").getString("Status")); -+ } -+ // Spigot end -+ -+ ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); -+ nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i)); -+ // Spigot start -+ if (stopBelowZero) { -+ nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString()); -+ } -+ // Spigot end -+ ChunkStorage.removeDatafixingContext(nbttagcompound); -+ NbtUtils.addCurrentDataVersion(nbttagcompound); -+ return nbttagcompound; - } catch (Exception exception) { - CrashReport crashreport = CrashReport.forThrowable(exception, "Updated chunk"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Updated chunk details"); -@@ -70,7 +135,7 @@ - } - } - -- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey worldKey, Supplier stateManagerGetter) { -+ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey worldKey, Supplier stateManagerGetter) { // CraftBukkit - LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; - - if (persistentstructurelegacy == null) { -@@ -85,7 +150,7 @@ - return persistentstructurelegacy; - } - -- public static void injectDatafixingContext(CompoundTag nbt, ResourceKey worldKey, Optional>> generatorCodecKey) { -+ public static void injectDatafixingContext(CompoundTag nbt, ResourceKey worldKey, Optional>> generatorCodecKey) { // CraftBukkit - CompoundTag nbttagcompound1 = new CompoundTag(); - - nbttagcompound1.putString("dimension", worldKey.location().toString()); -@@ -108,8 +173,19 @@ - } - - public CompletableFuture write(ChunkPos chunkPos, Supplier nbtSupplier) { -+ // Paper start - guard against possible chunk pos desync -+ final Supplier guardedPosCheck = () -> { -+ CompoundTag nbt = nbtSupplier.get(); -+ if (nbt != null && !chunkPos.equals(SerializableChunkData.getChunkCoordinate(nbt))) { -+ final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null; -+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos -+ + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); -+ } -+ return nbt; -+ }; -+ // Paper end - guard against possible chunk pos desync - this.handleLegacyStructureIndex(chunkPos); -- return this.worker.store(chunkPos, nbtSupplier); -+ return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync - } - - protected void handleLegacyStructureIndex(ChunkPos chunkPos) { diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch deleted file mode 100644 index 3955a59294..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFile.java.patch +++ /dev/null @@ -1,127 +0,0 @@ ---- a/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -1,3 +1,4 @@ -+// mc-dev import - package net.minecraft.world.level.chunk.storage; - - import com.google.common.annotations.VisibleForTesting; -@@ -49,7 +50,7 @@ - protected final RegionBitmap usedSectors; - - public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { -- this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync); -+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format - } - - public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { -@@ -63,8 +64,8 @@ - } else { - this.externalFileDir = directory; - this.offsets = this.header.asIntBuffer(); -- this.offsets.limit(1024); -- this.header.position(4096); -+ ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error -+ ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error - this.timestamps = this.header.asIntBuffer(); - if (dsync) { - this.file = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.DSYNC); -@@ -73,7 +74,7 @@ - } - - this.usedSectors.force(0, 2); -- this.header.position(0); -+ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error - int i = this.file.read(this.header, 0L); - - if (i != -1) { -@@ -89,6 +90,14 @@ - if (l != 0) { - int i1 = RegionFile.getSectorNumber(l); - int j1 = RegionFile.getNumSectors(l); -+ // Spigot start -+ if (j1 == 255) { -+ // We're maxed out, so we need to read the proper length from the section -+ ByteBuffer realLen = ByteBuffer.allocate(4); -+ this.file.read(realLen, i1 * 4096); -+ j1 = (realLen.getInt(0) + 4) / 4096 + 1; -+ } -+ // Spigot end - - if (i1 < 2) { - RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1}); -@@ -128,11 +137,18 @@ - } else { - int j = RegionFile.getSectorNumber(i); - int k = RegionFile.getNumSectors(i); -+ // Spigot start -+ if (k == 255) { -+ ByteBuffer realLen = ByteBuffer.allocate(4); -+ this.file.read(realLen, j * 4096); -+ k = (realLen.getInt(0) + 4) / 4096 + 1; -+ } -+ // Spigot end - int l = k * 4096; - ByteBuffer bytebuffer = ByteBuffer.allocate(l); - - this.file.read(bytebuffer, (long) (j * 4096)); -- bytebuffer.flip(); -+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - if (bytebuffer.remaining() < 5) { - RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); - return null; -@@ -246,7 +262,7 @@ - - try { - this.file.read(bytebuffer, (long) (j * 4096)); -- bytebuffer.flip(); -+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - if (bytebuffer.remaining() != 5) { - return false; - } else { -@@ -280,6 +296,7 @@ - return true; - } - } catch (IOException ioexception) { -+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper - ServerExceptionEvent - return false; - } - } -@@ -349,7 +366,7 @@ - - bytebuffer.putInt(1); - bytebuffer.put((byte) (this.version.getId() | 128)); -- bytebuffer.flip(); -+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - return bytebuffer; - } - -@@ -358,9 +375,10 @@ - FileChannel filechannel = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.WRITE); - - try { -- buf.position(5); -+ ((java.nio.Buffer) buf).position(5); // CraftBukkit - decompile error - filechannel.write(buf); - } catch (Throwable throwable) { -+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper - ServerExceptionEvent - if (filechannel != null) { - try { - filechannel.close(); -@@ -382,7 +400,7 @@ - } - - private void writeHeader() throws IOException { -- this.header.position(0); -+ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error - this.file.write(this.header, 0L); - } - -@@ -418,7 +436,7 @@ - if (i != j) { - ByteBuffer bytebuffer = RegionFile.PADDING_BUFFER.duplicate(); - -- bytebuffer.position(0); -+ ((java.nio.Buffer) bytebuffer).position(0); // CraftBukkit - decompile error - this.file.write(bytebuffer, (long) (j - 1)); - } - diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch deleted file mode 100644 index 4e4fd07bda..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch +++ /dev/null @@ -1,67 +0,0 @@ ---- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -32,21 +32,22 @@ - this.info = storageKey; - } - -- private RegionFile getRegionFile(ChunkPos pos) throws IOException { -- long i = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); -+ private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit -+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); - RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); - - if (regionfile != null) { - return regionfile; - } else { -- if (this.regionCache.size() >= 256) { -+ if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable - ((RegionFile) this.regionCache.removeLast()).close(); - } - - FileUtil.createDirectoriesSafe(this.folder); - Path path = this.folder; -- int j = pos.getRegionX(); -- Path path1 = path.resolve("r." + j + "." + pos.getRegionZ() + ".mca"); -+ int j = chunkcoordintpair.getRegionX(); -+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -+ if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit - RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync); - - this.regionCache.putAndMoveToFirst(i, regionfile1); -@@ -56,7 +57,12 @@ - - @Nullable - public CompoundTag read(ChunkPos pos) throws IOException { -- RegionFile regionfile = this.getRegionFile(pos); -+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -+ RegionFile regionfile = this.getRegionFile(pos, true); -+ if (regionfile == null) { -+ return null; -+ } -+ // CraftBukkit end - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); - - CompoundTag nbttagcompound; -@@ -96,7 +102,12 @@ - } - - public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { -- RegionFile regionfile = this.getRegionFile(chunkPos); -+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -+ RegionFile regionfile = this.getRegionFile(chunkPos, true); -+ if (regionfile == null) { -+ return; -+ } -+ // CraftBukkit end - DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos); - - try { -@@ -122,7 +133,7 @@ - } - - protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { -- RegionFile regionfile = this.getRegionFile(pos); -+ RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit - - if (nbt == null) { - regionfile.clear(pos); diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch deleted file mode 100644 index 15c1561f05..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch +++ /dev/null @@ -1,216 +0,0 @@ ---- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -@@ -76,7 +76,8 @@ - import net.minecraft.world.ticks.SavedTick; - import org.slf4j.Logger; - --public record SerializableChunkData(Registry biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List sectionData, List entities, List blockEntities, CompoundTag structureData) { -+// CraftBukkit - persistentDataContainer -+public record SerializableChunkData(Registry biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List sectionData, List entities, List blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) { - - public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -90,13 +91,39 @@ - public static final String SECTIONS_TAG = "sections"; - public static final String BLOCK_LIGHT_TAG = "BlockLight"; - public static final String SKY_LIGHT_TAG = "SkyLight"; -+ // 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 - -+ // 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 -+ - @Nullable - public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) { - if (!nbt.contains("Status", 8)) { - return null; - } else { -- ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); -+ // 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 chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - long i = nbt.getLong("LastUpdate"); - long j = nbt.getLong("InhabitedTime"); - ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status")); -@@ -110,7 +137,7 @@ - dataresult = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("blending_data")); - logger = SerializableChunkData.LOGGER; - Objects.requireNonNull(logger); -- blendingdata_d = (BlendingData.Packed) dataresult.resultOrPartial(logger::error).orElse((Object) null); -+ blendingdata_d = (BlendingData.Packed) ((DataResult) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error - } else { - blendingdata_d = null; - } -@@ -121,7 +148,7 @@ - dataresult = BelowZeroRetrogen.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen")); - logger = SerializableChunkData.LOGGER; - Objects.requireNonNull(logger); -- belowzeroretrogen = (BelowZeroRetrogen) dataresult.resultOrPartial(logger::error).orElse((Object) null); -+ belowzeroretrogen = (BelowZeroRetrogen) ((DataResult) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error - } else { - belowzeroretrogen = null; - } -@@ -178,7 +205,7 @@ - ListTag nbttaglist2 = nbt.getList("sections", 10); - List list4 = new ArrayList(nbttaglist2.size()); - Registry iregistry = registryManager.lookupOrThrow(Registries.BIOME); -- Codec>> codec = makeBiomeCodec(iregistry); -+ Codec>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write - - for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) { - CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1); -@@ -196,17 +223,17 @@ - datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); - } - -- Object object; -+ PalettedContainer object; // CraftBukkit - read/write - - if (nbttagcompound3.contains("biomes", 10)) { -- object = (PalettedContainerRO) codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { -+ object = codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { // CraftBukkit - read/write - logErrors(chunkcoordintpair, b0, s1); - }).getOrThrow(SerializableChunkData.ChunkReadException::new); - } else { - object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); - } - -- chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainerRO) object); -+ chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write - } else { - chunksection = null; - } -@@ -217,7 +244,8 @@ - list4.add(new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1)); - } - -- return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2); -+ // CraftBukkit - ChunkBukkitValues -+ return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2, nbt.get("ChunkBukkitValues")); - } - } - -@@ -287,7 +315,13 @@ - if (this.chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) { - protochunk.setLightEngine(levellightengine); - } -+ } -+ -+ // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading. -+ if (this.persistentDataContainer instanceof CompoundTag) { -+ ((ChunkAccess) object).persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer); - } -+ // CraftBukkit end - - ((ChunkAccess) object).setLightCorrect(this.lightCorrect); - EnumSet enumset = EnumSet.noneOf(Heightmap.Types.class); -@@ -329,6 +363,13 @@ - - while (iterator2.hasNext()) { - nbttagcompound = (CompoundTag) iterator2.next(); -+ // Paper start - do not read tile entities positioned outside the chunk -+ final BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); -+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { -+ LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", chunkPos, world.getWorld().getName(), blockposition); -+ continue; -+ } -+ // Paper end - do not read tile entities positioned outside the chunk - protochunk1.setBlockEntityNbt(nbttagcompound); - } - -@@ -348,6 +389,12 @@ - return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS)); - } - -+ // CraftBukkit start - read/write -+ private static Codec>> makeBiomeCodecRW(Registry iregistry) { -+ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS)); -+ } -+ // CraftBukkit end -+ - public static SerializableChunkData copyOf(ServerLevel world, ChunkAccess chunk) { - if (!chunk.canBeSerialized()) { - throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk)); -@@ -419,7 +466,14 @@ - }); - CompoundTag nbttagcompound1 = packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()); - -- return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1); -+ // CraftBukkit start - store chunk persistent data in nbt -+ CompoundTag persistentDataContainer = null; -+ if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. -+ persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); -+ } -+ -+ return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1, persistentDataContainer); -+ // CraftBukkit end - } - } - -@@ -432,7 +486,7 @@ - nbttagcompound.putLong("LastUpdate", this.lastUpdateTime); - nbttagcompound.putLong("InhabitedTime", this.inhabitedTime); - nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString()); -- DataResult dataresult; -+ DataResult dataresult; // CraftBukkit - decompile error - Logger logger; - - if (this.blendingData != null) { -@@ -513,6 +567,11 @@ - }); - nbttagcompound.put("Heightmaps", nbttagcompound2); - nbttagcompound.put("structures", this.structureData); -+ // CraftBukkit start - store chunk persistent data in nbt -+ if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading. -+ nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer); -+ } -+ // CraftBukkit end - return nbttagcompound; - } - -@@ -564,6 +623,13 @@ - chunk.setBlockEntityNbt(nbttagcompound); - } else { - BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); -+ // Paper start - do not read tile entities positioned outside the chunk -+ 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 - BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound, world.registryAccess()); - - if (tileentity != null) { -@@ -623,6 +689,12 @@ - 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); - } - }