diff options
author | Nassim Jahnke <[email protected]> | 2024-01-24 15:57:53 +0100 |
---|---|---|
committer | Nassim Jahnke <[email protected]> | 2024-01-24 17:14:57 +0100 |
commit | a4a08b73423459a58a055d442c07668e7d327b5b (patch) | |
tree | bac1321f819dcf378dc58a41349aefca387eb98f /patches/server/1001-Anti-Xray.patch | |
parent | d405ff12559716a1a838ec1252b949b2bdc50383 (diff) | |
download | Paper-a4a08b73423459a58a055d442c07668e7d327b5b.tar.gz Paper-a4a08b73423459a58a055d442c07668e7d327b5b.zip |
[ci skip] Move chunk system patch a bit back
Diffstat (limited to 'patches/server/1001-Anti-Xray.patch')
-rw-r--r-- | patches/server/1001-Anti-Xray.patch | 1636 |
1 files changed, 1636 insertions, 0 deletions
diff --git a/patches/server/1001-Anti-Xray.patch b/patches/server/1001-Anti-Xray.patch new file mode 100644 index 0000000000..9b8c973f5a --- /dev/null +++ b/patches/server/1001-Anti-Xray.patch @@ -0,0 +1,1636 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 <[email protected]> +Date: Thu, 25 Nov 2021 13:27:51 +0100 +Subject: [PATCH] Anti-Xray + + +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..52d2e2b744f91914802506e52a07161729bbcf3a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +@@ -0,0 +1,45 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++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.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public class ChunkPacketBlockController { ++ ++ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); ++ ++ protected ChunkPacketBlockController() { ++ ++ } ++ ++ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) { ++ return null; ++ } ++ ++ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { ++ return false; ++ } ++ ++ public ChunkPacketInfo<BlockState> getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ return null; ++ } ++ ++ public void modifyBlocks(ClientboundLevelChunkWithLightPacket 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, int sequence) { ++ ++ } ++} +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..e7fe98ea30ae6d0baea3ec1f9f98a89502a49a12 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +1,676 @@ ++package com.destroystokyo.paper.antixray; ++ ++import io.papermc.paper.configuration.WorldConfiguration; ++import io.papermc.paper.configuration.type.EngineMode; ++import java.util.ArrayList; ++import java.util.LinkedHashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ThreadLocalRandom; ++import java.util.function.IntSupplier; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++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.biome.Biomes; ++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.EmptyLevelChunk; ++import net.minecraft.world.level.chunk.GlobalPalette; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.MissingPaletteEntryException; ++import net.minecraft.world.level.chunk.Palette; ++import org.bukkit.Bukkit; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); ++ private static final LevelChunkSection EMPTY_SECTION = null; ++ 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[] presetBlockStatesDeepslate; ++ private final BlockState[] presetBlockStatesNetherrack; ++ private final BlockState[] presetBlockStatesEndStone; ++ private final int[] presetBlockStateBitsGlobal; ++ private final int[] presetBlockStateBitsStoneGlobal; ++ private final int[] presetBlockStateBitsDeepslateGlobal; ++ 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 = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION}; ++ private final int maxBlockHeightUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) { ++ this.executor = executor; ++ WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray; ++ engineMode = paperWorldConfig.engineMode; ++ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ List<Block> toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ presetBlockStates = null; ++ presetBlockStatesFull = null; ++ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()}; ++ presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()}; ++ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()}; ++ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()}; ++ presetBlockStateBitsGlobal = null; ++ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())}; ++ presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())}; ++ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())}; ++ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List<BlockState> presetBlockStateList = new LinkedList<>(); ++ ++ for (Block block : paperWorldConfig.hiddenBlocks) { ++ ++ if (!(block instanceof EntityBlock)) { ++ toObfuscate.add(block); ++ 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; ++ presetBlockStatesDeepslate = null; ++ presetBlockStatesNetherrack = null; ++ presetBlockStatesEndStone = null; ++ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length]; ++ ++ for (int i = 0; i < presetBlockStatesFull.length; i++) { ++ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]); ++ } ++ ++ presetBlockStateBitsStoneGlobal = null; ++ presetBlockStateBitsDeepslateGlobal = null; ++ presetBlockStateBitsNetherrackGlobal = null; ++ presetBlockStateBitsEndStoneGlobal = null; ++ } ++ ++ for (Block block : toObfuscate) { ++ ++ // 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[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; ++ } ++ } ++ } ++ ++ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS)); ++ BlockPos zeroPos = new BlockPos(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ BlockState blockState = 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 && blockState.getBlock() != Blocks.MANGROVE_ROOTS || 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, ChunkPos chunkPos, int chunkSectionY) { ++ // Return the block states to be added to the paletted containers so that they can be used for obfuscation ++ int bottomBlockY = chunkSectionY << 4; ++ ++ if (bottomBlockY < maxBlockHeight) { ++ if (engineMode == EngineMode.HIDE) { ++ return switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStatesNetherrack; ++ case THE_END -> presetBlockStatesEndStone; ++ default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : 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(ClientboundLevelChunkWithLightPacket 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(ClientboundLevelChunkWithLightPacket 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.getLevel(); ++ ((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[][]> NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ ++ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { ++ int[] presetBlockStateBits = this.presetBlockStateBits.get(); ++ boolean[] solid = SOLID.get(); ++ boolean[] obfuscate = OBFUSCATE.get(); ++ boolean[][] current = CURRENT.get(); ++ boolean[][] next = NEXT.get(); ++ boolean[][] nextNext = NEXT_NEXT.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.getLevel(); ++ 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. ++ LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() { ++ // engine-mode: 3 ++ private int state; ++ private int next; ++ ++ { ++ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; ++ } ++ ++ @Override ++ public void nextLayer() { ++ // https://en.wikipedia.org/wiki/Xorshift ++ state ^= state << 13; ++ state ^= state >>> 17; ++ state ^= state << 5; ++ // https://www.pcg-random.org/posts/bounded-rands.html ++ next = (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); ++ } ++ ++ @Override ++ public int getAsInt() { ++ return next; ++ } ++ } : new LayeredIntSupplier() { ++ // engine-mode: 2 ++ 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) instanceof GlobalPalette) { ++ if (engineMode == EngineMode.HIDE) { ++ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStateBitsNetherrackGlobal; ++ case THE_END -> presetBlockStateBitsEndStoneGlobal; ++ default -> chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : 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++) { ++ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible ++ // For more details see the comments in the readPalette method ++ 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]) == EMPTY_SECTION; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ current[z][x] = true; ++ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, 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 ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? 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; ++ random.nextLayer(); ++ 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]) != 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 (isTransparent(aboveChunkSection, x, 0, z)) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ bitStorageReader.setBits(0); ++ solid[0] = true; ++ random.nextLayer(); ++ 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; ++ random.nextLayer(); ++ 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] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 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] || isTransparent(nearbyChunkSections[2], 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] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 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] || isTransparent(nearbyChunkSections[0], 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] || isTransparent(nearbyChunkSections[1], 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] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 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] || isTransparent(nearbyChunkSections[3], 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] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) { ++ if (chunkSection == EMPTY_SECTION) { ++ return true; ++ } ++ ++ try { ++ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))]; ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care and treat the block as transparent ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur ++ return true; ++ } ++ } ++ ++ private boolean[] readPalette(Palette<BlockState> palette, boolean[] temp, boolean[] global) { ++ if (palette instanceof GlobalPalette) { ++ return global; ++ } ++ ++ try { ++ for (int i = 0; i < palette.getSize(); i++) { ++ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))]; ++ } ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care because we at least see the state as it was when the chunk packet was created ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here ++ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { ++ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[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, int sequence) { ++ 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.getBlockStateIfLoaded(blockPos); ++ ++ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) { ++ ((ServerLevel) level).getChunkSource().blockChanged(blockPos); ++ } ++ } ++ ++ @FunctionalInterface ++ private interface LayeredIntSupplier extends IntSupplier { ++ default void nextLayer() { ++ ++ } ++ } ++} +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..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d +--- /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.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.Palette; ++ ++public class ChunkPacketInfo<T> { ++ ++ private final ClientboundLevelChunkWithLightPacket 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(ClientboundLevelChunkWithLightPacket 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 ClientboundLevelChunkWithLightPacket 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..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d +--- /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.ClientboundLevelChunkWithLightPacket; ++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(ClientboundLevelChunkWithLightPacket 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/ClientboundChunksBiomesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java +index 6cff1a98dc7cf33947ec760dbc3d3d0ec5db5f6c..51f647de153255c919b1440338cf1b3e2d6b5dbf 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java +@@ -63,8 +63,10 @@ public record ClientboundChunksBiomesPacket(List<ClientboundChunksBiomesPacket.C + } + + public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ++ int chunkSectionIndex = 0; // Paper - Anti-Xray + for(LevelChunkSection levelChunkSection : chunk.getSections()) { +- levelChunkSection.getBiomes().write(buf); ++ levelChunkSection.getBiomes().write(buf, null, chunkSectionIndex); // Paper - Anti-Xray ++ chunkSectionIndex++; // Paper - Anti-Xray + } + + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index 89e3163b0301f8414c9400a6e00cdd85841fe2e8..74b5cec09d953a247bc5aba3c4232a4c28ea1046 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -25,7 +25,10 @@ public class ClientboundLevelChunkPacketData { + private final byte[] buffer; + private final List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData; + +- public ClientboundLevelChunkPacketData(LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } ++ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) { ++ // Paper end + this.heightmaps = new CompoundTag(); + + for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) { +@@ -35,7 +38,14 @@ public class ClientboundLevelChunkPacketData { + } + + this.buffer = new byte[calculateChunkSize(chunk)]; +- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk); ++ ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(this.buffer); ++ } ++ ++ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo); ++ // Paper end + this.blockEntitiesData = Lists.newArrayList(); + + for(Map.Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) { +@@ -85,9 +95,15 @@ public class ClientboundLevelChunkPacketData { + return byteBuf; + } + +- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } ++ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) { ++ int chunkSectionIndex = 0; ++ + for(LevelChunkSection levelChunkSection : chunk.getSections()) { +- levelChunkSection.write(buf); ++ levelChunkSection.write(buf, chunkPacketInfo, chunkSectionIndex); ++ chunkSectionIndex++; ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +index 26e46d751c8f8162c2bafe2fc109fc91dc4b7c0f..6412dff5ed0505f62dd5b71ab9606257858a7317 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa + private final int z; + private final ClientboundLevelChunkPacketData chunkData; + private final ClientboundLightUpdatePacketData lightData; ++ // Paper start - Async-Anti-Xray - Ready flag for the connection ++ private volatile boolean ready; + +- public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits) { ++ @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 @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits) { this(chunk, lightProvider, skyBits, blockBits, true); } ++ public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean modifyBlocks) { + ChunkPos chunkPos = chunk.getPos(); + this.x = chunkPos.x; + this.z = chunkPos.z; +- this.chunkData = new ClientboundLevelChunkPacketData(chunk); ++ com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; ++ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); ++ // Paper end + this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits); ++ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + } + + public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 4357d45305cdf82659fcc0df9fa42b1ae1029cc1..811c9c7970dfef290acdf0bbd803b27ca81a4767 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -570,7 +570,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Holder holder = worlddimension.type(); // CraftBukkit - decompile error + + // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error +- super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess()))); // Paper - create paper world configs ++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess())), executor); // Paper - create paper world configs; Async-Anti-Xray: Pass executor + this.pvpMode = minecraftserver.isPvpAllowed(); + this.convertable = convertable_conversionsession; + this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index bc0df2e1401b13737e23faae01ba16edc2a10d21..97490f58aace9bfb6d034e9a97393142f73043d1 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 = LogUtils.getLogger(); +- protected ServerLevel level; ++ public ServerLevel level; // Paper - Anti-Xray - protected -> public + protected final ServerPlayer player; + private GameType gameModeForPlayer; + @Nullable +@@ -326,6 +326,8 @@ public class ServerPlayerGameMode { + } + + } ++ ++ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray + } + + public void destroyAndAck(BlockPos pos, int sequence, String reason) { +diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +index f3b96a921e7d085b51da62fa5493384a7ded1f9d..12f2bf95d3ea3d29f6b4b9ec38a92f7102daa4a1 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java ++++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +@@ -88,7 +88,10 @@ public class PlayerChunkSender { + + public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public + handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); +- handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null)); ++ // Paper start - Anti-Xray ++ final boolean shouldModify = world.chunkPacketBlockController.shouldModify(handler.player, chunk); ++ handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null, shouldModify)); ++ // Paper end - Anti-Xray + // Paper start - PlayerChunkLoadEvent + if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { + new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 34bd7e81f9480c97afd69c11eca216b03e6a5a1f..eae6121a2f3fb33146b0a625cc82c8bce8efc91b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -418,7 +418,7 @@ public abstract class PlayerList { + .getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); + player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( + new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), +- worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null) ++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, true) + ); + } + // Paper end - Send empty chunk +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9b574d4aa1faf5814942641c48afe503d6f83bc4..6dafbbed864feebe73c648cf38cee6d64e9eea85 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -177,6 +177,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end - add paper world config + ++ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -202,7 +203,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public abstract ResourceKey<LevelStem> getTypeKey(); + +- protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config ++ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config; Async-Anti-Xray: Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.generator = gen; +@@ -288,6 +289,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper - Option to keep spawn chunks loaded + this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); ++ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + } + + // Paper start - Cancel hit for vanished players +@@ -563,6 +565,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + + BlockState iblockdata1 = chunk.setBlockState(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 5e8d2e4245757a0889645ea79ee68afb53f7dde4..f7e5e016a7028a9196e689e950805b0d5b31fe38 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -152,17 +152,17 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + } + } + +- ChunkAccess.replaceMissingSections(biomeRegistry, this.sections); ++ this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method + // CraftBukkit start + this.biomeRegistry = biomeRegistry; + } + public final Registry<Biome> biomeRegistry; + // CraftBukkit end + +- private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) { ++ private void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) { // Paper - Anti-Xray - static -> non-static + for (int i = 0; i < sectionArray.length; ++i) { + if (sectionArray[i] == null) { +- sectionArray[i] = new LevelChunkSection(biomeRegistry); ++ sectionArray[i] = new LevelChunkSection(biomeRegistry, this.levelHeightAccessor instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) this.levelHeightAccessor : null, this.chunkPos, this.levelHeightAccessor.getSectionYFromSectionIndex(i)); // Paper start - Anti-Xray - Add parameters + } + } + +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 6a5756bd333d9b221e7770842e5114d295cb7f1d..2eeb0c78f2b717b59542b6b668371558ae2fcc25 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -91,7 +91,7 @@ public class LevelChunk extends ChunkAccess { + } + + public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { +- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); ++ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry + this.tickersInLevel = Maps.newHashMap(); + this.level = (ServerLevel) world; // CraftBukkit - type + this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); +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 b606e33f8b64eaba28c008cc353d88aa45549e31..8852263cb6faec1b68326145aa30e5cd36d066e7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -33,9 +33,12 @@ public class LevelChunkSection { + this.recalcBlockCounts(); + } + +- public LevelChunkSection(Registry<Biome> biomeRegistry) { +- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); +- this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(Registry<Biome> biomeRegistry) { this(biomeRegistry, null, null, 0); } ++ public LevelChunkSection(Registry<Biome> biomeRegistry, net.minecraft.world.level.Level level, net.minecraft.world.level.ChunkPos chunkPos, int chunkSectionY) { ++ // Paper end ++ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, chunkPos, chunkSectionY)); // Paper - Anti-Xray - Add preset block states ++ this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + public BlockState getBlockState(int x, int y, int z) { +@@ -172,10 +175,13 @@ public class LevelChunkSection { + this.biomes = datapaletteblock; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } ++ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo, int chunkSectionIndex) { + buf.writeShort(this.nonEmptyBlockCount); +- this.states.write(buf); +- this.biomes.write(buf); ++ this.states.write(buf, chunkPacketInfo, chunkSectionIndex); ++ this.biomes.write(buf, null, chunkSectionIndex); ++ // Paper end + } + + 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 dfae0918079425df92d958b04275be8ae60d4b60..0f930f8355ea99d1cb1a8d27edc1c224588f852f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -30,6 +30,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + return 0; + }; + public final IdMap<T> registry; ++ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values + private volatile PalettedContainer.Data<T> data; + private final PalettedContainer.Strategy strategy; + // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused +@@ -42,14 +43,19 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + +- public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +- PalettedContainerRO.Unpacker<T, PalettedContainer<T>> unpacker = PalettedContainer::unpack; ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); } ++ public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ PalettedContainerRO.Unpacker<T, PalettedContainer<T>> unpacker = (idListx, paletteProviderx, serialized) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues); ++ }; ++ // Paper end + return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker); + } + + public static <T> Codec<PalettedContainerRO<T>> codecRO(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { + PalettedContainerRO.Unpacker<T, PalettedContainerRO<T>> unpacker = (idListx, paletteProviderx, serialized) -> { +- return unpack(idListx, paletteProviderx, serialized).map((result) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, null).map((result) -> { // Paper - Anti-Xray - Add preset values + return result; + }); + }; +@@ -66,19 +72,52 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + }); + } + +- public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } ++ public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; + this.registry = idList; + this.strategy = paletteProvider; + this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); ++ ++ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) { ++ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us ++ // We readd this here but in a smarter way than it was before ++ int maxSize = 1 << dataProvider.bits(); ++ ++ for (T presetValue : presetValues) { ++ if (this.data.palette.getSize() >= maxSize) { ++ java.util.Set<T> allValues = new java.util.HashSet<>(paletteEntries); ++ allValues.addAll(Arrays.asList(presetValues)); ++ int newBits = Mth.ceillog2(allValues.size()); ++ ++ if (newBits > dataProvider.bits()) { ++ this.onResize(newBits, null); ++ } ++ ++ break; ++ } ++ ++ this.data.palette.idFor(presetValue); ++ } ++ } ++ // Paper end + } + +- private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data) { ++ // Paper start - Anti-Xray - Add preset values ++ private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.registry = idList; + this.strategy = paletteProvider; + this.data = data; + } + +- public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } ++ public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.strategy = paletteProvider; + this.registry = idList; + this.data = this.createOrReuseData((PalettedContainer.Data<T>)null, 0); +@@ -93,11 +132,33 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + @Override + public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data<T> data = this.data; ++ ++ // Paper start - Anti-Xray - Add preset values ++ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) { ++ int duplicates = 0; ++ List<T> presetValues = Arrays.asList(this.presetValues); ++ duplicates += presetValues.contains(object) ? 1 : 0; ++ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0; ++ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates); ++ } ++ + PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); + this.data = data2; +- return data2.palette.idFor(object); ++ this.addPresetValues(); ++ return object == null ? -1 : data2.palette.idFor(object); ++ // Paper end ++ } ++ ++ // Paper start - Anti-Xray - Add preset values ++ private void addPresetValues() { ++ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) { ++ for (T presetValue : this.presetValues) { ++ this.data.palette.idFor(presetValue); ++ } ++ } + } ++ // Paper end + + public T getAndSet(int x, int y, int z, T value) { + this.acquire(); +@@ -167,25 +228,34 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + data.palette.read(buf); + buf.readLongArray(data.storage.getRaw()); + this.data = data; ++ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server) + } finally { + this.release(); + } + + } + ++ // Paper start - Anti-Xray; Add chunk packet info ++ @Override ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } + @Override +- public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize ++ public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) { // Paper - Synchronize + this.acquire(); + + try { +- this.data.write(buf); ++ this.data.write(buf, chunkPacketInfo, chunkSectionIndex); ++ ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); ++ } ++ // Paper end + } finally { + this.release(); + } + + } + +- private static <T> DataResult<PalettedContainer<T>> unpack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData<T> serialized) { ++ private static <T> DataResult<PalettedContainer<T>> unpack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData<T> serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values + List<T> list = serialized.paletteEntries(); + int i = paletteProvider.size(); + int j = paletteProvider.calculateBitsForSerialization(idList, list.size()); +@@ -225,7 +295,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + } + } + +- return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list)); ++ return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -285,12 +355,12 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + } + + public PalettedContainer<T> copy() { +- return new PalettedContainer<>(this.registry, this.strategy, this.data.copy()); ++ return new PalettedContainer<>(this.registry, this.strategy, this.data.copy(), this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override + public PalettedContainer<T> recreate() { +- return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy); ++ return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy, this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -334,9 +404,18 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + return 1 + this.palette.getSerializedSize() + VarInt.getByteSize(this.storage.getRaw().length) + this.storage.getRaw().length * 8; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) { + buf.writeByte(this.storage.getBits()); + this.palette.write(buf); ++ ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); ++ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); ++ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + VarInt.getByteSize(this.storage.getRaw().length)); ++ } ++ // Paper end ++ + buf.writeLongArray(this.storage.getRaw()); + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +index 9a2bf744abd8916d492e901be889223591bac3fd..1dd415c96d17eff8e7555c33d3c52e57f2559fa5 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +@@ -14,7 +14,10 @@ public interface PalettedContainerRO<T> { + + void getAll(Consumer<T> action); + +- void write(FriendlyByteBuf buf); ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf); ++ void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex); ++ // Paper end + + int getSerializedSize(); + +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 5aff6454b5b1c7834adca8f1234ec4848aa3709c..4bef691ad22b6638847aebb471a3c28522c7a461 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 +@@ -71,7 +71,7 @@ import org.slf4j.Logger; + + public class ChunkSerializer { + +- public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); ++ public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String TAG_UPGRADE_DATA = "UpgradeData"; + private static final String BLOCK_TICKS_TAG = "block_ticks"; +@@ -172,16 +172,20 @@ public class ChunkSerializer { + if (k >= 0 && k < achunksection.length) { + Logger logger; + PalettedContainer datapaletteblock; ++ // Paper start - Anti-Xray - Add preset block states ++ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0); + + if (nbttagcompound1.contains("block_states", 10)) { +- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { ++ Codec<PalettedContainer<BlockState>> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); ++ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { + ChunkSerializer.logErrors(chunkPos, b0, s); + }); + logger = ChunkSerializer.LOGGER; + Objects.requireNonNull(logger); + datapaletteblock = (PalettedContainer) ((DataResult<PalettedContainer<BlockState>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); ++ // Paper end + } + + PalettedContainer object; // CraftBukkit - read/write +@@ -194,7 +198,7 @@ public class ChunkSerializer { + Objects.requireNonNull(logger); + object = ((DataResult<PalettedContainer<Holder<Biome>>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write +@@ -429,7 +433,7 @@ public class ChunkSerializer { + + // CraftBukkit start - read/write + private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) { +- return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); ++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index d4e0ef75dd12709a0dcf9193821c30b8943e6c36..fd702027e62eb38d51fb7c46ef268e9bb94e1e92 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -53,7 +53,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 PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ private static final PalettedContainer<net.minecraft.world.level.block.state.BlockState> emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states + private static final byte[] FULL_LIGHT = new byte[2048]; + private static final byte[] EMPTY_LIGHT = new byte[2048]; + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index be285f915e669a6b9e29ab80cec4a46546f2d09b..ade9c05017f47e904fec7edb4a8dd2e14280ca14 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2610,7 +2610,7 @@ public final class CraftServer implements Server { + public ChunkGenerator.ChunkData createChunkData(World world) { + Preconditions.checkArgument(world != null, "World cannot be null"); + ServerLevel handle = ((CraftWorld) world).getHandle(); +- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME)); ++ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME), world); // Paper - Anti-Xray - Add parameters + } + + // Paper start - Allow delegation to vanilla chunk gen +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 29fa4ebcb1375a64023ada3d7055bc8085846bf4..cd559e6aaf4bd2bd322c953bbb186135489ddcaa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -522,11 +522,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { + List<ServerPlayer> playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); + if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader + +- ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); ++ // Paper start - Anti-Xray - Bypass ++ Map<Object, ClientboundLevelChunkWithLightPacket> refreshPackets = new HashMap<>(); + for (ServerPlayer player : playersInRange) { + if (player.connection == null) continue; + +- player.connection.send(refreshPacket); ++ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); ++ player.connection.send(refreshPackets.computeIfAbsent(shouldModify, s -> { // Use connection to prevent creating firing event ++ return new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, (Boolean) s); ++ })); ++ // Paper end + } + // Paper - rewrite player chunk loader + +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +index 9b640705f2c810160aa7fea5006429ec41d0c858..44a010590e830fd238cf6fdda443e28b72022e66 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + private final Registry<net.minecraft.world.level.biome.Biome> biomes; + private Set<BlockPos> tiles; + private final Set<BlockPos> lights = new HashSet<>(); ++ // Paper start - Anti-Xray - Add parameters ++ private final org.bukkit.World world; + +- public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes) { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes) { this(minHeight, maxHeight, biomes, null); } ++ public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes, org.bukkit.World world) { ++ this.world = world; ++ // Paper end + this.minHeight = minHeight; + this.maxHeight = maxHeight; + this.biomes = biomes; +@@ -176,7 +181,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(this.biomes); ++ this.sections[offset] = section = new LevelChunkSection(this.biomes, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null, null, offset + (this.minHeight >> 4)); // Paper - Anti-Xray - Add parameters + } + return section; + } |