net.minecraft.world.level.chunk.storage

This commit is contained in:
Jake Potrebic 2024-12-14 14:36:48 -08:00
parent 1dd7ab9203
commit 2546348b9d
No known key found for this signature in database
GPG key ID: ECE0B3C133C016C5
9 changed files with 461 additions and 570 deletions

View file

@ -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<Level> levelKey,
+ ResourceKey<LevelStem> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
- Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
+ Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> 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> level, Supplier<DimensionDataStorage> storage) {
+ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
LegacyStructureDataHandler legacyStructureDataHandler = this.legacyStructureHandler;
if (legacyStructureDataHandler == null) {
synchronized (this) {
@@ -86,7 +_,7 @@
}
public static void injectDatafixingContext(
- CompoundTag chunkData, ResourceKey<Level> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey
+ CompoundTag chunkData, ResourceKey<LevelStem> levelKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey // CraftBukkit
) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putString("dimension", levelKey.location().toString());
@@ -107,8 +_,19 @@
}
public CompletableFuture<Void> write(ChunkPos pos, Supplier<CompoundTag> tagSupplier) {
+ // Paper start - guard against possible chunk pos desync
+ final Supplier<CompoundTag> 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) {

View file

@ -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);
}

View file

@ -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 {

View file

@ -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<InputStream> inputWrapper;
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
@ -15,4 +15,4 @@
+ // Paper end - Configurable region compression format
private RegionFileVersion(
int id,
@Nullable String name,
@Nullable String optionName,

View file

@ -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<CompoundTag> entities,
List<CompoundTag> blockEntities,
CompoundTag structureData
+ , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer
) {
public static final Codec<PalettedContainer<BlockState>> 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<SerializableChunkData.SectionData> list8 = new ArrayList<>(list7.size());
Registry<Biome> registry = registries.lookupOrThrow(Registries.BIOME);
- Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
+ Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(registry); // CraftBukkit - read/write
for (int i2 = 0; i2 < list7.size(); i2++) {
CompoundTag compound2 = list7.getCompound(i2);
@@ -199,7 +_,7 @@
);
}
- PalettedContainerRO<Holder<Biome>> palettedContainerRo;
+ PalettedContainer<Holder<Biome>> 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<Heightmap.Types> 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<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> 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);
}
}

View file

@ -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<Level> worldKey, Supplier<DimensionDataStorage> persistentStateManagerFactory, CompoundTag nbt, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> 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<LevelStem> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag nbttagcompound, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> 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<Level> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) {
+ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) { // CraftBukkit
LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
if (persistentstructurelegacy == null) {
@@ -85,7 +150,7 @@
return persistentstructurelegacy;
}
- public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<Level> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) {
+ public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<LevelStem> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) { // CraftBukkit
CompoundTag nbttagcompound1 = new CompoundTag();
nbttagcompound1.putString("dimension", worldKey.location().toString());
@@ -108,8 +173,19 @@
}
public CompletableFuture<Void> write(ChunkPos chunkPos, Supplier<CompoundTag> nbtSupplier) {
+ // Paper start - guard against possible chunk pos desync
+ final Supplier<CompoundTag> 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) {

View file

@ -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));
}

View file

@ -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);

View file

@ -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<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData) {
+// CraftBukkit - persistentDataContainer
+public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) {
public static final Codec<PalettedContainer<BlockState>> 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<BlendingData.Packed>) 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<BelowZeroRetrogen>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
} else {
belowzeroretrogen = null;
}
@@ -178,7 +205,7 @@
ListTag nbttaglist2 = nbt.getList("sections", 10);
List<SerializableChunkData.SectionData> list4 = new ArrayList(nbttaglist2.size());
Registry<Biome> iregistry = registryManager.lookupOrThrow(Registries.BIOME);
- Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(iregistry);
+ Codec<PalettedContainer<Holder<Biome>>> 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<Heightmap.Types> 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<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> 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<Tag> 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);
}
}