Paper/patches/server/0367-Anti-Xray.patch
Nassim Jahnke ada930bf8d
Updated Upstream (Bukkit/CraftBukkit) (#6872)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
cfd18bd0 SPIGOT-6436: Add Player#stopAllSounds

CraftBukkit Changes:
b58f4299 SPIGOT-6436: Add Player#stopAllSounds
eb191612 SPIGOT-6783: Items do not appear in custom anvil inventories
376edf4f SPIGOT-6779: Fix LivingEntity#attack for Player entities
747a73ec SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
2021-11-05 15:08:24 +01:00

1454 lines
75 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: stonar96 <minecraft.stonar96@gmail.com>
Date: Mon, 20 Aug 2018 03:03:58 +0200
Subject: [PATCH] Anti-Xray
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 27de7cdc4f3bb5604162f2cc114fcd27a6ed4a14..4dde7eec6afad0b4340631482dc23a9796736d43 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -1,7 +1,9 @@
package com.destroystokyo.paper;
+import java.util.Arrays;
import java.util.List;
+import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.spigotmc.SpigotWorldConfig;
@@ -489,5 +491,40 @@ public class PaperWorldConfig {
private void lightQueueSize() {
lightQueueSize = getInt("light-queue-size", lightQueueSize);
}
+
+ public boolean antiXray;
+ public EngineMode engineMode;
+ public int maxBlockHeight;
+ public int updateRadius;
+ public boolean lavaObscures;
+ public boolean usePermission;
+ public List<String> hiddenBlocks;
+ public List<String> replacementBlocks;
+ private void antiXray() {
+ antiXray = getBoolean("anti-xray.enabled", false);
+ engineMode = EngineMode.getById(getInt("anti-xray.engine-mode", EngineMode.HIDE.getId()));
+ engineMode = engineMode == null ? EngineMode.HIDE : engineMode;
+ maxBlockHeight = getInt("anti-xray.max-block-height", 64);
+ updateRadius = getInt("anti-xray.update-radius", 2);
+ lavaObscures = getBoolean("anti-xray.lava-obscures", false);
+ usePermission = getBoolean("anti-xray.use-permission", false);
+ hiddenBlocks = getList("anti-xray.hidden-blocks", Arrays.asList("copper_ore", "deepslate_copper_ore", "gold_ore", "deepslate_gold_ore", "iron_ore", "deepslate_iron_ore",
+ "coal_ore", "deepslate_coal_ore", "lapis_ore", "deepslate_lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "deepslate_diamond_ore",
+ "redstone_ore", "deepslate_redstone_ore", "clay", "emerald_ore", "deepslate_emerald_ore", "ender_chest"));
+ replacementBlocks = getList("anti-xray.replacement-blocks", Arrays.asList("stone", "oak_planks", "deepslate"));
+ if (PaperConfig.version < 19) {
+ hiddenBlocks.remove("lit_redstone_ore");
+ int index = replacementBlocks.indexOf("planks");
+ if (index != -1) {
+ replacementBlocks.set(index, "oak_planks");
+ }
+ set("anti-xray.hidden-blocks", hiddenBlocks);
+ set("anti-xray.replacement-blocks", replacementBlocks);
+ }
+ log("Anti-Xray: " + (antiXray ? "enabled" : "disabled") + " / Engine Mode: " + engineMode.getDescription() + " / Up to " + ((maxBlockHeight >> 4) << 4) + " blocks / Update Radius: " + updateRadius);
+ if (antiXray && usePermission) {
+ Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues");
+ }
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
@@ -0,0 +1,51 @@
+package com.destroystokyo.paper.antixray;
+
+public final class BitStorageReader {
+
+ private byte[] buffer;
+ private int bits;
+ private int mask;
+ private int longInBufferIndex;
+ private int bitInLongIndex;
+ private long current;
+
+ public void setBuffer(byte[] buffer) {
+ this.buffer = buffer;
+ }
+
+ public void setBits(int bits) {
+ this.bits = bits;
+ mask = (1 << bits) - 1;
+ }
+
+ public void setIndex(int index) {
+ longInBufferIndex = index;
+ bitInLongIndex = 0;
+ init();
+ }
+
+ private void init() {
+ if (buffer.length > longInBufferIndex + 7) {
+ current = ((((long) buffer[longInBufferIndex]) << 56)
+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
+ | (((long) buffer[longInBufferIndex + 7] & 0xff)));
+ }
+ }
+
+ public int read() {
+ if (bitInLongIndex + bits > 64) {
+ bitInLongIndex = 0;
+ longInBufferIndex += 8;
+ init();
+ }
+
+ int value = (int) (current >>> bitInLongIndex) & mask;
+ bitInLongIndex += bits;
+ return value;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
@@ -0,0 +1,79 @@
+package com.destroystokyo.paper.antixray;
+
+public final class BitStorageWriter {
+
+ private byte[] buffer;
+ private int bits;
+ private long mask;
+ private int longInBufferIndex;
+ private int bitInLongIndex;
+ private long current;
+ private boolean dirty;
+
+ public void setBuffer(byte[] buffer) {
+ this.buffer = buffer;
+ }
+
+ public void setBits(int bits) {
+ this.bits = bits;
+ mask = (1L << bits) - 1;
+ }
+
+ public void setIndex(int index) {
+ longInBufferIndex = index;
+ bitInLongIndex = 0;
+ init();
+ }
+
+ private void init() {
+ if (buffer.length > longInBufferIndex + 7) {
+ current = ((((long) buffer[longInBufferIndex]) << 56)
+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
+ | (((long) buffer[longInBufferIndex + 7] & 0xff)));
+ }
+
+ dirty = false;
+ }
+
+ public void flush() {
+ if (dirty && buffer.length > longInBufferIndex + 7) {
+ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff);
+ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff);
+ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff);
+ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff);
+ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff);
+ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff);
+ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff);
+ buffer[longInBufferIndex + 7] = (byte) (current & 0xff);
+ }
+ }
+
+ public void write(int value) {
+ if (bitInLongIndex + bits > 64) {
+ flush();
+ bitInLongIndex = 0;
+ longInBufferIndex += 8;
+ init();
+ }
+
+ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
+ dirty = true;
+ bitInLongIndex += bits;
+ }
+
+ public void skip() {
+ bitInLongIndex += bits;
+
+ if (bitInLongIndex > 64) {
+ flush();
+ bitInLongIndex = bits;
+ longInBufferIndex += 8;
+ init();
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
new file mode 100644
index 0000000000000000000000000000000000000000..280ece653cdda74e9c8fab4e9e5b3a952901cb01
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
@@ -0,0 +1,46 @@
+package com.destroystokyo.paper.antixray;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ServerPlayerGameMode;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+
+public class ChunkPacketBlockController {
+
+ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController();
+
+ protected ChunkPacketBlockController() {
+
+ }
+
+ public BlockState[] getPresetBlockStates(Level level, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) {
+ return null;
+ }
+
+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
+ return false;
+ }
+
+ public ChunkPacketInfo<BlockState> getChunkPacketInfo(ClientboundLevelChunkPacket chunkPacket, LevelChunk chunk) {
+ return null;
+ }
+
+ public void modifyBlocks(ClientboundLevelChunkPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
+ chunkPacket.setReady(true);
+ }
+
+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
+
+ }
+
+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight) {
+
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
new file mode 100644
index 0000000000000000000000000000000000000000..58052a5f773b5882acd90bc0214a36e8a512817c
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
@@ -0,0 +1,635 @@
+package com.destroystokyo.paper.antixray;
+
+import com.destroystokyo.paper.PaperWorldConfig;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.core.Registry;
+import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ServerPlayerGameMode;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.EntityBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.*;
+import org.bukkit.Bukkit;
+
+import java.util.*;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.IntSupplier;
+
+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController {
+
+ private final Executor executor;
+ private final EngineMode engineMode;
+ private final int maxBlockHeight;
+ private final int updateRadius;
+ private final boolean usePermission;
+ private final BlockState[] presetBlockStates;
+ private final BlockState[] presetBlockStatesFull;
+ private final BlockState[] presetBlockStatesStone;
+ private final BlockState[] presetBlockStatesNetherrack;
+ private final BlockState[] presetBlockStatesEndStone;
+ private final int[] presetBlockStateBitsGlobal;
+ private final int[] presetBlockStateBitsStoneGlobal;
+ private final int[] presetBlockStateBitsNetherrackGlobal;
+ private final int[] presetBlockStateBitsEndStoneGlobal;
+ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
+ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
+ private final LevelChunkSection[] emptyNearbyChunkSections = {LevelChunk.EMPTY_SECTION, LevelChunk.EMPTY_SECTION, LevelChunk.EMPTY_SECTION, LevelChunk.EMPTY_SECTION};
+ private final int maxBlockHeightUpdatePosition;
+
+ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) {
+ this.executor = executor;
+ PaperWorldConfig paperWorldConfig = level.paperConfig;
+ engineMode = paperWorldConfig.engineMode;
+ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4;
+ updateRadius = paperWorldConfig.updateRadius;
+ usePermission = paperWorldConfig.usePermission;
+ List<String> toObfuscate;
+
+ if (engineMode == EngineMode.HIDE) {
+ toObfuscate = paperWorldConfig.hiddenBlocks;
+ presetBlockStates = null;
+ presetBlockStatesFull = null;
+ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()};
+ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()};
+ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()};
+ presetBlockStateBitsGlobal = null;
+ presetBlockStateBitsStoneGlobal = new int[]{LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())};
+ presetBlockStateBitsNetherrackGlobal = new int[]{LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())};
+ presetBlockStateBitsEndStoneGlobal = new int[]{LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())};
+ } else {
+ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks);
+ List<BlockState> presetBlockStateList = new LinkedList<>();
+
+ for (String id : paperWorldConfig.hiddenBlocks) {
+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
+
+ if (block != null && !(block instanceof EntityBlock)) {
+ toObfuscate.add(id);
+ presetBlockStateList.add(block.defaultBlockState());
+ }
+ }
+
+ // The doc of the LinkedHashSet(Collection<? extends E>) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation
+ Set<BlockState> presetBlockStateSet = new LinkedHashSet<>();
+ // Therefore addAll(Collection<? extends E>) is used, which guarantees this order in the doc
+ presetBlockStateSet.addAll(presetBlockStateList);
+ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]);
+ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]);
+ presetBlockStatesStone = null;
+ presetBlockStatesNetherrack = null;
+ presetBlockStatesEndStone = null;
+ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length];
+
+ for (int i = 0; i < presetBlockStatesFull.length; i++) {
+ presetBlockStateBitsGlobal[i] = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]);
+ }
+
+ presetBlockStateBitsStoneGlobal = null;
+ presetBlockStateBitsNetherrackGlobal = null;
+ presetBlockStateBitsEndStoneGlobal = null;
+ }
+
+ for (String id : toObfuscate) {
+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
+
+ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
+ if (block != null && !block.defaultBlockState().isAir()) {
+ // Replace all block states of a specified block
+ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
+ obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
+ }
+ }
+ }
+
+ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0));
+ BlockPos zeroPos = new BlockPos(0, 0, 0);
+
+ for (int i = 0; i < solidGlobal.length; i++) {
+ BlockState blockState = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.valueFor(i);
+
+ if (blockState != null) {
+ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos)
+ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState();
+ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used
+ // shulker box checks TE.
+ }
+ }
+
+ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1;
+ }
+
+ private int getPresetBlockStatesFullLength() {
+ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length;
+ }
+
+ @Override
+ public BlockState[] getPresetBlockStates(Level level, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) {
+ // Return the block states to be added to the paletted containers so that they can be used for obfuscation
+ if (chunkSection.bottomBlockY() < maxBlockHeight) {
+ if (engineMode == EngineMode.HIDE) {
+ return switch (level.getWorld().getEnvironment()) {
+ case NETHER -> presetBlockStatesNetherrack;
+ case THE_END -> presetBlockStatesEndStone;
+ default -> presetBlockStatesStone;
+ };
+ }
+
+ return presetBlockStates;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
+ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass");
+ }
+
+ @Override
+ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkPacket chunkPacket, LevelChunk chunk) {
+ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later
+ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this);
+ }
+
+ @Override
+ public void modifyBlocks(ClientboundLevelChunkPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
+ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) {
+ chunkPacket.setReady(true);
+ return;
+ }
+
+ if (!Bukkit.isPrimaryThread()) {
+ // Plugins?
+ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
+ return;
+ }
+
+ LevelChunk chunk = chunkPacketInfo.getChunk();
+ int x = chunk.getPos().x;
+ int z = chunk.getPos().z;
+ Level level = chunk.level;
+ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1));
+ executor.execute((Runnable) chunkPacketInfo);
+ }
+
+ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal)
+ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
+ private final ThreadLocal<int[]> presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]);
+ private static final ThreadLocal<boolean[]> solid = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+ private static final ThreadLocal<boolean[]> obfuscate = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
+ private static final ThreadLocal<boolean[][]> current = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private static final ThreadLocal<boolean[][]> next = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private static final ThreadLocal<boolean[][]> nextNext = ThreadLocal.withInitial(() -> new boolean[16][16]);
+
+ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
+ int[] presetBlockStateBits = this.presetBlockStateBits.get();
+ boolean[] solid = ChunkPacketBlockControllerAntiXray.solid.get();
+ boolean[] obfuscate = ChunkPacketBlockControllerAntiXray.obfuscate.get();
+ boolean[][] current = ChunkPacketBlockControllerAntiXray.current.get();
+ boolean[][] next = ChunkPacketBlockControllerAntiXray.next.get();
+ boolean[][] nextNext = ChunkPacketBlockControllerAntiXray.nextNext.get();
+ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
+ BitStorageReader bitStorageReader = new BitStorageReader();
+ BitStorageWriter bitStorageWriter = new BitStorageWriter();
+ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4];
+ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk();
+ Level level = chunk.level;
+ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount() - 1);
+ boolean[] solidTemp = null;
+ boolean[] obfuscateTemp = null;
+ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer());
+ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer());
+ int numberOfBlocks = presetBlockStateBits.length;
+ // Keep the lambda expressions as simple as possible. They are used very frequently.
+ IntSupplier random = numberOfBlocks == 1 ? (() -> 0) : new IntSupplier() {
+ private int state;
+
+ {
+ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
+ }
+
+ @Override
+ public int getAsInt() {
+ // https://en.wikipedia.org/wiki/Xorshift
+ state ^= state << 13;
+ state ^= state >>> 17;
+ state ^= state << 5;
+ // https://www.pcg-random.org/posts/bounded-rands.html
+ return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
+ }
+ };
+
+ for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) {
+ if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) {
+ int[] presetBlockStateBitsTemp;
+
+ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) {
+ if (engineMode == EngineMode.HIDE) {
+ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) {
+ case NETHER -> presetBlockStateBitsNetherrackGlobal;
+ case THE_END -> presetBlockStateBitsEndStoneGlobal;
+ default -> presetBlockStateBitsStoneGlobal;
+ };
+ } else {
+ presetBlockStateBitsTemp = presetBlockStateBitsGlobal;
+ }
+ } else {
+ // If it's presetBlockStates, use this.presetBlockStatesFull instead
+ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex);
+ presetBlockStateBitsTemp = presetBlockStateBits;
+
+ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) {
+ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]);
+ }
+ }
+
+ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
+
+ // Check if the chunk section below was not obfuscated
+ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) {
+ // If so, initialize some stuff
+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal);
+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal);
+ // Read the blocks of the upper layer of the chunk section below if it exists
+ LevelChunkSection belowChunkSection = null;
+ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == LevelChunk.EMPTY_SECTION;
+
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ current[z][x] = true;
+ next[z][x] = skipFirstLayer || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(belowChunkSection.getBlockState(x, 15, z))];
+ }
+ }
+
+ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section
+ bitStorageWriter.setBits(0);
+ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random);
+ }
+
+ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
+ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? LevelChunk.EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex];
+ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? LevelChunk.EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex];
+ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? LevelChunk.EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex];
+ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? LevelChunk.EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex];
+
+ // Obfuscate all layers of the current chunk section except the upper one
+ for (int y = 0; y < 15; y++) {
+ boolean[][] temp = current;
+ current = next;
+ next = nextNext;
+ nextNext = temp;
+ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+ }
+
+ // Check if the chunk section above doesn't need obfuscation
+ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) {
+ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists
+ LevelChunkSection aboveChunkSection;
+
+ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != LevelChunk.EMPTY_SECTION) {
+ boolean[][] temp = current;
+ current = next;
+ next = nextNext;
+ nextNext = temp;
+
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ if (!solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(aboveChunkSection.getBlockState(x, 0, z))]) {
+ current[z][x] = true;
+ }
+ }
+ }
+
+ // There is nothing to read anymore
+ bitStorageReader.setBits(0);
+ solid[0] = true;
+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+ }
+ } else {
+ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section
+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1));
+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1));
+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal);
+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal);
+ boolean[][] temp = current;
+ current = next;
+ next = nextNext;
+ nextNext = temp;
+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
+ }
+
+ bitStorageWriter.flush();
+ }
+ }
+
+ chunkPacketInfoAntiXray.getChunkPacket().setReady(true);
+ }
+
+ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) {
+ // First block of first line
+ int bits = bitStorageReader.read();
+
+ if (nextNext[0][0] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[0][1] = true;
+ next[1][0] = true;
+ } else {
+ if (current[0][0] || nearbyChunkSections[2] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[2].getBlockState(0, y, 15))] || nearbyChunkSections[0] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[0].getBlockState(15, y, 0))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[0][0] = true;
+ }
+
+ // First line
+ for (int x = 1; x < 15; x++) {
+ bits = bitStorageReader.read();
+
+ if (nextNext[0][x] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[0][x - 1] = true;
+ next[0][x + 1] = true;
+ next[1][x] = true;
+ } else {
+ if (current[0][x] || nearbyChunkSections[2] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[2].getBlockState(x, y, 15))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[0][x] = true;
+ }
+ }
+
+ // Last block of first line
+ bits = bitStorageReader.read();
+
+ if (nextNext[0][15] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[0][14] = true;
+ next[1][15] = true;
+ } else {
+ if (current[0][15] || nearbyChunkSections[2] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[2].getBlockState(15, y, 15))] || nearbyChunkSections[1] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[1].getBlockState(0, y, 0))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[0][15] = true;
+ }
+
+ // All inner lines
+ for (int z = 1; z < 15; z++) {
+ // First block
+ bits = bitStorageReader.read();
+
+ if (nextNext[z][0] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[z][1] = true;
+ next[z - 1][0] = true;
+ next[z + 1][0] = true;
+ } else {
+ if (current[z][0] || nearbyChunkSections[0] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[0].getBlockState(15, y, z))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[z][0] = true;
+ }
+
+ // All inner blocks
+ for (int x = 1; x < 15; x++) {
+ bits = bitStorageReader.read();
+
+ if (nextNext[z][x] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[z][x - 1] = true;
+ next[z][x + 1] = true;
+ next[z - 1][x] = true;
+ next[z + 1][x] = true;
+ } else {
+ if (current[z][x]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[z][x] = true;
+ }
+ }
+
+ // Last block
+ bits = bitStorageReader.read();
+
+ if (nextNext[z][15] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[z][14] = true;
+ next[z - 1][15] = true;
+ next[z + 1][15] = true;
+ } else {
+ if (current[z][15] || nearbyChunkSections[1] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[1].getBlockState(0, y, z))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[z][15] = true;
+ }
+ }
+
+ // First block of last line
+ bits = bitStorageReader.read();
+
+ if (nextNext[15][0] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[15][1] = true;
+ next[14][0] = true;
+ } else {
+ if (current[15][0] || nearbyChunkSections[3] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[3].getBlockState(0, y, 0))] || nearbyChunkSections[0] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[0].getBlockState(15, y, 15))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[15][0] = true;
+ }
+
+ // Last line
+ for (int x = 1; x < 15; x++) {
+ bits = bitStorageReader.read();
+
+ if (nextNext[15][x] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[15][x - 1] = true;
+ next[15][x + 1] = true;
+ next[14][x] = true;
+ } else {
+ if (current[15][x] || nearbyChunkSections[3] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[3].getBlockState(x, y, 0))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[15][x] = true;
+ }
+ }
+
+ // Last block of last line
+ bits = bitStorageReader.read();
+
+ if (nextNext[15][15] = !solid[bits]) {
+ bitStorageWriter.skip();
+ next[15][14] = true;
+ next[14][15] = true;
+ } else {
+ if (current[15][15] || nearbyChunkSections[3] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[3].getBlockState(15, y, 0))] || nearbyChunkSections[1] == LevelChunk.EMPTY_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(nearbyChunkSections[1].getBlockState(0, y, 15))]) {
+ bitStorageWriter.skip();
+ } else {
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
+ }
+ }
+
+ if (!obfuscate[bits]) {
+ next[15][15] = true;
+ }
+ }
+
+ private boolean[] readPalette(Palette<BlockState> palette, boolean[] temp, boolean[] global) {
+ if (palette == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) {
+ return global;
+ }
+
+ BlockState blockState;
+
+ for (int i = 0; (blockState = palette.valueFor(i)) != null; i++) {
+ temp[i] = global[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)];
+ }
+
+ return temp;
+ }
+
+ @Override
+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
+ if (oldBlockState != null && solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) {
+ updateNearbyBlocks(level, blockPos);
+ }
+ }
+
+ @Override
+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight) {
+ if (blockPos.getY() <= maxBlockHeightUpdatePosition) {
+ updateNearbyBlocks(serverPlayerGameMode.level, blockPos);
+ }
+ }
+
+ private void updateNearbyBlocks(Level level, BlockPos blockPos) {
+ if (updateRadius >= 2) {
+ BlockPos temp = blockPos.west();
+ updateBlock(level, temp);
+ updateBlock(level, temp.west());
+ updateBlock(level, temp.below());
+ updateBlock(level, temp.above());
+ updateBlock(level, temp.north());
+ updateBlock(level, temp.south());
+ updateBlock(level, temp = blockPos.east());
+ updateBlock(level, temp.east());
+ updateBlock(level, temp.below());
+ updateBlock(level, temp.above());
+ updateBlock(level, temp.north());
+ updateBlock(level, temp.south());
+ updateBlock(level, temp = blockPos.below());
+ updateBlock(level, temp.below());
+ updateBlock(level, temp.north());
+ updateBlock(level, temp.south());
+ updateBlock(level, temp = blockPos.above());
+ updateBlock(level, temp.above());
+ updateBlock(level, temp.north());
+ updateBlock(level, temp.south());
+ updateBlock(level, temp = blockPos.north());
+ updateBlock(level, temp.north());
+ updateBlock(level, temp = blockPos.south());
+ updateBlock(level, temp.south());
+ } else if (updateRadius == 1) {
+ updateBlock(level, blockPos.west());
+ updateBlock(level, blockPos.east());
+ updateBlock(level, blockPos.below());
+ updateBlock(level, blockPos.above());
+ updateBlock(level, blockPos.north());
+ updateBlock(level, blockPos.south());
+ } else {
+ // Do nothing if updateRadius <= 0 (test mode)
+ }
+ }
+
+ private void updateBlock(Level level, BlockPos blockPos) {
+ BlockState blockState = level.getTypeIfLoaded(blockPos);
+
+ if (blockState != null && obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) {
+ ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
+ }
+ }
+
+ public enum EngineMode {
+
+ HIDE(1, "hide ores"),
+ OBFUSCATE(2, "obfuscate");
+
+ private final int id;
+ private final String description;
+
+ EngineMode(int id, String description) {
+ this.id = id;
+ this.description = description;
+ }
+
+ public static EngineMode getById(int id) {
+ for (EngineMode engineMode : values()) {
+ if (engineMode.id == id) {
+ return engineMode;
+ }
+ }
+
+ return null;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bc2d4daffa8e9e71c3bf496d2cf1a2b7f3c6a4b
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java
@@ -0,0 +1,80 @@
+package com.destroystokyo.paper.antixray;
+
+import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.Palette;
+
+public class ChunkPacketInfo<T> {
+
+ private final ClientboundLevelChunkPacket chunkPacket;
+ private final LevelChunk chunk;
+ private final int[] bits;
+ private final Object[] palettes;
+ private final int[] indexes;
+ private final Object[][] presetValues;
+ private byte[] buffer;
+
+ public ChunkPacketInfo(ClientboundLevelChunkPacket chunkPacket, LevelChunk chunk) {
+ this.chunkPacket = chunkPacket;
+ this.chunk = chunk;
+ int sections = chunk.getSectionsCount();
+ bits = new int[sections];
+ palettes = new Object[sections];
+ indexes = new int[sections];
+ presetValues = new Object[sections][];
+ }
+
+ public ClientboundLevelChunkPacket getChunkPacket() {
+ return chunkPacket;
+ }
+
+ public LevelChunk getChunk() {
+ return chunk;
+ }
+
+ public byte[] getBuffer() {
+ return buffer;
+ }
+
+ public void setBuffer(byte[] buffer) {
+ this.buffer = buffer;
+ }
+
+ public int getBits(int chunkSectionIndex) {
+ return bits[chunkSectionIndex];
+ }
+
+ public void setBits(int chunkSectionIndex, int bits) {
+ this.bits[chunkSectionIndex] = bits;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Palette<T> getPalette(int chunkSectionIndex) {
+ return (Palette<T>) palettes[chunkSectionIndex];
+ }
+
+ public void setPalette(int chunkSectionIndex, Palette<T> palette) {
+ palettes[chunkSectionIndex] = palette;
+ }
+
+ public int getIndex(int chunkSectionIndex) {
+ return indexes[chunkSectionIndex];
+ }
+
+ public void setIndex(int chunkSectionIndex, int index) {
+ indexes[chunkSectionIndex] = index;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T[] getPresetValues(int chunkSectionIndex) {
+ return (T[]) presetValues[chunkSectionIndex];
+ }
+
+ public void setPresetValues(int chunkSectionIndex, T[] presetValues) {
+ this.presetValues[chunkSectionIndex] = presetValues;
+ }
+
+ public boolean isWritten(int chunkSectionIndex) {
+ return bits[chunkSectionIndex] != 0;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
new file mode 100644
index 0000000000000000000000000000000000000000..02324a59ac21db5349fe2a74248b2c6f92fa8233
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
@@ -0,0 +1,29 @@
+package com.destroystokyo.paper.antixray;
+
+import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo<BlockState> implements Runnable {
+
+ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray;
+ private LevelChunk[] nearbyChunks;
+
+ public ChunkPacketInfoAntiXray(ClientboundLevelChunkPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) {
+ super(chunkPacket, chunk);
+ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray;
+ }
+
+ public LevelChunk[] getNearbyChunks() {
+ return nearbyChunks;
+ }
+
+ public void setNearbyChunks(LevelChunk... nearbyChunks) {
+ this.nearbyChunks = nearbyChunks;
+ }
+
+ @Override
+ public void run() {
+ chunkPacketBlockControllerAntiXray.obfuscate(this);
+ }
+}
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
index c28879f32b004f36ff746ea2274f91ddd9501e71..60d72e488bc77cd913328be400ca374a873b4561 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
@@ -37,7 +37,24 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
}
// Paper end
- public ClientboundLevelChunkPacket(LevelChunk chunk) {
+ // Paper start - Async-Anti-Xray - Ready flag for the connection
+ private volatile boolean ready;
+
+ @Override
+ public boolean isReady() {
+ return this.ready;
+ }
+
+ public void setReady(boolean ready) {
+ this.ready = ready;
+ }
+ // Paper end
+
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public ClientboundLevelChunkPacket(LevelChunk chunk) { this(chunk, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
+ public ClientboundLevelChunkPacket(LevelChunk chunk, boolean modifyBlocks) {
+ com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo = modifyBlocks ? chunk.level.chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null;
+ // Paper end
ChunkPos chunkPos = chunk.getPos();
this.x = chunkPos.x;
this.z = chunkPos.z;
@@ -51,7 +68,14 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
this.biomes = chunk.getBiomes().writeBiomes();
this.buffer = new byte[this.calculateChunkSize(chunk)];
- this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk);
+
+ // Paper start - Anti-Xray - Add chunk packet info
+ if (chunkPacketInfo != null) {
+ chunkPacketInfo.setBuffer(this.buffer);
+ }
+
+ this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo);
+ // Paper end
this.blockEntitiesTags = Lists.newArrayList();
int totalTileEntities = 0; // Paper
@@ -71,6 +95,7 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
this.blockEntitiesTags.add(compoundTag);
}
+ chunk.level.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
}
public ClientboundLevelChunkPacket(FriendlyByteBuf buf) {
@@ -120,7 +145,10 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
return byteBuf;
}
- public BitSet extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) {
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public BitSet extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { return extractChunkData(buf, chunk, null); } // Notice for updates: Please make sure this method isn't used anywhere
+ public BitSet extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
+ // Paper end
BitSet bitSet = new BitSet();
LevelChunkSection[] levelChunkSections = chunk.getSections();
int i = 0;
@@ -129,7 +157,7 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
LevelChunkSection levelChunkSection = levelChunkSections[i];
if (levelChunkSection != LevelChunk.EMPTY_SECTION && !levelChunkSection.isEmpty()) {
bitSet.set(i);
- levelChunkSection.write(buf);
+ levelChunkSection.write(buf, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info
}
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 1197c510211b725742d48152443178eea94058a8..5bbdf56179d2e5fd0b42c37c84c9d4bc5faaee24 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -1534,7 +1534,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) {
if (packets[0] == null) {
- packets[0] = new ClientboundLevelChunkPacket(chunk);
+ packets[0] = new ClientboundLevelChunkPacket(chunk, chunk.level.chunkPacketBlockController.shouldModify(player, chunk)); // Paper - Ani-Xray - Bypass
packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, (BitSet) null, (BitSet) null, true);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index bf499e746652127700950cdbbf18df692987b389..4b9b708812ee502815dd6879412e8b942595344e 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -378,7 +378,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
// Add env and gen to constructor, WorldData -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
// Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
- super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, biomeProvider, env);
+ super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, biomeProvider, env, executor); // Paper - Anti-Xray - Pass executor
this.pvpMode = minecraftserver.isPvpAllowed();
this.convertable = convertable_conversionsession;
this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelPath.toFile());
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
index 6219634a57976a6a0a9b32ed08d56107d6b5d1c3..31e3534d6e15f91d781fabb0670e53ef66cb8977 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -49,7 +49,7 @@ import org.bukkit.event.player.PlayerInteractEvent;
public class ServerPlayerGameMode {
private static final Logger LOGGER = LogManager.getLogger();
- protected ServerLevel level;
+ public ServerLevel level; // Paper - Anti-Xray - protected -> public
protected final ServerPlayer player;
private GameType gameModeForPlayer;
@Nullable
@@ -315,6 +315,8 @@ public class ServerPlayerGameMode {
}
}
+
+ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight); // Paper - Anti-Xray
}
public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) {
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 13efcae91a863f9c7255472d1c45ce16be371c6d..92313e70c30ca5acc0e7b54d55a972f024bad162 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -166,6 +166,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper
+ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
public static BlockPos lastPhysicsProblem; // Spigot
@@ -187,7 +188,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
return this.typeKey;
}
- protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env) {
+ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
this.generator = gen;
@@ -261,6 +262,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper
this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime);
this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime);
+ this.chunkPacketBlockController = this.paperConfig.antiXray ?
+ new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor)
+ : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
}
// Paper start
@@ -451,6 +455,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// CraftBukkit end
BlockState iblockdata1 = chunk.setType(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
+ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray
if (iblockdata1 == null) {
// CraftBukkit start - remove blockstate if failed (or the same)
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
index 149ac5ec368b53a9a5e9208bd49a3c9453625d9c..12d11a249c759e99568a76c791cc0d65adfcfe94 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -74,7 +74,7 @@ public interface ChunkAccess extends BlockGetter, FeatureAccess {
default LevelChunkSection getOrCreateSection(int yIndex) {
LevelChunkSection[] levelChunkSections = this.getSections();
if (levelChunkSections[yIndex] == LevelChunk.EMPTY_SECTION) {
- levelChunkSections[yIndex] = new LevelChunkSection(this.getSectionYFromSectionIndex(yIndex));
+ levelChunkSections[yIndex] = new LevelChunkSection(this.getSectionYFromSectionIndex(yIndex), this, this.getLevel(), true); // Paper - Anti-Xray - Add parameters
}
return levelChunkSections[yIndex];
diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
index 69c2454533e6f21c70792b555ec02c6bc6d169b3..f9e0e109497d685a9d88d2fa8892287b9fa97443 100644
--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java
@@ -86,7 +86,7 @@ public class EmptyLevelChunk extends LevelChunk {
private static final Biome[] EMPTY_BIOMES = new Biome[0];
public EmptyChunkBiomeContainer(Level world) {
- super(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), world, EMPTY_BIOMES);
+ super(net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), world, EMPTY_BIOMES); // Paper - Anti-Xray - The world isnt ready yet, use server singleton for registry
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 8141935e2ee58bbb58c6b5cfdef5a9a88d7658ec..515e28eea8cbab261320352ee0db9b877807f3ed 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -456,7 +456,7 @@ public class LevelChunk implements ChunkAccess {
return null;
}
- chunksection = new LevelChunkSection(SectionPos.blockToSectionCoord(i));
+ chunksection = new LevelChunkSection(SectionPos.blockToSectionCoord(i), this, this.level, true); // Paper - Anti-Xray - Add parameters
this.sections[j] = chunksection;
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index 5fd66020a937b641e2a060cf38df731a43f3bf55..c9fefeef19bd46ade51b23eadb5eef3a88024ea1 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -20,16 +20,25 @@ public class LevelChunkSection {
private short tickingFluidCount;
final PalettedContainer<BlockState> states; // Paper - package-private
- public LevelChunkSection(int yOffset) {
- this(yOffset, (short)0, (short)0, (short)0);
+ // Paper start - Anti-Xray - Add parameters
+ @Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
+ public LevelChunkSection(int yOffset, ChunkAccess chunk, net.minecraft.world.level.Level level, boolean initializeBlocks) {
+ this(yOffset, (short) 0, (short) 0, (short) 0, chunk, level, initializeBlocks);
+ // Paper end
}
- public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) {
+ // Paper start - Anti-Xray - Add parameters
+ @Deprecated public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) { // Notice for updates: Please make sure this constructor isn't used anywhere
+ this(yOffset, nonEmptyBlockCount, randomTickableBlockCount, nonEmptyFluidCount, null, null, true);
+ }
+ public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount, ChunkAccess chunk, net.minecraft.world.level.Level level, boolean initializeBlocks) {
+ // Paper end
this.bottomBlockY = getBottomBlockY(yOffset);
this.nonEmptyBlockCount = nonEmptyBlockCount;
this.tickingBlockCount = randomTickableBlockCount;
this.tickingFluidCount = nonEmptyFluidCount;
- this.states = new PalettedContainer<>(GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState());
+ this.states = new PalettedContainer<>(GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState(),
+ level == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, chunk, this, initializeBlocks), initializeBlocks); // Paper - Anti-Xray - Add preset block states
}
public static int getBottomBlockY(int chunkPos) {
@@ -147,9 +156,12 @@ public class LevelChunkSection {
this.states.read(buf);
}
- public void write(FriendlyByteBuf buf) {
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public void write(FriendlyByteBuf buf) { write(buf, null); } // Notice for updates: Please make sure this method isn't used anywhere
+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo) {
+ // Paper end
buf.writeShort(this.nonEmptyBlockCount);
- this.states.write(buf);
+ this.states.write(buf, chunkPacketInfo, this.bottomBlockY); // Paper - Anti-Xray - Add chunk packet info
}
public int getSerializedSize() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
index bb8fd88aebb550edec8c679622a02a595cbc6694..ac51089aae57a5f1d2411367ff177e058702894c 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -28,6 +28,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
private final Function<CompoundTag, T> reader;
private final Function<T, CompoundTag> writer;
private final T defaultValue;
+ private final T[] presetValues; // Paper - Anti-Xray - Add preset values
protected BitStorage storage;
private Palette<T> palette;
private int bits;
@@ -48,14 +49,46 @@ public class PalettedContainer<T> implements PaletteResize<T> {
this.lock.release();
}
- public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement) {
+ // Paper start - Anti-Xray - Add preset values
+ @Deprecated public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement) { // Notice for updates: Please make sure this constructor isn't used anywhere
+ this(fallbackPalette, idList, elementDeserializer, elementSerializer, defaultElement, null, true);
+ }
+ public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> elementSerializer, T defaultElement, T[] presetValues, boolean initialize) {
+ // Paper end
this.globalPalette = fallbackPalette;
this.registry = idList;
this.reader = elementDeserializer;
this.writer = elementSerializer;
this.defaultValue = defaultElement;
- this.setBits(4);
+ // Paper start - Anti-Xray - Add preset values
+ this.presetValues = presetValues;
+
+ if (initialize) {
+ if (presetValues == null) {
+ // Default
+ this.setBits(4);
+ } else {
+ // Count the number of required bits
+ // Preset values: presetValues.length - 1
+ // Air: + 1
+ // Extra: + 15
+ // Air and extra correspond to the default behavior this.setBits(4)
+ this.setBits(32 - Integer.numberOfLeadingZeros(presetValues.length + 15));
+ this.addPresetValues();
+ }
+ }
+ // Paper end
+ }
+
+ // Paper start - Anti-Xray - Add preset values
+ private void addPresetValues() {
+ if (this.presetValues != null && this.palette != this.globalPalette) {
+ for (T presetValue : this.presetValues) {
+ this.palette.idFor(presetValue);
+ }
+ }
}
+ // Paper end
private static int getIndex(int x, int y, int z) {
return y << 8 | z << 4 | x;
@@ -84,6 +117,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
BitStorage bitStorage = this.storage;
Palette<T> palette = this.palette;
this.setBits(newSize);
+ this.addPresetValues(); // Paper - Anti-Xray - Add preset values
for(int i = 0; i < bitStorage.getSize(); ++i) {
T object = palette.valueFor(bitStorage.get(i));
@@ -153,17 +187,38 @@ public class PalettedContainer<T> implements PaletteResize<T> {
this.palette.read(buf);
buf.readLongArray(this.storage.getRaw());
+ // Paper start - Anti-Xray - Add preset values
+ // If there are many preset values this may require several resize operations
+ // This can be avoided by calculating the required bits in advance, as it is done in #read(ListTag, long[])
+ // However, this method is only used by the client, so it does not matter
+ this.addPresetValues();
+ // Paper end
} finally {
this.release();
}
}
- public void write(FriendlyByteBuf buf) {
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public void write(FriendlyByteBuf buf) { write(buf, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere
+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int bottomBlockY) {
+ // Paper end
try {
this.acquire();
buf.writeByte(this.bits);
this.palette.write(buf);
+
+ // Paper start - Anti-Xray - Add chunk packet info
+ if (chunkPacketInfo != null) {
+ // Bottom block to 0 based chunk section index
+ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection();
+ chunkPacketInfo.setBits(chunkSectionIndex, this.bits);
+ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette);
+ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getRaw().length));
+ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues);
+ }
+ // Paper end
+
buf.writeLongArray(this.storage.getRaw());
} finally {
this.release();
@@ -174,12 +229,14 @@ public class PalettedContainer<T> implements PaletteResize<T> {
public void read(ListTag paletteNbt, long[] data) {
try {
this.acquire();
- int i = Math.max(4, Mth.ceillog2(paletteNbt.size()));
- if (i != this.bits) {
+ // Paper - Anti-Xray - TODO: Should this.presetValues.length just be added here (faster) or should the contents be compared to calculate the size (less RAM)?
+ int i = Math.max(4, Mth.ceillog2(paletteNbt.size() + (this.presetValues == null ? 0 : this.presetValues.length))); // Paper - Anti-Xray - Calculate the size with preset values
+ if (true || i != this.bits) { // Paper - Anti-Xray - Not initialized yet
this.setBits(i);
}
this.palette.read(paletteNbt);
+ this.addPresetValues(); // Paper - Anti-Xray - Add preset values
int j = data.length * 64 / 4096;
if (this.palette == this.globalPalette) {
Palette<T> palette = new HashMapPalette<>(this.registry, i, this.dummyPaletteResize, this.reader, this.writer);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
index 670e4f65680ca36fba1c84cb334c470ea8fa9b60..79f2b3942a3ccccd8fe8719db12de458212e8659 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
@@ -134,7 +134,7 @@ public class ChunkSerializer {
byte b0 = nbttagcompound2.getByte("Y");
if (nbttagcompound2.contains("Palette", 9) && nbttagcompound2.contains("BlockStates", 12)) {
- LevelChunkSection chunksection = new LevelChunkSection(b0);
+ LevelChunkSection chunksection = new LevelChunkSection(b0, null, world, false); // Paper - Anti-Xray - Add parameters and don't initialize because it's done in the line below internally
chunksection.getStates().read(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates"));
chunksection.recalcBlockCounts();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index 7467de2da7cbe79df49d9bd95df1891810a1431b..46136c5daa1b4ea9103c736cc4b035195177368e 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -51,7 +51,7 @@ public class CraftChunk implements Chunk {
private final ServerLevel worldServer;
private final int x;
private final int z;
- private static final PalettedContainer<net.minecraft.world.level.block.state.BlockState> emptyBlockIDs = new LevelChunkSection(0).getStates();
+ private static final PalettedContainer<net.minecraft.world.level.block.state.BlockState> emptyBlockIDs = new LevelChunkSection(0, null, null, true).getStates(); // Paper - Anti-Xray - Add parameters
private static final byte[] emptyLight = new byte[2048];
public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) {
@@ -332,7 +332,7 @@ public class CraftChunk implements Chunk {
CompoundTag data = new CompoundTag();
cs[i].getStates().write(data, "Palette", "BlockStates");
- PalettedContainer blockids = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState()); // TODO: snapshot whole ChunkSection
+ PalettedContainer blockids = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState(), null, false); // TODO: snapshot whole ChunkSection // Paper - Anti-Xray - Add no preset block states and don't initialize because it's done in the line below internally
blockids.read(data.getList("Palette", CraftMagicNumbers.NBT.TAG_COMPOUND), data.getLongArray("BlockStates"));
sectionBlockIDs[i] = blockids;
diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
index 7bfd803d11976dba9ace79edc76bb083a2eccf38..a48c659c02c6c33a8efdac6daf9c9a0708f05071 100644
--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
@@ -25,9 +25,11 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData {
private final LevelChunkSection[] sections;
private Set<BlockPos> tiles;
private final Set<BlockPos> lights = new HashSet<>();
+ private World world; // Paper - Anti-Xray - Add parameters
public OldCraftChunkData(World world) {
this(world.getMinHeight(), world.getMaxHeight());
+ this.world = world; // Paper - Anti-Xray - Add parameters
}
/* pp for tests */ OldCraftChunkData(int minHeight, int maxHeight) {
@@ -177,7 +179,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData {
int offset = (y - this.minHeight) >> 4;
LevelChunkSection section = this.sections[offset];
if (create && section == null) {
- this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4));
+ this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), null, world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null, true); // Paper - Anti-Xray - Add parameters
}
return section;
}