Paper/patches/server/0993-Anti-Xray.patch

1637 lines
89 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: stonar96 <minecraft.stonar96@gmail.com>
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 abb4c32e8b35de332fa517523e8c598ea3275def..6f497b95fd870a32c56590c00b2b39f88c51ecb9 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 5063eb6d4a24600262c32d2c9eb5fb5bf8fa354e..692a01b52a71e26887ee42cbd5fd64b0a81bfc99 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 8a5f245fc98514b66216dde234650bfc0adc24b4..461c27292af06a5150de8ec263d0c8527e8c5278 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -419,7 +419,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 1ff8eed2aceb105a41ce7ecbba6a4efd104573bd..29368a97c6348e2d1d6241db3d57e84b9537231a 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -172,6 +172,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;
@@ -197,7 +198,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;
@@ -283,6 +284,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
@@ -558,6 +560,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 e67ebc8517a1afb0c7fe23f19a781942dded3241..539b36bde9cba3a44184eba36df9aa4c345a5b84 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 e47b00912fc76e9639f9c51d96e6d39da3c963e3..105f925fb1a78879d2eb618f0c672c8b9a759dd9 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -55,7 +55,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 a76f966d72b749f1706363d33caa351b54e9fa14..c9137151f0d2978adb432c40da68689465d2325d 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2622,7 +2622,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 49d8fd363391d085067253504c146c6fcf297d8f..bfb178c69026e9759e9afaebb9da141b62d1f144 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -530,11 +530,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;
}