diff --git a/orebfuscator-common/src/main/java/net/imprex/orebfuscator/config/WorldConfigBundle.java b/orebfuscator-common/src/main/java/net/imprex/orebfuscator/config/WorldConfigBundle.java index c13cad55..f5a82eef 100644 --- a/orebfuscator-common/src/main/java/net/imprex/orebfuscator/config/WorldConfigBundle.java +++ b/orebfuscator-common/src/main/java/net/imprex/orebfuscator/config/WorldConfigBundle.java @@ -18,7 +18,7 @@ public interface WorldConfigBundle { int maxSectionIndex(); - boolean shouldObfuscate(int y); + boolean shouldProcessBlock(int y); int nextRandomObfuscationBlock(int y); diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java index 2e91161c..91ed454a 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java @@ -26,7 +26,7 @@ public static void initialize(Config config) { } String nmsVersion = MinecraftVersion.nmsVersion(); - if (ServerVersion.isMojangMapped()) { + if (ServerVersion.isMojangMapped() && !ServerVersion.isPaper()) { nmsVersion += "_mojang"; } @@ -58,6 +58,10 @@ public static int getMaxBitsPerBlockState() { return instance.getMaxBitsPerBlockState(); } + public static int getMaxBitsPerBiome() { + return instance.getMaxBitsPerBiome(); + } + public static BlockProperties getBlockByName(String key) { return instance.getBlockByName(NamespacedKey.fromString(key)); } diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java index 3bc532fe..cde86965 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java @@ -15,15 +15,19 @@ public abstract class AbstractNmsManager implements NmsManager { private final int uniqueBlockStateCount; private final int maxBitsPerBlockState; + private final int maxBitsPerBiome; + private final BlockStateProperties[] blockStates; private final Map blocks = new HashMap<>(); - public AbstractNmsManager(int uniqueBlockStateCount, AbstractRegionFileCache regionFileCache) { + public AbstractNmsManager(int uniqueBlockStateCount, int uniqueBiomesCount, AbstractRegionFileCache regionFileCache) { this.regionFileCache = regionFileCache; this.uniqueBlockStateCount = uniqueBlockStateCount; this.maxBitsPerBlockState = MathUtil.ceilLog2(uniqueBlockStateCount); + this.maxBitsPerBiome = MathUtil.ceilLog2(uniqueBiomesCount); + this.blockStates = new BlockStateProperties[uniqueBlockStateCount]; } @@ -50,6 +54,11 @@ public final int getMaxBitsPerBlockState() { return this.maxBitsPerBlockState; } + @Override + public int getMaxBitsPerBiome() { + return this.maxBitsPerBiome; + } + @Override public final BlockProperties getBlockByName(NamespacedKey key) { return this.blocks.get(key); diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java index 7df81e57..e6931e99 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java @@ -15,6 +15,8 @@ public interface NmsManager { int getMaxBitsPerBlockState(); + int getMaxBitsPerBiome(); + BlockProperties getBlockByName(NamespacedKey key); boolean isAir(int blockId); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java index a0e29308..8b50c77d 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java @@ -69,7 +69,10 @@ private static EntityPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.REGISTRY_ID.a(), new RegionFileCache(config.cache())); + super( + Block.REGISTRY_ID.a(), + IRegistry.h.get(IRegistry.u.a()).keySet().size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : IRegistry.BLOCK.c()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java index 6de27bda..74a0d12b 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java @@ -69,7 +69,10 @@ private static EntityPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.REGISTRY_ID.a(), new RegionFileCache(config.cache())); + super( + Block.REGISTRY_ID.a(), + IRegistry.f.get(IRegistry.ay.a()).keySet().size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : IRegistry.BLOCK.d()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java index 6b1a040e..c03f7a15 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java @@ -69,7 +69,10 @@ private static EntityPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.REGISTRY_ID.a(), new RegionFileCache(config.cache())); + super( + Block.REGISTRY_ID.a(), + IRegistry.f.get(IRegistry.ay.a()).keySet().size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : IRegistry.BLOCK.d()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java index a4733ccf..8eb8dc45 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java @@ -68,7 +68,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + Registry.REGISTRY.get(Registry.BIOME_REGISTRY.location()).keySet().size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java index a5f59059..69c67dfc 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java @@ -69,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + Registry.REGISTRY.get(Registry.BIOME_REGISTRY.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java index 9d2ef265..2296da67 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java @@ -69,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + Registry.REGISTRY.get(Registry.BIOME_REGISTRY.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java index cb5fcb95..b79fa103 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java @@ -68,7 +68,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + Registry.REGISTRY.get(Registry.BIOME_REGISTRY.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java index 4a6a9479..0619fa1d 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java index 4f81a4cc..daf4d288 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java index cc1b5f3c..7f4373c3 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java index 1b026f68..eabb627a 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java index 8d73c036..b9342a8d 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java index f37d98f8..d2df19eb 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +69,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + BuiltInRegistries.REGISTRY.get(Registries.BIOME.location()).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java index 27493057..61c05d6a 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java @@ -8,6 +8,7 @@ import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry; import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; @@ -26,6 +27,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; @@ -68,7 +70,10 @@ private static ServerPlayer player(Player player) { } public NmsManager(Config config) { - super(Block.BLOCK_STATE_REGISTRY.size(), new RegionFileCache(config.cache())); + super( + Block.BLOCK_STATE_REGISTRY.size(), + CraftRegistry.getMinecraftRegistry(Registries.BIOME).size(), + new RegionFileCache(config.cache())); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); @@ -99,6 +104,10 @@ public NmsManager(Config config) { public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); + /** + * LevelChunkSection + * PalettedContainer + */ return new ReadOnlyChunkWrapper(chunk); } diff --git a/orebfuscator-plugin/pom.xml b/orebfuscator-plugin/pom.xml index c5c13098..8254c8d3 100644 --- a/orebfuscator-plugin/pom.xml +++ b/orebfuscator-plugin/pom.xml @@ -149,7 +149,7 @@ ${revision} compile - + net.imprex orebfuscator-nms-v1_21_R1 diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java index 9eecdaa9..666a6dee 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java @@ -44,6 +44,7 @@ public void onLoad() { @Override public void onEnable() { try { + // GZIPOutputStream // Check for valid minecraft version if (MinecraftVersion.isBelow("1.16")) { throw new RuntimeException("Orebfuscator only supports minecraft 1.16 and above"); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/Chunk.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/Chunk.java index d9a8030a..c1711ec8 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/Chunk.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/Chunk.java @@ -88,14 +88,14 @@ private void skipBiomePalettedContainer() { int bitsPerValue = this.inputBuffer.readUnsignedByte(); if (bitsPerValue == 0) { - ByteBufUtil.readVarInt(this.inputBuffer); + VarInt.read(this.inputBuffer); } else if (bitsPerValue <= 3) { - for (int i = ByteBufUtil.readVarInt(this.inputBuffer); i > 0; i--) { - ByteBufUtil.readVarInt(this.inputBuffer); + for (int i = VarInt.read(this.inputBuffer); i > 0; i--) { + VarInt.read(this.inputBuffer); } } - int dataLength = ByteBufUtil.readVarInt(this.inputBuffer); + int dataLength = VarInt.read(this.inputBuffer); if (SimpleVarBitBuffer.calculateArraySize(bitsPerValue, 64) != dataLength) { throw new IndexOutOfBoundsException("data.length != VarBitBuffer::size " + dataLength + " " + SimpleVarBitBuffer.calculateArraySize(bitsPerValue, 64)); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkCapabilities.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkCapabilities.java index 68c4e6a1..26000376 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkCapabilities.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkCapabilities.java @@ -1,5 +1,8 @@ package net.imprex.orebfuscator.chunk; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; import net.imprex.orebfuscator.util.MinecraftVersion; public final class ChunkCapabilities { @@ -18,9 +21,48 @@ public final class ChunkCapabilities { private static final boolean hasHeightBitMask = MinecraftVersion.isBelow("1.18"); private static final boolean hasDynamicHeight = MinecraftVersion.isAtOrAbove("1.17"); + private static final byte[] emptyBlockPalettedContainer = createEmptyBlockPalettedContainer(); + + private static byte[] createEmptyBlockPalettedContainer() { + ByteBuf buffer = Unpooled.buffer(); + + // write block count + buffer.writeShort(0); + + if (ChunkCapabilities.hasSingleValuePalette()) { + // write bitsPerBlock + palette + buffer.writeByte(0); + VarInt.write(buffer, 0); + + // write empty block data + VarInt.write(buffer, 0); + } else { + // write min allowed bitsPerBlock + final int bitsPerBlock = 4; + buffer.writeByte(bitsPerBlock); + + // write palette with air entry + VarInt.write(buffer, 1); + VarInt.write(buffer, 0); + + // write empty block data + int packetSize = BitStorage.getSerializedSize(bitsPerBlock, 4096); + VarInt.write(buffer, packetSize); + buffer.skipBytes(packetSize); + } + + return buffer + .capacity(buffer.readableBytes()) + .array(); + } + private ChunkCapabilities() { } + public static byte[] emptyBlockPalettedContainer() { + return emptyBlockPalettedContainer; + } + public static boolean hasChunkPosFieldUnloadPacket() { return hasChunkPosFieldUnloadPacket; } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkSection.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkSection.java index f6b2873b..7eb06424 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkSection.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ChunkSection.java @@ -97,7 +97,7 @@ public void write(ByteBuf buffer) { this.palette.write(buffer); long[] data = this.data.toArray(); - ByteBufUtil.writeVarInt(buffer, data.length); + VarInt.write(buffer, data.length); for (long entry : data) { buffer.writeLong(entry); } @@ -111,7 +111,7 @@ public int[] read(ByteBuf buffer) { this.palette.read(buffer); long[] data = this.data.toArray(); - int length = ByteBufUtil.readVarInt(buffer); + int length = VarInt.read(buffer); if (data.length != length) { throw new IndexOutOfBoundsException("data.length != VarBitBuffer::size " + length + " " + this.data); } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/IndirectPalette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/IndirectPalette.java index be579c71..d187bee8 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/IndirectPalette.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/IndirectPalette.java @@ -52,9 +52,9 @@ public int valueFor(int id) { @Override public void read(ByteBuf buffer) { - this.size = ByteBufUtil.readVarInt(buffer); + this.size = VarInt.read(buffer); for (int id = 0; id < size; id++) { - int value = ByteBufUtil.readVarInt(buffer); + int value = VarInt.read(buffer); this.byId[id] = value; this.byValue[value] = (byte) id; } @@ -62,10 +62,10 @@ public void read(ByteBuf buffer) { @Override public void write(ByteBuf buffer) { - ByteBufUtil.writeVarInt(buffer, this.size); + VarInt.write(buffer, this.size); for (int id = 0; id < this.size; id++) { - ByteBufUtil.writeVarInt(buffer, this.valueFor(id)); + VarInt.write(buffer, this.valueFor(id)); } } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/SingleValuePalette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/SingleValuePalette.java index 1e5036af..af75caa1 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/SingleValuePalette.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/SingleValuePalette.java @@ -34,7 +34,7 @@ public int valueFor(int id) { @Override public void read(ByteBuf buffer) { - this.value = ByteBufUtil.readVarInt(buffer); + this.value = VarInt.read(buffer); } @Override @@ -42,7 +42,7 @@ public void write(ByteBuf buffer) { if (this.value == -1) { throw new IllegalStateException("value isn't initialized"); } else { - ByteBufUtil.writeVarInt(buffer, this.value); + VarInt.write(buffer, this.value); } } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ByteBufUtil.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/VarInt.java similarity index 76% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ByteBufUtil.java rename to orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/VarInt.java index 8dbd842f..14c7710e 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/ByteBufUtil.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/VarInt.java @@ -2,9 +2,9 @@ import io.netty.buffer.ByteBuf; -public class ByteBufUtil { +public class VarInt { - public static int getVarIntSize(int value) { + public static int getSerializedSize(int value) { for (int bytes = 1; bytes < 5; bytes++) { if ((value & -1 << bytes * 7) == 0) { return bytes; @@ -13,7 +13,7 @@ public static int getVarIntSize(int value) { return 5; } - public static void skipVarInt(ByteBuf buffer) { + public static void skipBytes(ByteBuf buffer) { int bytes = 0; byte in; do { @@ -24,7 +24,7 @@ public static void skipVarInt(ByteBuf buffer) { } while ((in & 0x80) != 0); } - public static int readVarInt(ByteBuf buffer) { + public static int read(ByteBuf buffer) { int out = 0; int bytes = 0; byte in; @@ -38,7 +38,7 @@ public static int readVarInt(ByteBuf buffer) { return out; } - public static void writeVarInt(ByteBuf buffer, int value) { + public static void write(ByteBuf buffer, int value) { while ((value & -0x80) != 0) { buffer.writeByte(value & 0x7F | 0x80); value >>>= 7; diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/Chunk.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/Chunk.java new file mode 100644 index 00000000..2fdc42f0 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/Chunk.java @@ -0,0 +1,98 @@ +package net.imprex.orebfuscator.chunk.next; + +import java.util.Arrays; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.Unpooled; +import net.imprex.orebfuscator.chunk.ChunkStruct; +import net.imprex.orebfuscator.chunk.next.ChunkSegment.ProcessingChunkSegment; +import net.imprex.orebfuscator.chunk.next.ChunkSegment.ReadOnlyChunkSegment; +import net.imprex.orebfuscator.chunk.next.ChunkSegment.SkippedChunkSegment; +import net.imprex.orebfuscator.config.WorldConfigBundle; +import net.imprex.orebfuscator.util.HeightAccessor; + +public class Chunk implements AutoCloseable { + + public static Chunk fromChunkStruct(ChunkStruct chunkStruct, WorldConfigBundle bundle) { + return new Chunk(chunkStruct, bundle); + } + + private final int chunkX; + private final int chunkZ; + + private final HeightAccessor heightAccessor; + private final ChunkSegment[] sections; + + private final ByteBuf inputBuffer; + private final ByteBuf outputBuffer; + + private final ChunkWriter writer; + + private Chunk(ChunkStruct chunkStruct, WorldConfigBundle bundle) { + this.chunkX = chunkStruct.chunkX; + this.chunkZ = chunkStruct.chunkZ; + + this.heightAccessor = HeightAccessor.get(chunkStruct.world); + this.sections = new ChunkSegment[this.heightAccessor.getSectionCount()]; + + this.inputBuffer = Unpooled.wrappedBuffer(chunkStruct.data); + this.outputBuffer = PooledByteBufAllocator.DEFAULT.heapBuffer(chunkStruct.data.length); + + for (int sectionIndex = 0; sectionIndex < this.sections.length; sectionIndex++) { + try { + if (chunkStruct.sectionMask.get(sectionIndex)) { + ChunkSegment sectionHolder; + + if (bundle.skipReadSectionIndex(sectionIndex)) { + sectionHolder = new SkippedChunkSegment(ChunkSection.skip(inputBuffer)); + } else { + ChunkSection chunkSection = new ChunkSection(inputBuffer); + if (bundle.skipProcessingSectionIndex(sectionIndex)) { + sectionHolder = new ReadOnlyChunkSegment(chunkSection); + } else { + sectionHolder = new ProcessingChunkSegment(chunkSection); + } + } + + this.sections[sectionIndex] = sectionHolder; + } + } catch (Exception e) { + throw new RuntimeException("An error occurred while reading chunk section: " + sectionIndex, e); + } + } + + this.writer = new ChunkWriter(this.outputBuffer, this.sections); + } + + public ChunkWriter getWriter() { + return writer; + } + + public HeightAccessor getHeightAccessor() { + return heightAccessor; + } + + public int getBlockState(int x, int y, int z) { + if (x >> 4 == this.chunkX && z >> 4 == this.chunkZ) { + ChunkSegment segment = this.sections[this.heightAccessor.getSectionIndex(y)]; + if (segment != null && segment.hasSection()) { + return segment.getSection().getBlockState(x, y, z); + } + return 0; + } + return -1; + } + + public byte[] finalizeOutput() { + this.outputBuffer.writeBytes(this.inputBuffer); + return Arrays.copyOfRange(this.outputBuffer.array(), this.outputBuffer.arrayOffset(), + this.outputBuffer.arrayOffset() + this.outputBuffer.readableBytes()); + } + + @Override + public void close() throws Exception { + this.inputBuffer.release(); + this.outputBuffer.release(); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSection.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSection.java new file mode 100644 index 00000000..a4eb4d9a --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSection.java @@ -0,0 +1,77 @@ +package net.imprex.orebfuscator.chunk.next; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.imprex.orebfuscator.chunk.ChunkCapabilities; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.palette.Palette; +import net.imprex.orebfuscator.chunk.next.strategies.BiomeStrategy; +import net.imprex.orebfuscator.chunk.next.strategies.BlockStrategy; + +public class ChunkSection { + + static ByteBuf skip(ByteBuf buffer) { + int startIndex = buffer.readerIndex(); + + buffer.skipBytes(2); // skip block count + + int bitsPerBlock = buffer.readUnsignedByte(); + BlockStrategy.skipPalette(buffer, bitsPerBlock); + BlockStrategy.skipBitStorage(buffer, bitsPerBlock); + + if (ChunkCapabilities.hasBiomePalettedContainer()) { + int bitsPerBiome = buffer.readUnsignedByte(); + BiomeStrategy.skipPalette(buffer, bitsPerBiome); + BiomeStrategy.skipBitStorage(buffer, bitsPerBiome); + } + + int sectionLength = buffer.readerIndex() - startIndex; + return buffer.slice(startIndex, sectionLength); + } + + public final int blockCount; + + public final Palette blockPalette; + public final BitStorage blockBitStorage; + + public final ByteBuf biomeSlice; + public final ByteBuf sectionSlice; + + public ChunkSection(ByteBuf buffer) { + int startIndex = buffer.readerIndex(); + + this.blockCount = buffer.readShort(); + + int bitsPerBlock = buffer.readUnsignedByte(); + this.blockPalette = BlockStrategy.readPalette(buffer, bitsPerBlock); + this.blockBitStorage = BlockStrategy.readBitStorage(buffer, this.blockPalette); + + if (ChunkCapabilities.hasBiomePalettedContainer()) { + int suffixStart = buffer.readerIndex(); + + int bitsPerBiome = buffer.readUnsignedByte(); + BiomeStrategy.skipPalette(buffer, bitsPerBiome); + BiomeStrategy.skipBitStorage(buffer, bitsPerBiome); + + int suffixLength = buffer.readerIndex() - suffixStart; + this.biomeSlice = buffer.slice(suffixStart, suffixLength); + } else { + this.biomeSlice = Unpooled.EMPTY_BUFFER; + } + + int sectionLength = buffer.readerIndex() - startIndex; + this.sectionSlice = buffer.slice(startIndex, sectionLength); + } + + public boolean isEmpty() { + return this.blockCount == 0; + } + + public int getBlockState(int x, int y, int z) { + return getBlockState(BlockStrategy.getIndex(x, y, z)); + } + + public int getBlockState(int index) { + return this.blockBitStorage.get(index); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSegment.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSegment.java new file mode 100644 index 00000000..d08d5012 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkSegment.java @@ -0,0 +1,95 @@ +package net.imprex.orebfuscator.chunk.next; + +import io.netty.buffer.ByteBuf; + +public interface ChunkSegment { + + boolean needsProcessing(); + + boolean hasSection(); + + default ByteBuf getBuffer() { + throw new UnsupportedOperationException(); + } + + default ChunkSection getSection() { + throw new UnsupportedOperationException(); + } + + public static class SkippedChunkSegment implements ChunkSegment { + + private final ByteBuf buffer; + + public SkippedChunkSegment(ByteBuf buffer) { + this.buffer = buffer; + } + + @Override + public boolean needsProcessing() { + return false; + } + + @Override + public boolean hasSection() { + return false; + } + + @Override + public ByteBuf getBuffer() { + return this.buffer; + } + } + + public static class ReadOnlyChunkSegment implements ChunkSegment { + + private final ChunkSection section; + + public ReadOnlyChunkSegment(ChunkSection section) { + this.section = section; + } + + @Override + public boolean needsProcessing() { + return false; + } + + @Override + public boolean hasSection() { + return true; + } + + @Override + public ByteBuf getBuffer() { + return this.section.sectionSlice; + } + + @Override + public ChunkSection getSection() { + return this.section; + } + } + + public static class ProcessingChunkSegment implements ChunkSegment { + + private final ChunkSection section; + + public ProcessingChunkSegment(ChunkSection section) { + this.section = section; + } + + @Override + public boolean needsProcessing() { + return true; + } + + @Override + public boolean hasSection() { + return true; + } + + @Override + public ChunkSection getSection() { + return this.section; + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkWriter.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkWriter.java new file mode 100644 index 00000000..4849fa15 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/ChunkWriter.java @@ -0,0 +1,97 @@ +package net.imprex.orebfuscator.chunk.next; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.ChunkCapabilities; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.palette.Palette; +import net.imprex.orebfuscator.chunk.next.strategies.BlockStrategy; + +public class ChunkWriter { + + private static int getSerializedSize(Palette palette, int suffixLength) { + return 3 // blockCount + bitsPerBlock + + palette.getSerializedSize() + + BitStorage.getSerializedSize(palette.getBitsPerEntry(), BlockStrategy.size()) + + suffixLength; + } + + private final ByteBuf outputBuffer; + private final ChunkSegment[] sections; + + private int nextSectionIndex = 0; + + public ChunkWriter(ByteBuf outputBuffer, ChunkSegment[] sections) { + this.outputBuffer = outputBuffer; + this.sections = sections; + } + + public boolean hasNext() { + return this.nextSectionIndex < this.sections.length; + } + + public int getSectionIndex() { + return this.nextSectionIndex - 1; + } + + public ChunkSection getOrWriteNext() { + ChunkSegment sectionHolder = this.sections[this.nextSectionIndex++]; + // skip non-present chunks (e.g. heightMask) + if (sectionHolder == null) { + return null; + } + + // write section buffer if present and skip processing + if (!sectionHolder.needsProcessing()) { + this.outputBuffer.writeBytes(sectionHolder.getBuffer()); + return null; + } + + // always write empty sections without processing + ChunkSection chunkSection = sectionHolder.getSection(); + if (chunkSection != null && chunkSection.isEmpty()) { + this.writeEmpty(chunkSection.biomeSlice); + return null; + } + + return chunkSection; + } + + public void writeSection(Palette palette, int blockCount, int[] blockStates, ByteBuf suffix) { + // skip write if block count is zero + if (blockCount == 0) { + writeEmpty(suffix); + return; + } + + // make sure buffer has enough space + int serializedSize = getSerializedSize(palette, suffix.readableBytes()); + this.outputBuffer.ensureWritable(serializedSize); + + // write block count + this.outputBuffer.writeShort(blockCount); + + // write bitsPerBlock + palette + this.outputBuffer.writeByte(palette.getBitsPerEntry()); + palette.write(this.outputBuffer); + + // write block data + BitStorage.Writer blockWriter = BlockStrategy.createBitStorageWriter(this.outputBuffer, palette); + if (palette.getBitsPerEntry() > 0) { + for (int i = 0; i < blockStates.length; i++) { + blockWriter.write(blockStates[i]); + } + blockWriter.throwIfNotExhausted(); + } + + // append remaining suffix + this.outputBuffer.writeBytes(suffix); + } + + private void writeEmpty(ByteBuf suffix) { + // write default empty section + this.outputBuffer.writeBytes(ChunkCapabilities.emptyBlockPalettedContainer()); + + // append remaining suffix + this.outputBuffer.writeBytes(suffix); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/BitStorage.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/BitStorage.java new file mode 100644 index 00000000..27f90da6 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/BitStorage.java @@ -0,0 +1,58 @@ +package net.imprex.orebfuscator.chunk.next.bitstorage; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.ChunkCapabilities; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public interface BitStorage { + + static int getSerializedSize(int bitsPerEntry, int size) { + if (ChunkCapabilities.hasSingleValuePalette() && bitsPerEntry == 0) { + return EmptyBitStorage.getSerializedSize(); + } else { + return SimpleBitStorage.getSerializedSize(bitsPerEntry, size); + } + } + + static void skipBytes(ByteBuf buffer, int bitsPerEntry) { + if (ChunkCapabilities.hasSingleValuePalette() && bitsPerEntry == 0) { + EmptyBitStorage.skipBytes(buffer); + } else { + SimpleBitStorage.skipBytes(buffer); + } + } + + static BitStorage read(ByteBuf buffer, int size, Palette palette) { + if (ChunkCapabilities.hasSingleValuePalette() && palette.getBitsPerEntry() == 0) { + return EmptyBitStorage.read(buffer, size, palette); + } else { + return SimpleBitStorage.read(buffer, size, palette); + } + } + + static Writer createWriter(ByteBuf buffer, int size, Palette palette) { + if (ChunkCapabilities.hasSingleValuePalette() && palette.getBitsPerEntry() == 0) { + return EmptyBitStorage.createWriter(buffer); + } else { + return SimpleBitStorage.createWriter(buffer, size, palette); + } + } + + int get(int index); + + int size(); + + interface Writer { + + default void throwIfNotExhausted() { + if (!isExhausted()) { + throw new IllegalStateException("BitStorage.Writer is not exhausted but closed!"); + } + } + + boolean isExhausted(); + + void write(int value); + + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/EmptyBitStorage.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/EmptyBitStorage.java new file mode 100644 index 00000000..8cb21d02 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/EmptyBitStorage.java @@ -0,0 +1,68 @@ +package net.imprex.orebfuscator.chunk.next.bitstorage; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.VarInt; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class EmptyBitStorage implements BitStorage { + + public static int getSerializedSize() { + return 1; + } + + public static void skipBytes(ByteBuf buffer) { + VarInt.skipBytes(buffer); + } + + public static BitStorage read(ByteBuf buffer, int size, Palette palette) { + if (palette.getBitsPerEntry() != 0) { + throw new IllegalArgumentException("Invalid palette, expected single valued palette!"); + } + + // skip length field + VarInt.skipBytes(buffer); + + return new EmptyBitStorage(size, palette.valueAtIndex(0)); + } + + public static Writer createWriter(ByteBuf buffer) { + VarInt.write(buffer, 0); + return Writer.INSTANCE; + } + + private final int size; + private final int value; + + public EmptyBitStorage(int size, int value) { + this.size = size; + this.value = value; + } + + @Override + public int get(int index) { + return this.value; + } + + @Override + public int size() { + return this.size; + } + + private static class Writer implements BitStorage.Writer { + + private static final Writer INSTANCE = new Writer(); + + private Writer() { + } + + @Override + public boolean isExhausted() { + return true; + } + + @Override + public void write(int value) { + throw new UnsupportedOperationException("Can't write to EmptyBitStorage!"); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/SimpleBitStorage.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/SimpleBitStorage.java new file mode 100644 index 00000000..3f96ca07 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/bitstorage/SimpleBitStorage.java @@ -0,0 +1,134 @@ +package net.imprex.orebfuscator.chunk.next.bitstorage; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.VarInt; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class SimpleBitStorage implements BitStorage { + + public static int getSerializedSize(int bitsPerEntry, int size) { + int arrayLength = getArrayLength(size, bitsPerEntry); + return VarInt.getSerializedSize(arrayLength) + (arrayLength * Long.BYTES); + } + + public static void skipBytes(ByteBuf buffer) { + int length = VarInt.read(buffer); + buffer.skipBytes(length * Long.BYTES); + } + + public static BitStorage read(ByteBuf buffer, int size, Palette palette) { + int bitsPerEntry = palette.getBitsPerEntry(); + int expectedLength = getArrayLength(size, bitsPerEntry); + + int length = VarInt.read(buffer); + if (length != expectedLength) { + // fix: FAWE chunk format incompatibility with bitsPerEntry == 1 + // https://github.com/Imprex-Development/Orebfuscator/issues/36 + buffer.skipBytes(Long.BYTES * length); + return new EmptyBitStorage(size, palette.valueAtIndex(0)); + } + + int[] values = new int[size]; + int valueMask = (1 << palette.getBitsPerEntry()) - 1; + + int bitOffset = 0; + long longBuffer = buffer.readLong(); + + for (int index = 0; index < values.length; index++) { + if (bitOffset + bitsPerEntry > 64) { + longBuffer = buffer.readLong(); + bitOffset = 0; + } + + int paletteIndex = (int) (longBuffer >>> bitOffset) & valueMask; + values[index] = palette.valueAtIndex(paletteIndex); + + bitOffset += bitsPerEntry; + } + + return new SimpleBitStorage(values); + } + + public static Writer createWriter(ByteBuf buffer, int size, Palette palette) { + return new Writer(buffer, size, palette); + } + + private static int getArrayLength(int size, int bitsPerEntry) { + int valuesPerLong = 64 / bitsPerEntry; + return (size + valuesPerLong - 1) / valuesPerLong; + } + + private final int[] values; + + private SimpleBitStorage(int[] values) { + this.values = values; + } + + @Override + public int get(int index) { + return this.values[index]; + } + + @Override + public int size() { + return this.values.length; + } + + public static class Writer implements BitStorage.Writer { + + private final int bitsPerEntry; + private final long valueMask; + + private final ByteBuf buffer; + private final Palette palette; + + private int bitOffset = 0; + private long longBuffer = 0; + + private int capacity; + + /* + * TODO: Investigate if long[] + flush is faster than + * write once + */ + + public Writer(ByteBuf buffer, int size, Palette palette) { + this.bitsPerEntry = palette.getBitsPerEntry(); + this.valueMask = (1L << this.bitsPerEntry) - 1L; + + this.capacity = size; + + this.buffer = buffer; + this.palette = palette; + + VarInt.write(buffer, getArrayLength(size, this.bitsPerEntry)); + } + + @Override + public boolean isExhausted() { + return this.capacity == 0; + } + + @Override + public void write(int value) { + if (this.capacity <= 0) { + throw new IllegalStateException("BitStorage.Writer is already exhausted"); + } + + if (this.bitOffset + this.bitsPerEntry > 64) { + this.buffer.writeLong(this.longBuffer); + this.longBuffer = 0; + this.bitOffset = 0; + } + + value = this.palette.indexForValue(value); + longBuffer |= (value & this.valueMask) << this.bitOffset; + + this.bitOffset += this.bitsPerEntry; + + if (--this.capacity == 0) { + this.buffer.writeLong(this.longBuffer); + } + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/DirectPalette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/DirectPalette.java new file mode 100644 index 00000000..98ec04f8 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/DirectPalette.java @@ -0,0 +1,59 @@ +package net.imprex.orebfuscator.chunk.next.palette; + +import java.util.List; + +import io.netty.buffer.ByteBuf; + +public class DirectPalette implements Palette { + + public static final Factory createFactory(int maxBitsPerEntry) { + DirectPalette palette = new DirectPalette(maxBitsPerEntry); + + return new Factory() { + + @Override + public void skipBytes(ByteBuf buffer) { + } + + @Override + public Palette read(ByteBuf buffer, int bitsPerEntry) { + return palette; + } + + @Override + public Palette create(int bitsPerEntry, List values) { + return palette; + } + }; + } + + private final int bitsPerEntry; + + public DirectPalette(int maxBitsPerEntry) { + this.bitsPerEntry = maxBitsPerEntry; + } + + @Override + public int getBitsPerEntry() { + return this.bitsPerEntry; + } + + @Override + public int valueAtIndex(int index) { + return index; + } + + @Override + public int indexForValue(int value) { + return value; + } + + @Override + public void write(ByteBuf buffer) { + } + + @Override + public int getSerializedSize() { + return 0; + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/IndirectPalette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/IndirectPalette.java new file mode 100644 index 00000000..5e183efb --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/IndirectPalette.java @@ -0,0 +1,130 @@ +package net.imprex.orebfuscator.chunk.next.palette; + +import java.util.BitSet; +import java.util.List; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.VarInt; + +public class IndirectPalette implements Palette { + + public static final Factory FACTORY = new Factory() { + + @Override + public void skipBytes(ByteBuf buffer) { + int size = VarInt.read(buffer); + for (int i = 0; i < size; i++) { + VarInt.skipBytes(buffer); + } + } + + @Override + public Palette read(ByteBuf buffer, int bitsPerEntry) { + int size = VarInt.read(buffer); + if (size > (1 << bitsPerEntry)) { + throw new IllegalStateException( + String.format("indirect palette is too big (got=%d < expected=%d)", size, (1 << bitsPerEntry))); + } + + int[] byIndex = new int[size]; + int maxValue = 0; + + for (int index = 0; index < size; index++) { + int value = VarInt.read(buffer); + byIndex[index] = value; + + if (value > maxValue) { + maxValue = value; + } + } + + return new IndirectPalette(bitsPerEntry, byIndex, maxValue + 1); + } + + @Override + public Palette create(int bitsPerEntry, List values) { + int[] byIndex = new int[values.size()]; + int maxValue = 0; + + for (int index = 0; index < values.size(); index++) { + int value = values.get(index); + byIndex[index] = value; + + if (value > maxValue) { + maxValue = value; + } + } + + return new IndirectPalette(bitsPerEntry, byIndex, maxValue + 1); + } + }; + + private final int bitsPerEntry; + private final int size; + + private final BitSet byValuePresent; + private final byte[] byValue; + private final int[] byIndex; + + /* + * TODO: remove byValue if PaletteBuilder is reworked + */ + + public IndirectPalette(int bitsPerEntry, int[] byIndex, int maxValue) { + this.size = byIndex.length; + this.bitsPerEntry = bitsPerEntry; + + this.byValuePresent = new BitSet(maxValue); + this.byValue = new byte[maxValue]; + + this.byIndex = byIndex; + + for (int index = 0; index < byIndex.length; index++) { + int value = byIndex[index]; + this.byValue[value] = (byte) index; + this.byValuePresent.set(value); + } + } + + @Override + public int getBitsPerEntry() { + return this.bitsPerEntry; + } + + @Override + public int getSerializedSize() { + int serializedSize = VarInt.getSerializedSize(this.size); + + for (int index = 0; index < this.size; index++) { + serializedSize += VarInt.getSerializedSize(this.byIndex[index]); + } + + return serializedSize; + } + + @Override + public int valueAtIndex(int index) { + if (index < 0 || index >= this.byIndex.length) { + throw new IndexOutOfBoundsException(); + } else { + return this.byIndex[index]; + } + } + + @Override + public int indexForValue(int value) { + if (!this.byValuePresent.get(value)) { + throw new IllegalArgumentException("value=" + value); + } + return this.byValue[value] & 0xFF; + } + + @Override + public void write(ByteBuf buffer) { + VarInt.write(buffer, this.size); + + for (int index = 0; index < this.size; index++) { + VarInt.write(buffer, this.byIndex[index]); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/Palette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/Palette.java new file mode 100644 index 00000000..45d1861c --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/Palette.java @@ -0,0 +1,34 @@ +package net.imprex.orebfuscator.chunk.next.palette; + +import java.util.List; + +import io.netty.buffer.ByteBuf; + +public interface Palette { + + int getBitsPerEntry(); + + int getSerializedSize(); + + int valueAtIndex(int index); + + int indexForValue(int value); + + void write(ByteBuf buffer); + + public interface Factory { + + void skipBytes(ByteBuf buffer); + + Palette read(ByteBuf buffer, int bitsPerEntry); + + Palette create(int bitsPerEntry, List values); + } + + public interface Builder { + + Builder add(int value); + + Palette build(); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/SingleValuedPalette.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/SingleValuedPalette.java new file mode 100644 index 00000000..a0915cde --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/palette/SingleValuedPalette.java @@ -0,0 +1,65 @@ +package net.imprex.orebfuscator.chunk.next.palette; + +import java.util.List; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.VarInt; + +public class SingleValuedPalette implements Palette { + + public static final Factory FACTORY = new Factory() { + + @Override + public void skipBytes(ByteBuf buffer) { + VarInt.skipBytes(buffer); + } + + @Override + public Palette read(ByteBuf buffer, int bitsPerEntry) { + int value = VarInt.read(buffer); + return new SingleValuedPalette(value); + } + + @Override + public Palette create(int bitsPerEntry, List values) { + return new SingleValuedPalette(values.get(0)); + } + }; + + private final int value; + + public SingleValuedPalette(int value) { + this.value = value; + } + + @Override + public int getBitsPerEntry() { + return 0; + } + + @Override + public int getSerializedSize() { + return VarInt.getSerializedSize(this.value); + } + + @Override + public int valueAtIndex(int index) { + if (index != 0) { + throw new IllegalArgumentException("Invalid index, single valued palette only has one entry!"); + } + return this.value; + } + + @Override + public int indexForValue(int value) { + if (value != this.value) { + throw new IllegalArgumentException("Invalid value, single valued palette only has one entry!"); + } + return 0; + } + + @Override + public void write(ByteBuf buffer) { + VarInt.write(buffer, this.value); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BiomeStrategy.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BiomeStrategy.java new file mode 100644 index 00000000..1f9b9c23 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BiomeStrategy.java @@ -0,0 +1,40 @@ +package net.imprex.orebfuscator.chunk.next.strategies; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class BiomeStrategy { + + public static int size() { + return PalettedContainerStrategy.BIOMES.size(); + } + + public static int getIndex(int x, int y, int z) { + return PalettedContainerStrategy.BIOMES.getIndex(x, y, z); + } + + public static void skipPalette(ByteBuf buffer, int bits) { + PalettedContainerStrategy.BIOMES.getConfiguration(bits).skipBytes(buffer); + } + + public static Palette readPalette(ByteBuf buffer, int bits) { + return PalettedContainerStrategy.BIOMES.getConfiguration(bits).read(buffer); + } + + public static Palette.Builder createPaletteBuilder() { + return PalettedContainerStrategy.BIOMES.createBuilder(); + } + + public static void skipBitStorage(ByteBuf buffer, int bits) { + BitStorage.skipBytes(buffer, bits); + } + + public static BitStorage readBitStorage(ByteBuf buffer, Palette palette) { + return BitStorage.read(buffer, PalettedContainerStrategy.BIOMES.size(), palette); + } + + public static BitStorage.Writer createBitStorageWriter(ByteBuf buffer, Palette palette) { + return BitStorage.createWriter(buffer, PalettedContainerStrategy.BIOMES.size(), palette); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BlockStrategy.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BlockStrategy.java new file mode 100644 index 00000000..8fd396fe --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/BlockStrategy.java @@ -0,0 +1,40 @@ +package net.imprex.orebfuscator.chunk.next.strategies; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class BlockStrategy { + + public static int size() { + return PalettedContainerStrategy.BLOCK_STATES.size(); + } + + public static int getIndex(int x, int y, int z) { + return PalettedContainerStrategy.BLOCK_STATES.getIndex(x, y, z); + } + + public static void skipPalette(ByteBuf buffer, int bits) { + PalettedContainerStrategy.BLOCK_STATES.getConfiguration(bits).skipBytes(buffer); + } + + public static Palette readPalette(ByteBuf buffer, int bits) { + return PalettedContainerStrategy.BLOCK_STATES.getConfiguration(bits).read(buffer); + } + + public static Palette.Builder createPaletteBuilder() { + return PalettedContainerStrategy.BLOCK_STATES.createBuilder(); + } + + public static void skipBitStorage(ByteBuf buffer, int bits) { + BitStorage.skipBytes(buffer, bits); + } + + public static BitStorage readBitStorage(ByteBuf buffer, Palette palette) { + return BitStorage.read(buffer, PalettedContainerStrategy.BLOCK_STATES.size(), palette); + } + + public static BitStorage.Writer createBitStorageWriter(ByteBuf buffer, Palette palette) { + return BitStorage.createWriter(buffer, PalettedContainerStrategy.BLOCK_STATES.size(), palette); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PaletteBuilder.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PaletteBuilder.java new file mode 100644 index 00000000..57f9271a --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PaletteBuilder.java @@ -0,0 +1,50 @@ +package net.imprex.orebfuscator.chunk.next.strategies; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class PaletteBuilder implements Palette.Builder { + + /** + * TODO: rewrite to return index on value add to allow + * for fast write without remapping, discard index[] if + * size exceeds 8 bit; + * + * Use a hash bucket strategy with a power-2 growth until + * max size; + */ + + private final PalettedContainerStrategy strategy; + + private final BitSet byValue; + private final List byIndex; + + public PaletteBuilder(PalettedContainerStrategy strategy) { + this.strategy = strategy; + + this.byValue = new BitSet(); + this.byIndex = new ArrayList<>(); + } + + @Override + public PaletteBuilder add(int value) { + if (this.byIndex.size() > 256) { + return this; + } + + if (!this.byValue.get(value)) { + this.byValue.set(value); + this.byIndex.add(value); + } + + return this; + } + + @Override + public Palette build() { + return this.strategy.createPalette(this.byIndex); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PalettedContainerStrategy.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PalettedContainerStrategy.java new file mode 100644 index 00000000..cb711116 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/chunk/next/strategies/PalettedContainerStrategy.java @@ -0,0 +1,103 @@ +package net.imprex.orebfuscator.chunk.next.strategies; + +import java.util.List; +import java.util.function.IntFunction; + +import io.netty.buffer.ByteBuf; +import net.imprex.orebfuscator.OrebfuscatorNms; +import net.imprex.orebfuscator.chunk.ChunkCapabilities; +import net.imprex.orebfuscator.chunk.next.palette.DirectPalette; +import net.imprex.orebfuscator.chunk.next.palette.IndirectPalette; +import net.imprex.orebfuscator.chunk.next.palette.Palette; +import net.imprex.orebfuscator.chunk.next.palette.SingleValuedPalette; +import net.imprex.orebfuscator.util.MathUtil; + +public class PalettedContainerStrategy { + + public static final PalettedContainerStrategy BLOCK_STATES = new PalettedContainerStrategy(4, OrebfuscatorNms.getMaxBitsPerBlockState(), + bits -> { + if (ChunkCapabilities.hasSingleValuePalette() && bits == 0) { + return Configuration.singleValued(); + } else if (bits < 9) { + return Configuration.indirect(Math.max(4, bits)); + } else { + return Configuration.direct(OrebfuscatorNms.getMaxBitsPerBlockState()); + } + }); + + public static final PalettedContainerStrategy BIOMES = new PalettedContainerStrategy(2, OrebfuscatorNms.getMaxBitsPerBiome(), + bits -> { + if (ChunkCapabilities.hasSingleValuePalette() && bits == 0) { + return Configuration.singleValued(); + } else if (bits < 4) { + return Configuration.indirect(bits); + } else { + return Configuration.direct(OrebfuscatorNms.getMaxBitsPerBiome()); + } + }); + + private final int sizeBits; + private final Configuration[] configurations; + + private PalettedContainerStrategy(int sizeBits, int maxBitsPerEntry, IntFunction configure) { + this.sizeBits = sizeBits; + + this.configurations = new Configuration[maxBitsPerEntry + 1]; + for (int bits = 0; bits <= maxBitsPerEntry; bits++) { + this.configurations[bits] = configure.apply(bits); + } + } + + public int size() { + return 1 << (this.sizeBits * 3); + } + + public int getIndex(int x, int y, int z) { + int mask = (1 << this.sizeBits) - 1; + return ((y & mask) << this.sizeBits | (z & mask)) << this.sizeBits | (x & mask); + } + + public Palette.Builder createBuilder() { + return new PaletteBuilder(this); + } + + Palette createPalette(List values) { + int bits = values.size() == 1 + ? 0 + : MathUtil.ceilLog2(values.size()); + return getConfiguration(bits).createPalette(values); + } + + public Configuration getConfiguration(int bits) { + return bits > this.configurations.length + ? this.configurations[this.configurations.length - 1] + : this.configurations[bits]; + } + + public static record Configuration(Palette.Factory factory, int bits) { + + private static Configuration singleValued() { + return new Configuration(SingleValuedPalette.FACTORY, 0); + } + + private static Configuration indirect(int bits) { + return new Configuration(IndirectPalette.FACTORY, bits); + } + + private static Configuration direct(int maxBitsPerEntry) { + return new Configuration(DirectPalette.createFactory(maxBitsPerEntry), maxBitsPerEntry); + } + + public void skipBytes(ByteBuf buffer) { + this.factory.skipBytes(buffer); + } + + public Palette read(ByteBuf buffer) { + return this.factory.read(buffer, this.bits); + } + + private Palette createPalette(List values) { + return this.factory.create(this.bits, values); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/config/OrebfuscatorConfig.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/config/OrebfuscatorConfig.java index 9652fa09..5db22b9e 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/config/OrebfuscatorConfig.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/config/OrebfuscatorConfig.java @@ -393,7 +393,7 @@ public int maxSectionIndex() { } @Override - public boolean shouldObfuscate(int y) { + public boolean shouldProcessBlock(int y) { return y >= this.minY && y <= this.maxY; } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java index 2b761fd7..3b799137 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java @@ -60,7 +60,7 @@ public void process(ObfuscationTask task) { final int baseY = heightAccessor.getMinBuildHeight() + (sectionIndex << 4); for (int index = 0; index < 4096; index++) { int y = baseY + (index >> 8 & 15); - if (!bundle.shouldObfuscate(y)) { + if (!bundle.shouldProcessBlock(y)) { continue; } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java index 7238a468..c915e690 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java @@ -16,7 +16,7 @@ public class ObfuscationSystem { private final OrebfuscatorConfig config; private final ObfuscationCache cache; - private final ObfuscationProcessor processor; + private final ObfuscationTaskProcessor processor; private final ObfuscationTaskDispatcher dispatcher; private ObfuscationListener listener; @@ -27,7 +27,7 @@ public ObfuscationSystem(Orebfuscator orebfuscator) { this.config = orebfuscator.getOrebfuscatorConfig(); this.cache = orebfuscator.getObfuscationCache(); - this.processor = new ObfuscationProcessor(orebfuscator); + this.processor = new ObfuscationTaskProcessor(orebfuscator); this.dispatcher = new ObfuscationTaskDispatcher(orebfuscator, this.processor); this.deobfuscationWorker = new DeobfuscationWorker(orebfuscator); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java index 0cf1aa1e..5d792b35 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java @@ -11,10 +11,10 @@ class ObfuscationTaskDispatcher { private final Queue tasks = new ConcurrentLinkedQueue<>(); - private final ObfuscationProcessor processor; + private final ObfuscationTaskProcessor processor; private final ObfuscationTaskWorker[] worker; - public ObfuscationTaskDispatcher(Orebfuscator orebfuscator, ObfuscationProcessor processor) { + public ObfuscationTaskDispatcher(Orebfuscator orebfuscator, ObfuscationTaskProcessor processor) { this.processor = processor; AdvancedConfig config = orebfuscator.getOrebfuscatorConfig().advanced(); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessor.java new file mode 100644 index 00000000..1040d982 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessor.java @@ -0,0 +1,60 @@ +package net.imprex.orebfuscator.obfuscation; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.bukkit.Bukkit; + +import net.imprex.orebfuscator.Orebfuscator; + +public class ObfuscationTaskProcessor { + + private static final AtomicInteger COUNT = new AtomicInteger(-2500); + + private static final AtomicLong TIME_OLD = new AtomicLong(); + private static final AtomicLong TIME_NEW = new AtomicLong(); + + private static final AtomicLong SIZE_OLD = new AtomicLong(); + private static final AtomicLong SIZE_NEW = new AtomicLong(); + + private final ObfuscationTaskProcessorOld oldProcessor; + private final ObfuscationTaskProcessorNew newProcessor; + + public ObfuscationTaskProcessor(Orebfuscator orebfuscator) { + this.oldProcessor = new ObfuscationTaskProcessorOld(orebfuscator); + this.newProcessor = new ObfuscationTaskProcessorNew(orebfuscator); + } + + public void process(ObfuscationTask task) { + long processTimeNew, processTimeOld; + int sizeNew, sizeOld; + + long time = System.currentTimeMillis(); + try { + sizeNew = newProcessor.process(task); + } finally { + processTimeNew = System.currentTimeMillis() - time; + } + + time = System.currentTimeMillis(); + try { + sizeOld = oldProcessor.process(task); + } finally { + processTimeOld = System.currentTimeMillis() - time; + } + + int count = COUNT.incrementAndGet(); + if (count > 0) { + double totalTimeNew = (double) TIME_NEW.addAndGet(processTimeNew); + double totalTimeOld = (double) TIME_OLD.addAndGet(processTimeOld); + + double totalSizeNew = (double) SIZE_NEW.addAndGet(sizeNew); + double totalSizeOld = (double) SIZE_OLD.addAndGet(sizeOld); + + if (count % 100 == 0) { + Bukkit.broadcastMessage(String.format("ms/chunk: %.3f - %.3f", (totalTimeNew / count), (totalTimeOld / count))); + Bukkit.broadcastMessage(String.format("kb/chunk: %.3f - %.3f", (totalSizeNew / 1024 / count), (totalSizeOld / 1024 / count))); + } + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorNew.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorNew.java new file mode 100644 index 00000000..45cf4d95 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorNew.java @@ -0,0 +1,176 @@ +package net.imprex.orebfuscator.obfuscation; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.bukkit.World; + +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorNms; +import net.imprex.orebfuscator.chunk.ChunkStruct; +import net.imprex.orebfuscator.chunk.next.Chunk; +import net.imprex.orebfuscator.chunk.next.ChunkSection; +import net.imprex.orebfuscator.chunk.next.ChunkWriter; +import net.imprex.orebfuscator.chunk.next.palette.Palette; +import net.imprex.orebfuscator.chunk.next.strategies.BlockStrategy; +import net.imprex.orebfuscator.config.BlockFlags; +import net.imprex.orebfuscator.config.ObfuscationConfig; +import net.imprex.orebfuscator.config.OrebfuscatorConfig; +import net.imprex.orebfuscator.config.ProximityConfig; +import net.imprex.orebfuscator.config.WorldConfigBundle; +import net.imprex.orebfuscator.util.BlockPos; +import net.imprex.orebfuscator.util.HeightAccessor; + +public class ObfuscationTaskProcessorNew { + + private final OrebfuscatorConfig config; + + public ObfuscationTaskProcessorNew(Orebfuscator orebfuscator) { + this.config = orebfuscator.getOrebfuscatorConfig(); + } + + public int process(ObfuscationTask task) { + ChunkStruct chunkStruct = task.getChunkStruct(); + + World world = chunkStruct.world; + HeightAccessor heightAccessor = HeightAccessor.get(world); + + WorldConfigBundle bundle = this.config.world(world); + BlockFlags blockFlags = bundle.blockFlags(); + ObfuscationConfig obfuscationConfig = bundle.obfuscation(); + ProximityConfig proximityConfig = bundle.proximity(); + + Set blockEntities = new HashSet<>(); + List proximityBlocks = new ArrayList<>(); + + int baseX = chunkStruct.chunkX << 4; + int baseZ = chunkStruct.chunkZ << 4; + + int layerY = Integer.MIN_VALUE; + int layerYBlockState = -1; + + int[] blockStates = new int[4096]; + + try (Chunk chunk = Chunk.fromChunkStruct(chunkStruct, bundle)) { + ChunkWriter writer = chunk.getWriter(); + while (writer.hasNext()) { + try { + ChunkSection chunkSection = writer.getOrWriteNext(); + if (chunkSection == null) { + continue; + } + + int blockCount = chunkSection.blockCount; + Palette.Builder builder = BlockStrategy.createPaletteBuilder(); + + final int baseY = heightAccessor.getMinBuildHeight() + (writer.getSectionIndex() << 4); + for (int index = 0; index < 4096; index++) { + int blockState = chunkSection.getBlockState(index); + + int y = baseY + (index >> 8 & 15); + if (bundle.shouldProcessBlock(y)) { + + int obfuscateBits = blockFlags.flags(blockState, y); + if (!BlockFlags.isEmpty(obfuscateBits)) { + + int x = baseX + (index & 15); + int z = baseZ + (index >> 4 & 15); + boolean obfuscated = true; + + if (BlockFlags.isObfuscateBitSet(obfuscateBits) + && obfuscationConfig.shouldObfuscate(y) + && shouldObfuscate(task, chunk, x, y, z)) { + if (obfuscationConfig.layerObfuscation()) { + if (layerY != y) { + layerY = y; + layerYBlockState = bundle.nextRandomObfuscationBlock(y); + } + blockState = layerYBlockState; + } else { + blockState = bundle.nextRandomObfuscationBlock(y); + } + } else if (BlockFlags.isProximityBitSet(obfuscateBits) + && proximityConfig.shouldObfuscate(y)) { + proximityBlocks.add(new BlockPos(x, y, z)); + + if (BlockFlags.isUseBlockBelowBitSet(obfuscateBits)) { + blockState = getBlockStateBelow(blockFlags, chunk, x, y, z); + if (blockState == -1) { + blockState = bundle.nextRandomProximityBlock(y); + } + } else { + blockState = bundle.nextRandomProximityBlock(y); + } + } else { + obfuscated = false; + } + + if (obfuscated) { + if (BlockFlags.isBlockEntityBitSet(obfuscateBits)) { + blockEntities.add(new BlockPos(x, y, z)); + } + if (OrebfuscatorNms.isAir(blockState)) { + blockCount--; + } + } + } + } + + builder.add(blockState); + blockStates[index] = blockState; + } + + writer.writeSection(builder.build(), blockCount, blockStates, chunkSection.biomeSlice); + } catch (Exception e) { + throw new RuntimeException("error in section: " + writer.getSectionIndex(), e); + } + } + + byte[] output = chunk.finalizeOutput(); + task.complete(output, blockEntities, proximityBlocks); + return output.length; + } catch (Exception e) { + task.completeExceptionally(e); + return 0; + } + } + + // returns first block below given position that wouldn't be obfuscated in any + // way at given position + private int getBlockStateBelow(BlockFlags blockFlags, Chunk chunk, int x, int y, int z) { + for (int targetY = y - 1; targetY > chunk.getHeightAccessor().getMinBuildHeight(); targetY--) { + int blockData = chunk.getBlockState(x, targetY, z); + if (blockData != -1) { + int mask = blockFlags.flags(blockData, y); + if (BlockFlags.isEmpty(mask) || BlockFlags.isAllowForUseBlockBelowBitSet(mask)) { + return blockData; + } + } + } + return -1; + } + + private boolean shouldObfuscate(ObfuscationTask task, Chunk chunk, int x, int y, int z) { + return isAdjacentBlockOccluding(task, chunk, x, y + 1, z) + && isAdjacentBlockOccluding(task, chunk, x, y - 1, z) + && isAdjacentBlockOccluding(task, chunk, x + 1, y, z) + && isAdjacentBlockOccluding(task, chunk, x - 1, y, z) + && isAdjacentBlockOccluding(task, chunk, x, y, z + 1) + && isAdjacentBlockOccluding(task, chunk, x, y, z - 1); + } + + private boolean isAdjacentBlockOccluding(ObfuscationTask task, Chunk chunk, int x, int y, int z) { + if (y >= chunk.getHeightAccessor().getMaxBuildHeight() || y < chunk.getHeightAccessor().getMinBuildHeight()) { + return false; + } + + int blockId = chunk.getBlockState(x, y, z); + if (blockId == -1) { + blockId = task.getBlockState(x, y, z); + } + + return blockId >= 0 && OrebfuscatorNms.isOccluding(blockId); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorOld.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorOld.java new file mode 100644 index 00000000..e489d7f7 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskProcessorOld.java @@ -0,0 +1,164 @@ +package net.imprex.orebfuscator.obfuscation; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.bukkit.World; + +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorNms; +import net.imprex.orebfuscator.chunk.Chunk; +import net.imprex.orebfuscator.chunk.ChunkSection; +import net.imprex.orebfuscator.chunk.ChunkStruct; +import net.imprex.orebfuscator.config.BlockFlags; +import net.imprex.orebfuscator.config.ObfuscationConfig; +import net.imprex.orebfuscator.config.OrebfuscatorConfig; +import net.imprex.orebfuscator.config.ProximityConfig; +import net.imprex.orebfuscator.config.ProximityHeightCondition; +import net.imprex.orebfuscator.config.WorldConfigBundle; +import net.imprex.orebfuscator.util.BlockPos; +import net.imprex.orebfuscator.util.HeightAccessor; + +public class ObfuscationTaskProcessorOld { + + private final OrebfuscatorConfig config; + + public ObfuscationTaskProcessorOld(Orebfuscator orebfuscator) { + this.config = orebfuscator.getOrebfuscatorConfig(); + } + + public int process(ObfuscationTask task) { + ChunkStruct chunkStruct = task.getChunkStruct(); + + World world = chunkStruct.world; + HeightAccessor heightAccessor = HeightAccessor.get(world); + + WorldConfigBundle bundle = this.config.world(world); + BlockFlags blockFlags = bundle.blockFlags(); + ObfuscationConfig obfuscationConfig = bundle.obfuscation(); + ProximityConfig proximityConfig = bundle.proximity(); + + Set blockEntities = new HashSet<>(); + List proximityBlocks = new ArrayList<>(); + + int baseX = chunkStruct.chunkX << 4; + int baseZ = chunkStruct.chunkZ << 4; + + int layerY = Integer.MIN_VALUE; + int layerYBlockState = -1; + + try (Chunk chunk = Chunk.fromChunkStruct(chunkStruct)) { + for (int sectionIndex = Math.max(0, bundle.minSectionIndex()); sectionIndex <= Math + .min(chunk.getSectionCount() - 1, bundle.maxSectionIndex()); sectionIndex++) { + ChunkSection chunkSection = chunk.getSection(sectionIndex); + if (chunkSection == null || chunkSection.isEmpty()) { + continue; + } + + final int baseY = heightAccessor.getMinBuildHeight() + (sectionIndex << 4); + for (int index = 0; index < 4096; index++) { + int y = baseY + (index >> 8 & 15); + if (!bundle.shouldProcessBlock(y)) { + continue; + } + + int blockState = chunkSection.getBlockState(index); + + int obfuscateBits = blockFlags.flags(blockState, y); + if (BlockFlags.isEmpty(obfuscateBits)) { + continue; + } + + int x = baseX + (index & 15); + int z = baseZ + (index >> 4 & 15); + + boolean isObfuscateBitSet = BlockFlags.isObfuscateBitSet(obfuscateBits); + boolean obfuscated = false; + + // should current block be obfuscated + if (isObfuscateBitSet && obfuscationConfig.shouldObfuscate(y) && shouldObfuscate(task, chunk, x, y, z)) { + if (obfuscationConfig.layerObfuscation()) { + if (layerY != y) { + layerY = y; + layerYBlockState = bundle.nextRandomObfuscationBlock(y); + } + blockState = layerYBlockState; + } else { + blockState = bundle.nextRandomObfuscationBlock(y); + } + obfuscated = true; + } + + // should current block be proximity hidden + if (!obfuscated && BlockFlags.isProximityBitSet(obfuscateBits) && proximityConfig.shouldObfuscate(y)) { + proximityBlocks.add(new BlockPos(x, y, z)); + if (BlockFlags.isUseBlockBelowBitSet(obfuscateBits)) { + boolean allowNonOcclude = !isObfuscateBitSet || !ProximityHeightCondition.isPresent(obfuscateBits); + blockState = getBlockStateBelow(bundle, chunk, x, y, z, allowNonOcclude); + } else { + blockState = bundle.nextRandomProximityBlock(y); + } + obfuscated = true; + } + + // update block state if needed + if (obfuscated) { + chunkSection.setBlockState(index, blockState); + if (BlockFlags.isBlockEntityBitSet(obfuscateBits)) { + blockEntities.add(new BlockPos(x, y, z)); + } + } + } + } + + byte[] output = chunk.finalizeOutput(); + task.complete(output, blockEntities, proximityBlocks); + return output.length; + } catch (Exception e) { + task.completeExceptionally(e); + return 0; + } + } + + // returns first block below given position that wouldn't be obfuscated in any + // way at given position + private int getBlockStateBelow(WorldConfigBundle bundle, Chunk chunk, int x, int y, int z, boolean allowNonOcclude) { + BlockFlags blockFlags = bundle.blockFlags(); + + for (int targetY = y - 1; targetY > chunk.getHeightAccessor().getMinBuildHeight(); targetY--) { + int blockData = chunk.getBlockState(x, targetY, z); + if (blockData != -1 && (allowNonOcclude || OrebfuscatorNms.isOccluding(blockData))) { + int mask = blockFlags.flags(blockData, y); + if (BlockFlags.isEmpty(mask) || BlockFlags.isAllowForUseBlockBelowBitSet(mask)) { + return blockData; + } + } + } + + return bundle.nextRandomProximityBlock(y); + } + + private boolean shouldObfuscate(ObfuscationTask task, Chunk chunk, int x, int y, int z) { + return isAdjacentBlockOccluding(task, chunk, x, y + 1, z) + && isAdjacentBlockOccluding(task, chunk, x, y - 1, z) + && isAdjacentBlockOccluding(task, chunk, x + 1, y, z) + && isAdjacentBlockOccluding(task, chunk, x - 1, y, z) + && isAdjacentBlockOccluding(task, chunk, x, y, z + 1) + && isAdjacentBlockOccluding(task, chunk, x, y, z - 1); + } + + private boolean isAdjacentBlockOccluding(ObfuscationTask task, Chunk chunk, int x, int y, int z) { + if (y >= chunk.getHeightAccessor().getMaxBuildHeight() || y < chunk.getHeightAccessor().getMinBuildHeight()) { + return false; + } + + int blockId = chunk.getBlockState(x, y, z); + if (blockId == -1) { + blockId = task.getBlockState(x, y, z); + } + + return blockId >= 0 && OrebfuscatorNms.isOccluding(blockId); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java index c4223394..cf0c1a2d 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java @@ -10,12 +10,12 @@ class ObfuscationTaskWorker implements Runnable { private static final AtomicInteger WORKER_ID = new AtomicInteger(); private final ObfuscationTaskDispatcher dispatcher; - private final ObfuscationProcessor processor; + private final ObfuscationTaskProcessor processor; private final Thread thread; private volatile boolean running = true; - public ObfuscationTaskWorker(ObfuscationTaskDispatcher dispatcher, ObfuscationProcessor processor) { + public ObfuscationTaskWorker(ObfuscationTaskDispatcher dispatcher, ObfuscationTaskProcessor processor) { this.dispatcher = dispatcher; this.processor = processor; diff --git a/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/EmptyBitStorageTest.java b/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/EmptyBitStorageTest.java new file mode 100644 index 00000000..bff46371 --- /dev/null +++ b/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/EmptyBitStorageTest.java @@ -0,0 +1,54 @@ +package net.imprex.orebfuscator.chunk.bitstorage; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.bitstorage.EmptyBitStorage; +import net.imprex.orebfuscator.chunk.next.palette.IndirectPalette; +import net.imprex.orebfuscator.chunk.next.palette.Palette; +import net.imprex.orebfuscator.chunk.next.palette.SingleValuedPalette; + +public class EmptyBitStorageTest { + + private static final int STORAGE_SIZE = 4095; + private static final int VALUE = 7; + + @Test + public void testReadWriteSkip() { + Palette validPalette = new SingleValuedPalette(VALUE); + Palette invalidPalette = new IndirectPalette(1, new int[0], 0); + + int serializedSize = EmptyBitStorage.getSerializedSize(); + ByteBuf buffer = Unpooled.buffer(serializedSize); + + BitStorage.Writer writer = EmptyBitStorage.createWriter(buffer); + + assertEquals(true, writer.isExhausted()); + assertDoesNotThrow(() -> writer.throwIfNotExhausted()); + + assertThrows(UnsupportedOperationException.class, () -> writer.write(0)); + assertEquals(serializedSize, buffer.readableBytes()); + + assertThrows(IllegalArgumentException.class, () -> EmptyBitStorage.read(buffer, STORAGE_SIZE, invalidPalette)); + BitStorage bitStorage = EmptyBitStorage.read(buffer, STORAGE_SIZE, validPalette); + + for (int i = 0; i < STORAGE_SIZE; i++) { + assertEquals(VALUE, bitStorage.get(i)); + } + + assertEquals(0, buffer.readableBytes()); + assertEquals(STORAGE_SIZE, bitStorage.size()); + + buffer.readerIndex(0); + + EmptyBitStorage.skipBytes(buffer); + + assertEquals(0, buffer.readableBytes()); + } +} \ No newline at end of file diff --git a/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/SimpleBitStorageTest.java b/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/SimpleBitStorageTest.java new file mode 100644 index 00000000..7725290d --- /dev/null +++ b/orebfuscator-plugin/src/test/java/net/imprex/orebfuscator/chunk/bitstorage/SimpleBitStorageTest.java @@ -0,0 +1,155 @@ +package net.imprex.orebfuscator.chunk.bitstorage; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.Test; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.imprex.orebfuscator.chunk.VarInt; +import net.imprex.orebfuscator.chunk.next.bitstorage.BitStorage; +import net.imprex.orebfuscator.chunk.next.bitstorage.SimpleBitStorage; +import net.imprex.orebfuscator.chunk.next.palette.Palette; + +public class SimpleBitStorageTest { + + private static final byte[] IDENTITY_BUFFER = readIdentityBuffer("src/test/resources/simple-bit-storage.bin"); + private static final int TEST_ENTRY_COUNT = 65535; + + private static byte[] readIdentityBuffer(String path) { + try { + return Files.readAllBytes(Paths.get(path)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testReadWriteSkip() { + for (int bits = 1; bits < 32; bits++) { + Palette palette = new DummyIdentityPalette(bits); + int maxValue = (1 << bits) - 1; + + int serializedSize = SimpleBitStorage.getSerializedSize(bits, TEST_ENTRY_COUNT); + ByteBuf buffer = Unpooled.buffer(serializedSize); + + BitStorage.Writer writer = SimpleBitStorage.createWriter(buffer, TEST_ENTRY_COUNT, palette); + + // initial writer state check + assertEquals(false, writer.isExhausted()); + assertThrows(IllegalStateException.class, () -> writer.throwIfNotExhausted()); + + for (int i = 0; i < TEST_ENTRY_COUNT; i++) { + assertEquals(false, writer.isExhausted()); + writer.write(i % maxValue); + } + + // exhausted writer state check + assertEquals(true, writer.isExhausted()); + assertDoesNotThrow(() -> writer.throwIfNotExhausted()); + assertThrows(IllegalStateException.class, () -> writer.write(0)); + + // expected vs actual serialized size check + assertEquals(serializedSize, buffer.readableBytes()); + + BitStorage bitStorage = SimpleBitStorage.read(buffer, TEST_ENTRY_COUNT, palette); + for (int i = 0; i < TEST_ENTRY_COUNT; i++) { + assertEquals(i % maxValue, bitStorage.get(i)); + } + + assertEquals(0, buffer.readableBytes()); + assertEquals(TEST_ENTRY_COUNT, bitStorage.size()); + + buffer.readerIndex(0); + SimpleBitStorage.skipBytes(buffer); + + assertEquals(0, buffer.readableBytes()); + } + } + + @Test + public void testFaweFix() { + Palette palette = new DummyIdentityPalette(4); + + int faweLength = (int) Math.ceil(TEST_ENTRY_COUNT / 64f); + int fawePacketSize = faweLength * Long.BYTES; + ByteBuf buffer = Unpooled.buffer(VarInt.getSerializedSize(faweLength) + fawePacketSize); + + VarInt.write(buffer, faweLength); + buffer.writerIndex(buffer.writerIndex() + fawePacketSize); + + BitStorage bitStorage = SimpleBitStorage.read(buffer, TEST_ENTRY_COUNT, palette); + for (int i = 0; i < TEST_ENTRY_COUNT; i++) { + assertEquals(0, bitStorage.get(i)); + } + + assertEquals(0, buffer.readableBytes()); + } + + @Test + public void testReadIdentity() { + Palette palette = new DummyIdentityPalette(16); + ByteBuf buffer = Unpooled.wrappedBuffer(IDENTITY_BUFFER).asReadOnly(); + + BitStorage bitStorage = SimpleBitStorage.read(buffer, TEST_ENTRY_COUNT, palette); + for (int i = 0; i < TEST_ENTRY_COUNT; i++) { + assertEquals(i, bitStorage.get(i)); + } + } + + public void testWriteIdentity() { + Palette palette = new DummyIdentityPalette(16); + byte[] output = new byte[IDENTITY_BUFFER.length]; + + ByteBuf buffer = Unpooled.wrappedBuffer(output); + buffer.writerIndex(0); + + BitStorage.Writer writer = new SimpleBitStorage.Writer(buffer, TEST_ENTRY_COUNT, palette); + for (int i = 0; i < TEST_ENTRY_COUNT; i++) { + writer.write(i); + } + + assertArrayEquals(IDENTITY_BUFFER, output); + } + + public class DummyIdentityPalette implements Palette { + + private final int bitsPerEntry; + + public DummyIdentityPalette(int bitsPerEntry) { + this.bitsPerEntry = bitsPerEntry; + } + + @Override + public int getBitsPerEntry() { + return this.bitsPerEntry; + } + + @Override + public int getSerializedSize() { + throw new UnsupportedOperationException(); + } + + @Override + public int valueAtIndex(int index) { + return index; + } + + @Override + public int indexForValue(int value) { + return value; + } + + @Override + public void write(ByteBuf buffer) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/orebfuscator-plugin/src/test/resources/simple-bit-storage.bin b/orebfuscator-plugin/src/test/resources/simple-bit-storage.bin new file mode 100644 index 00000000..29c590a6 Binary files /dev/null and b/orebfuscator-plugin/src/test/resources/simple-bit-storage.bin differ