From db9480a650b267295f12b5a98c609a711296bd29 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 6 Apr 2025 20:07:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8C=BA=E5=9D=97=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=89=88=E6=9C=AC=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/PacketConsumers.java | 3 - .../craftengine/core/plugin/CraftEngine.java | 1 + .../plugin/dependency/DependencyManager.java | 3 +- .../core/util/FriendlyByteBuf.java | 1 - .../chunk/storage/CompressionMethod.java | 2 - .../storage/DefaultRegionFileStorage.java | 15 +--- .../core/world/chunk/storage/RegionFile.java | 73 +++++++++++++++++-- 7 files changed, 69 insertions(+), 29 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index d43517621..c99cf53b3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.plugin.network; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.text.Component; @@ -30,9 +29,7 @@ import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; import net.momirealms.craftengine.core.world.chunk.packet.MCSection; -import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; -import net.momirealms.sparrow.nbt.TagTypes; import net.momirealms.sparrow.nbt.serializer.NBTComponentSerializer; import org.bukkit.*; import org.bukkit.block.data.BlockData; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 2bc314a2d..868152174 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -232,6 +232,7 @@ public abstract class CraftEngine implements Plugin { if (this.scheduler != null) this.scheduler.shutdownExecutor(); if (this.commandManager != null) this.commandManager.unregisterFeatures(); if (this.senderFactory != null) this.senderFactory.close(); + if (this.dependencyManager != null) this.dependencyManager.close(); ResourcePackHost.instance().disable(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManager.java index 54b17e5af..5d54d3bab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManager.java @@ -6,7 +6,7 @@ import java.util.Set; /** * Loads and manages runtime dependencies for the plugin. */ -public interface DependencyManager extends AutoCloseable { +public interface DependencyManager { /** * Loads dependencies. @@ -23,6 +23,5 @@ public interface DependencyManager extends AutoCloseable { */ ClassLoader obtainClassLoaderWith(Set dependencies); - @Override void close(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java index 6fcfe3c16..304f24c60 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java @@ -11,7 +11,6 @@ import io.netty.util.ByteProcessor; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java index a53fb35cb..bf9f74ecd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java @@ -1,7 +1,5 @@ package net.momirealms.craftengine.core.world.chunk.storage; -import com.github.luben.zstd.ZstdInputStream; -import com.github.luben.zstd.ZstdOutputStream; import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java index bd4e62f31..3881127f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java @@ -15,10 +15,8 @@ import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.stream.Stream; public class DefaultRegionFileStorage implements WorldDataStorage { - private static final int FORMAT_VERSION = 1; private final Path folder; public static final String REGION_FILE_SUFFIX = ".mca"; @@ -30,15 +28,6 @@ public class DefaultRegionFileStorage implements WorldDataStorage { public DefaultRegionFileStorage(Path directory) { this.folder = directory; - Path metaDataFile = this.folder.getParent().resolve("craftengine.dat"); - } - - private Path[] regionFiles() throws IOException { - try (Stream paths = Files.walk(this.folder)) { - return paths - .filter(path -> path.toString().endsWith(REGION_FILE_SUFFIX)) - .toArray(Path[]::new); - } } @SuppressWarnings("BooleanMethodIsAlwaysInverted") @@ -138,7 +127,7 @@ public class DefaultRegionFileStorage implements WorldDataStorage { if (dataInputStream == null) { return null; } - tag = NBT.readCompound(dataInputStream, true); + tag = NBT.readCompound(dataInputStream, false); } catch (Throwable t1) { try { dataInputStream.close(); @@ -163,7 +152,7 @@ public class DefaultRegionFileStorage implements WorldDataStorage { } else { DataOutputStream dataOutputStream = regionFile.getChunkDataOutputStream(pos); try { - NBT.writeCompound(nbt, dataOutputStream, true); + NBT.writeCompound(nbt, dataOutputStream, false); } catch (Throwable t1) { if (dataOutputStream != null) { try { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java index ad5188aa2..0c0330efb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.core.world.chunk.storage; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.logger.PluginLogger; import net.momirealms.craftengine.core.world.ChunkPos; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.NBT; import javax.annotation.Nullable; import java.io.*; @@ -15,11 +17,13 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.time.Instant; +import java.util.List; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; public class RegionFile implements AutoCloseable { private static final PluginLogger LOGGER = CraftEngine.instance().logger(); - + private static final byte FORMAT_VERSION = 1; public static final int SECTOR_BYTES = 4096; public static final int CHUNK_HEADER_SIZE = 5; public static final int EXTERNAL_STREAM_FLAG = 128; @@ -42,6 +46,19 @@ public class RegionFile implements AutoCloseable { public final ReentrantLock fileLock = new ReentrantLock(true); public final Path regionFile; + private static final List> FORMAT_UPDATER = List.of( + // version 0 -> 1 (Use nameless compound tag) + (old) -> { + try { + CompoundTag tag = NBT.readCompound(new DataInputStream(old), true); + return new DataInputStream(new ByteArrayInputStream(NBT.toBytes(tag))); + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to migrate data from version 0 -> 1", e); + return null; + } + } + ); + public RegionFile(Path path, Path directory, CompressionMethod compressionMethod) throws IOException { this.header = ByteBuffer.allocateDirect(8192); this.regionFile = path; @@ -120,7 +137,10 @@ public class RegionFile implements AutoCloseable { // Read the chunk's stub information int size = bytebuffer.getInt(); - byte type = bytebuffer.get(); + byte flags = bytebuffer.get(); + byte compressionScheme = (byte) (flags & 0b00000111); + byte version = (byte) ((flags & 0b01111000) >>> 3); + if (size == 0) { LOGGER.warn(String.format("Chunk %s is allocated, but stream is missing", pos)); return null; @@ -128,13 +148,24 @@ public class RegionFile implements AutoCloseable { // Calculate the actual data size int actualSize = size - 1; - if (RegionFile.isExternalStreamChunk(type)) { + if (RegionFile.isExternalStreamChunk(flags)) { // If the chunk has both internal and external streams, log a warning. if (actualSize != 0) { LOGGER.warn("Chunk has both internal and external streams"); } // Create and return an input stream for the external chunk. - return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(type)); + if (version == FORMAT_VERSION) { + return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(compressionScheme)); + } else { + int currentVersion = version; + DataInputStream inputStream = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(compressionScheme)); + while (currentVersion < FORMAT_VERSION) { + inputStream = FORMAT_UPDATER.get(currentVersion).apply(inputStream); + if (inputStream == null) break; + currentVersion++; + } + return inputStream; + } } else if (actualSize > bytebuffer.remaining()) { // If the declared size of the chunk is greater than the remaining bytes in the buffer, the stream is truncated. LOGGER.severe(String.format("Chunk %s stream is truncated: expected %s but read %s", pos, actualSize, bytebuffer.remaining())); @@ -144,11 +175,36 @@ public class RegionFile implements AutoCloseable { LOGGER.severe(String.format("Declared size %s of chunk %s is negative", size, pos)); return null; } else { - // Otherwise, create and return a standard input stream for the chunk data. - return this.createChunkInputStream(pos, type, RegionFile.createInputStream(bytebuffer, actualSize)); + if (version == FORMAT_VERSION) { + // Otherwise, create and return a standard input stream for the chunk data. + return this.createChunkInputStream(pos, compressionScheme, RegionFile.createInputStream(bytebuffer, actualSize)); + } else { + int currentVersion = version; + DataInputStream inputStream = this.createChunkInputStream(pos, compressionScheme, RegionFile.createInputStream(bytebuffer, actualSize)); + while (currentVersion < FORMAT_VERSION) { + inputStream = FORMAT_UPDATER.get(currentVersion).apply(inputStream); + if (inputStream == null) break; + currentVersion++; + } + return inputStream; + } } } + public static byte encodeFlag(byte compressionScheme, byte version, boolean external) { + if (compressionScheme <= 0 || compressionScheme > 7) { + throw new IllegalArgumentException("compression method can only be a number between 1 and 7"); + } + if (version < 0 || version > 15) { + throw new IllegalArgumentException("Version number can only be a number between 0 and 15"); + } + return (byte) ( + (external ? 0b10000000 : 0) | + ((version & 0b00001111) << 3) | + (compressionScheme & 0b00000111) + ); + } + private static int getTimestamp() { return (int) (Instant.now().toEpochMilli() / 1000L); } @@ -307,7 +363,8 @@ public class RegionFile implements AutoCloseable { private ByteBuffer createExternalHeader(CompressionMethod compression) { ByteBuffer bytebuffer = ByteBuffer.allocate(CHUNK_HEADER_SIZE); bytebuffer.putInt(1); - bytebuffer.put((byte) (compression.getId() | EXTERNAL_STREAM_FLAG)); + bytebuffer.put(encodeFlag((byte) compression.getId(), FORMAT_VERSION, true)); + //bytebuffer.put((byte) (compression.getId() | EXTERNAL_STREAM_FLAG)); ((Buffer) bytebuffer).flip(); return bytebuffer; } @@ -402,7 +459,7 @@ public class RegionFile implements AutoCloseable { super.write(0); super.write(0); // compression method - super.write(RegionFile.this.compression.getId()); + super.write(encodeFlag((byte) RegionFile.this.compression.getId(), FORMAT_VERSION, false)); this.pos = pos; }