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

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -61,6 +_,15 @@
private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
+ // Paper start - Configurable region compression format
+ public static RegionFileVersion getCompressionFormat() {
+ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) {
+ case GZIP -> VERSION_GZIP;
+ case ZLIB -> VERSION_DEFLATE;
+ case NONE -> VERSION_NONE;
+ };
+ }
+ // Paper end - Configurable region compression format
private RegionFileVersion(
int id,
@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);
}
}