From bb439800f3307b5dfa021ba247682a618320b021 Mon Sep 17 00:00:00 2001 From: kyngs <38181667+kyngs@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:50:34 +0100 Subject: [PATCH] v12 SRF (per-chunk PDC support, no double-compression); fix bad bug causing chunks go poof. (#91) * Add v12, chunk pdc and extra nbt. Fix double compression on tile entities and entities. Fix horrible bug which made chunks go poof. * Quick rebase on main * Fix entity loading bug. (Authored by @AverageGithub) * Fix entity loading bug. (Authored by @AverageGithub) * Fix a bug with the extra tag being empty. * Yeet debug logs * Fix crash. (Authored by @AverageGithub) * Update core/src/main/java/com/infernalsuite/aswm/serialization/anvil/AnvilWorldReader.java Co-authored-by: Paul * Paul doesn't like var :( * Review changes --------- Co-authored-by: kyngs Co-authored-by: Paul --- SLIME_FORMAT | 17 +- .../aswm/api/utils/SlimeFormat.java | 2 +- .../aswm/api/world/SlimeChunk.java | 13 ++ .../serialization/anvil/AnvilWorldReader.java | 6 +- .../serialization/slime/SlimeSerializer.java | 25 ++- .../reader/SlimeWorldReaderRegistry.java | 2 + .../impl/v10/v10SlimeWorldDeSerializer.java | 2 +- .../impl/v11/v11SlimeWorldDeSerializer.java | 2 +- .../impl/v12/v12SlimeWorldDeSerializer.java | 172 +++++++++++++++ .../slime/reader/impl/v12/v12WorldFormat.java | 10 + .../reader/impl/v19/v1_9SlimeConverter.java | 5 +- .../aswm/skeleton/SkeletonCloning.java | 3 +- .../aswm/skeleton/SlimeChunkSkeleton.java | 9 +- patches/server/0008-Fix-entity-loading.patch | 85 ++++++-- .../server/0009-Remove-catch-throwable.patch | 4 +- ...c-and-extra-nbt.-Fix-double-compress.patch | 200 ++++++++++++++++++ 16 files changed, 519 insertions(+), 38 deletions(-) create mode 100644 core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java create mode 100644 core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12WorldFormat.java create mode 100644 patches/server/0012-Add-v12-chunk-pdc-and-extra-nbt.-Fix-double-compress.patch diff --git a/SLIME_FORMAT b/SLIME_FORMAT index 4f1e4b6d..e01c6014 100644 --- a/SLIME_FORMAT +++ b/SLIME_FORMAT @@ -1,7 +1,7 @@ ------------------------------------- “Slime” file format 2 bytes - magic = 0xB10B -1 byte (ubyte) - version, current = 0x0B +1 byte (ubyte) - version, current = 0x0C 4 bytes (int) - world version (see version list below) 4 bytes (int) - compressed chunks size 4 bytes (int) - uncompressed chunks size @@ -10,7 +10,7 @@ 4 bytes (int) - compressed “extra” size 4 bytes (int) - uncompressed “extra” size -[depends] - compound tag compressed using zstd +[depends] - extra compound tag compressed using zstd (used for PDC, and/or custom data) ------------------------------------- Custom chunk format @@ -31,19 +31,17 @@ Custom chunk format 4 bytes (int) - heightmaps size same format as mc, uncompressed -4 bytes (int) - compressed tile entities size -4 bytes (int) - uncompressed tile entities size +4 bytes (int) - tile entities size Same format as mc inside an nbt list named “tiles”, in a global compound, no gzip anywhere - compressed using zstd -4 bytes (int) compressed entities size -4 bytes (int) uncompressed entities size + uncompressed +4 bytes (int) entities size Same format as mc EXCEPT optional “CustomId” inside an nbt list named “entities”, in a global compound Compressed using zstd - +[depends] - compound tag uncompressed (used for PDC, and/or custom data) ------------------------------------- World version list: @@ -68,4 +66,5 @@ Version history: - v8: Variable biomes size - v9: Fix issue with biomes size, causing old worlds to be corrupted - v10: Use minecraft version id, remove legacy version artifacts - - v11: Move entities and tile entities into the chunk structure \ No newline at end of file + - v11: Move entities and tile entities into the chunk structure + - v12: Add support for chunk-based PDC \ No newline at end of file diff --git a/api/src/main/java/com/infernalsuite/aswm/api/utils/SlimeFormat.java b/api/src/main/java/com/infernalsuite/aswm/api/utils/SlimeFormat.java index aed87909..d41e4477 100644 --- a/api/src/main/java/com/infernalsuite/aswm/api/utils/SlimeFormat.java +++ b/api/src/main/java/com/infernalsuite/aswm/api/utils/SlimeFormat.java @@ -9,5 +9,5 @@ public class SlimeFormat { public static final byte[] SLIME_HEADER = new byte[] { -79, 11 }; /** Latest version of the SRF that SWM supports **/ - public static final byte SLIME_VERSION = 11; + public static final byte SLIME_VERSION = 12; } diff --git a/api/src/main/java/com/infernalsuite/aswm/api/world/SlimeChunk.java b/api/src/main/java/com/infernalsuite/aswm/api/world/SlimeChunk.java index 8219d0fa..3c9f0a4f 100644 --- a/api/src/main/java/com/infernalsuite/aswm/api/world/SlimeChunk.java +++ b/api/src/main/java/com/infernalsuite/aswm/api/world/SlimeChunk.java @@ -53,4 +53,17 @@ public interface SlimeChunk { */ List getEntities(); + /** + * Returns the extra data of the chunk. + * Inside this {@link CompoundTag} + * can be stored any information to then be retrieved later, as it's + * saved alongside the chunk data. + *
+ * Beware, a compound tag under the key "ChunkBukkitValues" will be stored here. + * It is used for storing chunk-based Bukkit PDC. Do not overwrite it. + * + * @return A {@link CompoundTag} containing the extra data of the chunk, + */ + CompoundTag getExtraData(); + } diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/anvil/AnvilWorldReader.java b/core/src/main/java/com/infernalsuite/aswm/serialization/anvil/AnvilWorldReader.java index 2412f625..468ae105 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/anvil/AnvilWorldReader.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/anvil/AnvilWorldReader.java @@ -381,9 +381,13 @@ private static SlimeChunk readChunk(CompoundTag compound, int worldVersion) { sectionArray[index - minSectionY] = new SlimeChunkSectionSkeleton(/*paletteTag, blockStatesArray,*/ blockStatesTag, biomeTag, blockLightArray, skyLightArray); } + CompoundTag extraTag = new CompoundTag("", new CompoundMap()); + + extraTag.getValue().put(compound.getValue().get("ChunkBukkitValues")); // Attempt to Migrate PDC from Anvil format to Slime format. + for (SlimeChunkSection section : sectionArray) { if (section != null) { // Chunk isn't empty - return new SlimeChunkSkeleton(chunkX, chunkZ, sectionArray, heightMapsCompound, tileEntities, entities); + return new SlimeChunkSkeleton(chunkX, chunkZ, sectionArray, heightMapsCompound, tileEntities, entities, extraTag); } } diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/SlimeSerializer.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/SlimeSerializer.java index 37b1bd95..3b3e8b7d 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/SlimeSerializer.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/SlimeSerializer.java @@ -12,6 +12,8 @@ import com.infernalsuite.aswm.api.world.SlimeChunkSection; import com.infernalsuite.aswm.api.world.SlimeWorld; import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -21,6 +23,8 @@ public class SlimeSerializer { + private static final Logger LOGGER = LoggerFactory.getLogger(SlimeSerializer.class); + public static byte[] serialize(SlimeWorld world) { CompoundTag extraData = world.getExtraData(); SlimePropertyMap propertyMap = world.getPropertyMap(); @@ -78,7 +82,7 @@ static byte[] serializeChunks(SlimeWorld world, Collection chunks) t if (!ChunkPruner.canBePruned(world, chunk)) { emptyChunks.add(chunk); } else { - System.out.println("PRUNED: " + chunk); + LOGGER.info("PRUNED: " + chunk); } } @@ -127,21 +131,28 @@ static byte[] serializeChunks(SlimeWorld world, Collection chunks) t ListTag tileEntitiesNbtList = new ListTag<>("tileEntities", TagType.TAG_COMPOUND, chunk.getTileEntities()); CompoundTag tileEntitiesCompound = new CompoundTag("", new CompoundMap(Collections.singletonList(tileEntitiesNbtList))); byte[] tileEntitiesData = serializeCompoundTag(tileEntitiesCompound); - byte[] compressedTileEntitiesData = Zstd.compress(tileEntitiesData); - outStream.writeInt(compressedTileEntitiesData.length); outStream.writeInt(tileEntitiesData.length); - outStream.write(compressedTileEntitiesData); + outStream.write(tileEntitiesData); // Entities ListTag entitiesNbtList = new ListTag<>("entities", TagType.TAG_COMPOUND, chunk.getEntities()); CompoundTag entitiesCompound = new CompoundTag("", new CompoundMap(Collections.singletonList(entitiesNbtList))); byte[] entitiesData = serializeCompoundTag(entitiesCompound); - byte[] compressedEntitiesData = Zstd.compress(entitiesData); - outStream.writeInt(compressedEntitiesData.length); outStream.writeInt(entitiesData.length); - outStream.write(compressedEntitiesData); + outStream.write(entitiesData); + + // Extra Tag + { + if (chunk.getExtraData() == null) { + LOGGER.warn("Chunk at " + chunk.getX() + ", " + chunk.getZ() + " from world " + world.getName() + " has no extra data! When deserialized, this chunk will have an empty extra data tag!"); + } + byte[] extra = serializeCompoundTag(chunk.getExtraData()); + + outStream.writeInt(extra.length); + outStream.write(extra); + } } return outByteStream.toByteArray(); diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/SlimeWorldReaderRegistry.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/SlimeWorldReaderRegistry.java index 757b7cbd..02610c37 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/SlimeWorldReaderRegistry.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/SlimeWorldReaderRegistry.java @@ -7,6 +7,7 @@ import com.infernalsuite.aswm.api.world.SlimeWorld; import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; import com.infernalsuite.aswm.serialization.slime.reader.impl.v11.v11WorldFormat; +import com.infernalsuite.aswm.serialization.slime.reader.impl.v12.v12WorldFormat; import com.infernalsuite.aswm.serialization.slime.reader.impl.v19.v1_9WorldFormat; import com.infernalsuite.aswm.serialization.slime.reader.impl.v10.v10WorldFormat; @@ -25,6 +26,7 @@ public class SlimeWorldReaderRegistry { register(v1_9WorldFormat.FORMAT, 1, 2, 3, 4, 5, 6, 7, 8, 9); register(v10WorldFormat.FORMAT, 10); register(v11WorldFormat.FORMAT, 11); + register(v12WorldFormat.FORMAT, 12); } private static void register(VersionedByteSlimeWorldReader format, int... bytes) { diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java index 2f10dc40..b5e5c1be 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java @@ -167,7 +167,7 @@ private static Map readChunks(SlimePropertyMap slimeProper } chunkMap.put(new ChunkPos(x, z), - new SlimeChunkSkeleton(x, z, chunkSectionArray, heightMaps, new ArrayList<>(), new ArrayList<>()) + new SlimeChunkSkeleton(x, z, chunkSectionArray, heightMaps, new ArrayList<>(), new ArrayList<>(), new CompoundTag("", new CompoundMap())) ); } } diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java index 6f23e744..347f28c3 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java @@ -139,7 +139,7 @@ private static Map readChunks(SlimePropertyMap slimeProper List serializedEntities = ((ListTag) entitiesCompound.getValue().get("entities")).getValue(); chunkMap.put(new ChunkPos(x, z), - new SlimeChunkSkeleton(x, z, chunkSections, heightMaps, serializedTileEntities, serializedEntities)); + new SlimeChunkSkeleton(x, z, chunkSections, heightMaps, serializedTileEntities, serializedEntities, new CompoundTag("", new CompoundMap()))); } return chunkMap; } diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java new file mode 100644 index 00000000..e9a29054 --- /dev/null +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java @@ -0,0 +1,172 @@ +package com.infernalsuite.aswm.serialization.slime.reader.impl.v12; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.ListTag; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.github.luben.zstd.Zstd; +import com.infernalsuite.aswm.ChunkPos; +import com.infernalsuite.aswm.api.exceptions.CorruptedWorldException; +import com.infernalsuite.aswm.api.exceptions.NewerFormatException; +import com.infernalsuite.aswm.api.loaders.SlimeLoader; +import com.infernalsuite.aswm.api.utils.NibbleArray; +import com.infernalsuite.aswm.api.world.SlimeChunk; +import com.infernalsuite.aswm.api.world.SlimeChunkSection; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.api.world.properties.SlimeProperties; +import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap; +import com.infernalsuite.aswm.serialization.slime.reader.VersionedByteSlimeWorldReader; +import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; +import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton; +import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class v12SlimeWorldDeSerializer implements VersionedByteSlimeWorldReader { + + public static final int ARRAY_SIZE = 16 * 16 * 16 / (8 / 4); + + @Override + public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, String worldName, DataInputStream dataStream, SlimePropertyMap propertyMap, boolean readOnly) throws IOException, CorruptedWorldException, NewerFormatException { + int worldVersion = dataStream.readInt(); + + byte[] chunkBytes = readCompressed(dataStream); + Map chunks = readChunks(propertyMap, chunkBytes); + + byte[] extraTagBytes = readCompressed(dataStream); + CompoundTag extraTag = readCompound(extraTagBytes); + + SlimePropertyMap worldPropertyMap = propertyMap; + Optional propertiesMap = extraTag + .getAsCompoundTag("properties") + .map(CompoundTag::getValue); + + if (propertiesMap.isPresent()) { + worldPropertyMap = new SlimePropertyMap(propertiesMap.get()); + worldPropertyMap.merge(propertyMap); + } + + return new SkeletonSlimeWorld(worldName, loader, readOnly, chunks, extraTag, worldPropertyMap, worldVersion); + } + + private static Map readChunks(SlimePropertyMap slimePropertyMap, byte[] chunkBytes) throws IOException { + Map chunkMap = new HashMap<>(); + DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(chunkBytes)); + + int chunks = chunkData.readInt(); + for (int i = 0; i < chunks; i++) { + // ChunkPos + int x = chunkData.readInt(); + int z = chunkData.readInt(); + + // Sections + int sectionAmount = slimePropertyMap.getValue(SlimeProperties.CHUNK_SECTION_MAX) - slimePropertyMap.getValue(SlimeProperties.CHUNK_SECTION_MIN) + 1; + SlimeChunkSection[] chunkSections = new SlimeChunkSection[sectionAmount]; + + int sectionCount = chunkData.readInt(); + for (int sectionId = 0; sectionId < sectionCount; sectionId++) { + + // Block Light Nibble Array + NibbleArray blockLightArray; + if (chunkData.readBoolean()) { + byte[] blockLightByteArray = new byte[ARRAY_SIZE]; + chunkData.read(blockLightByteArray); + blockLightArray = new NibbleArray(blockLightByteArray); + } else { + blockLightArray = null; + } + + // Sky Light Nibble Array + NibbleArray skyLightArray; + if (chunkData.readBoolean()) { + byte[] skyLightByteArray = new byte[ARRAY_SIZE]; + chunkData.read(skyLightByteArray); + skyLightArray = new NibbleArray(skyLightByteArray); + } else { + skyLightArray = null; + } + + // Block Data + byte[] blockStateData = new byte[chunkData.readInt()]; + chunkData.read(blockStateData); + CompoundTag blockStateTag = readCompound(blockStateData); + + // Biome Data + byte[] biomeData = new byte[chunkData.readInt()]; + chunkData.read(biomeData); + CompoundTag biomeTag = readCompound(biomeData); + + chunkSections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); + } + + // HeightMaps + byte[] heightMapData = new byte[chunkData.readInt()]; + chunkData.read(heightMapData); + CompoundTag heightMaps = readCompound(heightMapData); + + // Tile Entities + + byte[] tileEntities = read(chunkData); + + CompoundTag tileEntitiesCompound = readCompound(tileEntities); + @SuppressWarnings("unchecked") + List serializedTileEntities = ((ListTag) tileEntitiesCompound.getValue().get("tileEntities")).getValue(); + + // Entities + + byte[] entities = read(chunkData); + + CompoundTag entitiesCompound = readCompound(entities); + @SuppressWarnings("unchecked") + List serializedEntities = ((ListTag) entitiesCompound.getValue().get("entities")).getValue(); + + + // Extra Tag + byte[] rawExtra = read(chunkData); + CompoundTag extra = readCompound(rawExtra); + // If the extra tag is empty, the serializer will save it as null. + // So if we deserialize a null extra tag, we will assume it was empty. + if (extra == null) { + extra = new CompoundTag("", new CompoundMap()); + } + + chunkMap.put(new ChunkPos(x, z), + new SlimeChunkSkeleton(x, z, chunkSections, heightMaps, serializedTileEntities, serializedEntities, extra)); + } + return chunkMap; + } + + private static byte[] readCompressed(DataInputStream stream) throws IOException { + int compressedLength = stream.readInt(); + int decompressedLength = stream.readInt(); + byte[] compressedData = new byte[compressedLength]; + byte[] decompressedData = new byte[decompressedLength]; + stream.read(compressedData); + Zstd.decompress(decompressedData, compressedData); + return decompressedData; + } + + private static byte[] read(DataInputStream stream) throws IOException { + int length = stream.readInt(); + byte[] data = new byte[length]; + stream.read(data); + return data; + } + + private static CompoundTag readCompound(byte[] tagBytes) throws IOException { + if (tagBytes.length == 0) { + return null; + } + + NBTInputStream nbtStream = new NBTInputStream(new ByteArrayInputStream(tagBytes), NBTInputStream.NO_COMPRESSION, ByteOrder.BIG_ENDIAN); + return (CompoundTag) nbtStream.readTag(); + } +} diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12WorldFormat.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12WorldFormat.java new file mode 100644 index 00000000..8fdef840 --- /dev/null +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v12/v12WorldFormat.java @@ -0,0 +1,10 @@ +package com.infernalsuite.aswm.serialization.slime.reader.impl.v12; + +import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.infernalsuite.aswm.serialization.slime.reader.impl.SimpleWorldFormat; + +public interface v12WorldFormat { + + SimpleWorldFormat FORMAT = new SimpleWorldFormat<>(data -> data, new v12SlimeWorldDeSerializer()); + +} diff --git a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v19/v1_9SlimeConverter.java b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v19/v1_9SlimeConverter.java index 9d772208..bce59671 100644 --- a/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v19/v1_9SlimeConverter.java +++ b/core/src/main/java/com/infernalsuite/aswm/serialization/slime/reader/impl/v19/v1_9SlimeConverter.java @@ -1,5 +1,7 @@ package com.infernalsuite.aswm.serialization.slime.reader.impl.v19; +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; import com.infernalsuite.aswm.ChunkPos; import com.infernalsuite.aswm.serialization.SlimeWorldReader; import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld; @@ -61,7 +63,8 @@ public SlimeWorld readFromData(v1_9SlimeWorld data) { sections, slimeChunk.heightMap, slimeChunk.tileEntities, - slimeChunk.entities + slimeChunk.entities, + new CompoundTag("", new CompoundMap()) )); } diff --git a/core/src/main/java/com/infernalsuite/aswm/skeleton/SkeletonCloning.java b/core/src/main/java/com/infernalsuite/aswm/skeleton/SkeletonCloning.java index 8ba829b6..d8c2a990 100644 --- a/core/src/main/java/com/infernalsuite/aswm/skeleton/SkeletonCloning.java +++ b/core/src/main/java/com/infernalsuite/aswm/skeleton/SkeletonCloning.java @@ -71,7 +71,8 @@ private static Map cloneChunkStorage(Collection blockEntities, - List entities) implements SlimeChunk { + List entities, + CompoundTag extra) implements SlimeChunk { + @Override public int getX() { return this.x; @@ -39,4 +41,9 @@ public List getTileEntities() { public List getEntities() { return this.entities; } + + @Override + public CompoundTag getExtraData() { + return this.extra; + } } diff --git a/patches/server/0008-Fix-entity-loading.patch b/patches/server/0008-Fix-entity-loading.patch index 3cdad62d..4440a8a4 100644 --- a/patches/server/0008-Fix-entity-loading.patch +++ b/patches/server/0008-Fix-entity-loading.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Fix entity loading diff --git a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java -index c32d52c68188dc1eb7feeac364cdc4aded1c4574..e74d9edca5d5166502f39e617939e0c7038f200a 100644 +index c32d52c68188dc1eb7feeac364cdc4aded1c4574..8485296594c01edcaef271fc37bf2c70932f4986 100644 --- a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java +++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java @@ -1,12 +1,13 @@ @@ -40,7 +40,7 @@ index c32d52c68188dc1eb7feeac364cdc4aded1c4574..e74d9edca5d5166502f39e617939e0c7 } protected ChunkAccess runOnMain(final SlimeChunk data) { -@@ -73,6 +73,10 @@ public final class ChunkDataLoadTask implements CommonLoadTask { +@@ -73,8 +73,12 @@ public final class ChunkDataLoadTask implements CommonLoadTask { // } LevelChunk chunk = this.world.slimeInstance.promote(chunkX, chunkZ, data); @@ -49,10 +49,13 @@ index c32d52c68188dc1eb7feeac364cdc4aded1c4574..e74d9edca5d5166502f39e617939e0c7 + data.getEntities().stream().map(flowTag -> (CompoundTag) Converter.convertTag(flowTag)).forEach(protoChunk::addEntity); + } - return new ImposterProtoChunk(chunk, false); +- return new ImposterProtoChunk(chunk, false); ++ return protoChunk; } catch (final ThreadDeath death) { + throw death; + } catch (final Throwable thr2) { diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java -index 91a7f41db47c7df3ecc301e0827a1d07305f604e..276c48a4e9d10fd3124ce29586d3b2ef8c3a1727 100644 +index 91a7f41db47c7df3ecc301e0827a1d07305f604e..27af3aa6ba8ffb100a6b1b50ba584e65c4aee86a 100644 --- a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java +++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java @@ -15,6 +15,8 @@ import net.minecraft.core.Holder; @@ -64,17 +67,73 @@ index 91a7f41db47c7df3ecc301e0827a1d07305f604e..276c48a4e9d10fd3124ce29586d3b2ef import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biomes; -@@ -127,6 +129,13 @@ public class SlimeChunkConverter { - } - } +@@ -106,26 +108,11 @@ public class SlimeChunkConverter { + + + LevelChunk.PostLoadProcessor loadEntities = (nmsChunk) -> { ++ List entities = chunk.getEntities(); + +- // TODO +- // Load tile entities +- List tileEntities = chunk.getTileEntities(); +- +- if (tileEntities != null) { +- for (CompoundTag tag : tileEntities) { +- Optional type = tag.getStringValue("id"); +- +- // Sometimes null tile entities are saved +- if (type.isPresent()) { +- BlockPos blockPosition = new BlockPos(tag.getIntValue("x").get(), tag.getIntValue("y").get(), tag.getIntValue("z").get()); +- BlockState blockData = nmsChunk.getBlockState(blockPosition); +- BlockEntity entity = BlockEntity.loadStatic(blockPosition, blockData, (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)); +- +- if (entity != null) { +- nmsChunk.setBlockEntity(entity); +- } +- } +- } ++ if (entities != null) { ++ net.minecraft.server.level.ChunkMap.postLoadProtoChunk(instance, entities.stream() ++ .map(flowTag -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(flowTag)).toList(), nmsChunk.getPos()); } + }; + +@@ -133,6 +120,25 @@ public class SlimeChunkConverter { + LevelChunkTicks fluidLevelChunkTicks = new LevelChunkTicks<>(); + SlimeChunkLevel nmsChunk = new SlimeChunkLevel(instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, 0L, sections, loadEntities, null); + ++ List tileEntities = chunk.getTileEntities(); + -+ List entities = chunk.getEntities(); ++ if (tileEntities != null) { ++ for (CompoundTag tag : tileEntities) { ++ Optional type = tag.getStringValue("id"); + -+ if (entities != null) { -+ instance.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(entities.stream() -+ .map(flowTag -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(flowTag)).toList(), instance), nmsChunk.getPos()); ++ // Sometimes null tile entities are saved ++ if (type.isPresent()) { ++ BlockPos blockPosition = new BlockPos(tag.getIntValue("x").get(), tag.getIntValue("y").get(), tag.getIntValue("z").get()); ++ BlockState blockData = nmsChunk.getBlockState(blockPosition); ++ BlockEntity entity = BlockEntity.loadStatic(blockPosition, blockData, (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)); ++ ++ if (entity != null) { ++ nmsChunk.setBlockEntity(entity); ++ } ++ } + } - }; ++ } ++ + // Height Maps + EnumSet heightMapTypes = nmsChunk.getStatus().heightmapsAfter(); + CompoundMap heightMaps = chunk.getHeightMaps().getValue(); +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +index b66a7d4aab887309579154815a0d4abf9de506b0..1654d7ec66e5077810494992e3c56fb692301269 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +@@ -112,7 +112,7 @@ public final class NewChunkHolder { + } + + if (!transientChunk) { +- if (entityChunk != null) { ++ if (!(this.world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance) && entityChunk != null) { + final List entities = EntityStorage.readEntities(this.world, entityChunk); - LevelChunkTicks blockLevelChunkTicks = new LevelChunkTicks<>(); + this.world.getEntityLookup().addEntityChunkEntities(entities, new ChunkPos(this.chunkX, this.chunkZ)); diff --git a/patches/server/0009-Remove-catch-throwable.patch b/patches/server/0009-Remove-catch-throwable.patch index c4e726b0..89b3cf28 100644 --- a/patches/server/0009-Remove-catch-throwable.patch +++ b/patches/server/0009-Remove-catch-throwable.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Remove catch throwable diff --git a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java -index e74d9edca5d5166502f39e617939e0c7038f200a..65642a9bde0fffc6531604026dd957fd268b6642 100644 +index 8485296594c01edcaef271fc37bf2c70932f4986..f9ac1efca06d8debbb7894160c3e67fd23440ebb 100644 --- a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java +++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java @@ -48,7 +48,7 @@ public final class ChunkDataLoadTask implements CommonLoadTask { @@ -20,7 +20,7 @@ index e74d9edca5d5166502f39e617939e0c7038f200a..65642a9bde0fffc6531604026dd957fd @@ -79,10 +79,8 @@ public final class ChunkDataLoadTask implements CommonLoadTask { } - return new ImposterProtoChunk(chunk, false); + return protoChunk; - } catch (final ThreadDeath death) { - throw death; - } catch (final Throwable thr2) { diff --git a/patches/server/0012-Add-v12-chunk-pdc-and-extra-nbt.-Fix-double-compress.patch b/patches/server/0012-Add-v12-chunk-pdc-and-extra-nbt.-Fix-double-compress.patch new file mode 100644 index 00000000..4e8ca918 --- /dev/null +++ b/patches/server/0012-Add-v12-chunk-pdc-and-extra-nbt.-Fix-double-compress.patch @@ -0,0 +1,200 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kyngs +Date: Tue, 19 Dec 2023 21:47:15 +0100 +Subject: [PATCH] Add v12, chunk pdc and extra nbt. Fix double compression on + tile entities and entities. Fix horrible bug which made chunks go poof. + + +diff --git a/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java +index 1a4be97069f01a82deadd26a94e86dbebe0e47a0..ca4a80e7b5c73f9669a717adc46b2e9b8c1f48b6 100644 +--- a/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java ++++ b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java +@@ -70,11 +70,12 @@ class SimpleDataFixerConverter implements SlimeWorldReader { + + chunks.put(chunkPos, new SlimeChunkSkeleton( + chunk.getX(), +- chunk.getX(), ++ chunk.getZ(), + sections, + chunk.getHeightMaps(), + blockEntities, +- entities ++ entities, ++ chunk.getExtraData() + )); + + } +diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java +index f1db2fe121bb3aabfad727a8133b645524b8f19a..ad30e83670ca88f09fa7625fc52c224247410623 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java ++++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java +@@ -67,9 +67,11 @@ public class NMSSlimeChunk implements SlimeChunk { + } + + private LevelChunk chunk; ++ private CompoundTag extra; + +- public NMSSlimeChunk(LevelChunk chunk) { ++ public NMSSlimeChunk(LevelChunk chunk, SlimeChunk reference) { + this.chunk = chunk; ++ this.extra = reference == null ? new CompoundTag("", new CompoundMap()) : reference.getExtraData(); + } + + @Override +@@ -192,6 +194,11 @@ public class NMSSlimeChunk implements SlimeChunk { + }); + } + ++ @Override ++ public CompoundTag getExtraData() { ++ return extra; ++ } ++ + public LevelChunk getChunk() { + return chunk; + } +diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java +index 07e542e3f75bf272f53345dc040d90358e7d7b2d..004d7bcc5b35c76855787dcf32fe460e73cab38f 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java ++++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java +@@ -45,14 +45,14 @@ public class NMSSlimeWorld implements SlimeWorld { + return null; + } + +- return new NMSSlimeChunk(chunk); ++ return new NMSSlimeChunk(chunk, memoryWorld.getChunk(x, z)); + } + + @Override + public Collection getChunkStorage() { + List chunks = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.instance); // Paper + return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull) +- .map(NMSSlimeChunk::new) ++ .map((chunkLevel) -> new NMSSlimeChunk(chunkLevel, memoryWorld.getChunk(chunkLevel.getPos().x, chunkLevel.getPos().z))) // This sucks, is there a better way? + .collect(Collectors.toList()); + } + +diff --git a/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java +index b20a037679182e3c4a8bf31f084078f6d7e4ff46..e449b3eebe0d245a2107a6d0018930d32dfc4976 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java ++++ b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java +@@ -62,6 +62,15 @@ public class SafeNmsChunkWrapper implements SlimeChunk { + return this.wrapper.getEntities(); + } + ++ @Override ++ public CompoundTag getExtraData() { ++ if (shouldDefaultBackToSlimeChunk()) { ++ return this.safety.getExtraData(); ++ } ++ ++ return this.wrapper.getExtraData(); ++ } ++ + /* + Slime chunks can still be requested but not actually loaded, this caused + some things to not properly save because they are not "loaded" into the chunk. +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java +index 27af3aa6ba8ffb100a6b1b50ba584e65c4aee86a..afa5804039d6b15c29f0ddc7d778b5b0b1d9dc6f 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java +@@ -165,6 +165,13 @@ public class SlimeChunkConverter { + Heightmap.primeHeightmaps(nmsChunk, unsetHeightMaps); + } + ++ var nmsExtraData = (net.minecraft.nbt.CompoundTag) Converter.convertTag(chunk.getExtraData()); ++ ++ // Attempt to read PDC from the extra tag ++ if (nmsExtraData.get("ChunkBukkitValues") != null) { ++ nmsChunk.persistentDataContainer.putAll(nmsExtraData.getCompound("ChunkBukkitValues")); ++ } ++ + return nmsChunk; + } + } +\ No newline at end of file +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java +index 092dae1f9e68f1c395cd0f8151cd696c0bcdceb0..208339f93fbd078b89d4bbb24fd8f7d3704a4736 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java +@@ -88,11 +88,11 @@ public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { + levelChunk = new SlimeChunkLevel(this.instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, + 0L, null, null, null); + +- chunk = new NMSSlimeChunk(levelChunk); ++ chunk = new NMSSlimeChunk(levelChunk, getChunk(x, z)); + + } else { + levelChunk = SlimeChunkConverter.deserializeSlimeChunk(this.instance, chunk); +- chunk = new SafeNmsChunkWrapper(new NMSSlimeChunk(levelChunk), chunk); ++ chunk = new SafeNmsChunkWrapper(new NMSSlimeChunk(levelChunk, chunk), chunk); + } + this.chunkStorage.put(new ChunkPos(x, z), chunk); + +@@ -105,7 +105,7 @@ public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { + final int x = providedChunk.locX; + final int z = providedChunk.locZ; + +- SlimeChunk chunk = new NMSSlimeChunk(providedChunk); ++ SlimeChunk chunk = new NMSSlimeChunk(providedChunk, getChunk(x, z)); + + if (FastChunkPruner.canBePruned(this.liveWorld, providedChunk)) { + this.chunkStorage.remove(new ChunkPos(x, z)); +@@ -114,7 +114,7 @@ public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { + + this.chunkStorage.put(new ChunkPos(x, z), + new SlimeChunkSkeleton(chunk.getX(), chunk.getZ(), chunk.getSections(), +- chunk.getHeightMaps(), chunk.getTileEntities(), chunk.getEntities())); ++ chunk.getHeightMaps(), chunk.getTileEntities(), chunk.getEntities(), chunk.getExtraData())); + } + + @Override +@@ -227,13 +227,20 @@ public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance { + continue; + } + ++ // Serialize Bukkit Values (PDC) ++ ++ var flowTag = Converter.convertTag("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound()); ++ ++ clonedChunk.getExtraData().getValue().put(flowTag); ++ + clonedChunk = new SlimeChunkSkeleton( + clonedChunk.getX(), + clonedChunk.getZ(), + clonedChunk.getSections(), + clonedChunk.getHeightMaps(), + clonedChunk.getTileEntities(), +- clonedChunk.getEntities() ++ clonedChunk.getEntities(), ++ clonedChunk.getExtraData() + ); + } + } +diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java +index 65b475b1292e01c918c1f8144599b5fa78688e97..a525fa1781535d458c5ecb67e261520692c858ac 100644 +--- a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java ++++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java +@@ -151,9 +151,7 @@ public class SlimeLevelInstance extends ServerLevel { + Bukkit.getLogger().log(Level.INFO, "Saving world " + this.slimeInstance.getName() + "..."); + long start = System.currentTimeMillis(); + +- Bukkit.getLogger().log(Level.INFO, "CONVERTING NMS -> SKELETON"); + SlimeWorld world = this.slimeInstance.getForSerialization(); +- Bukkit.getLogger().log(Level.INFO, "CONVERTED TO SKELETON, PUSHING OFF-THREAD"); + return WORLD_SAVER_SERVICE.submit(() -> { + try { + byte[] serializedWorld = SlimeSerializer.serialize(world); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 529decbe9f01e874ecfb70bd1c4da016e0262d2a..feec24fcb10074932743d8dfd160458d60b5c979 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -455,7 +455,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() + ); + } +- return this.entityStorage.read(new ChunkPos(chunkX, chunkZ)); ++ return this instanceof com.infernalsuite.aswm.level.SlimeLevelInstance ? null : this.entityStorage.read(new ChunkPos(chunkX, chunkZ)); + } + + private final io.papermc.paper.chunk.system.entity.EntityLookup entityLookup;