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

Bukkit Changes:
f29cb801 Separate checkstyle-suppressions file is not required
86f99bbe SPIGOT-7540, PR-946: Add ServerTickManager API
d4119585 SPIGOT-6903, PR-945: Add BlockData#getMapColor
b7a2ed41 SPIGOT-7530, PR-947: Add Player#removeResourcePack
9dd56255 SPIGOT-7527, PR-944: Add WindCharge#explode()
994a6163 Attempt upgrade of resolver libraries

CraftBukkit Changes:
b3b43a6ad Add Checkstyle check for unused imports
13fb3358e SPIGOT-7544: Scoreboard#getEntries() doesn't get entries but class names
3dda99c06 SPIGOT-7540, PR-1312: Add ServerTickManager API
2ab4508c0 SPIGOT-6903, PR-1311: Add BlockData#getMapColor
1dbdbbed4 PR-1238: Remove unnecessary sign ticking
659728d2a MC-264285, SPIGOT-7439, PR-1237: Fix unbreakable flint and steel is completely consumed while igniting creeper
e37e29ce0 Increase outdated build delay
c00438b39 SPIGOT-7530, PR-1313: Add Player#removeResourcePack
492dd80ce SPIGOT-7527, PR-1310: Add WindCharge#explode()
e11fbb9d7 Upgrade MySQL driver
9f3a0bd2a Attempt upgrade of resolver libraries
60d16d7ca PR-1306: Centralize Bukkit and Minecraft entity conversion

Spigot Changes:
06d602e7 Rebuild patches
2023-12-16 18:09:28 -08:00

1624 lines
88 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 0711ad682206e783fd0a1e566828670321f297e2..bf6cdc08367fc26716e7904162a21e63fecab3ed 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
@@ -33,7 +33,10 @@ public class ClientboundLevelChunkPacketData {
}
// Paper end
- 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()) {
@@ -43,7 +46,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();
int totalTileEntities = 0; // Paper
@@ -103,9 +113,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 be89e5b8c1ea7f85aef267a15986affa5fa1fd4b..43472855136f26b282d94fd241853d862a19ae17 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 2a3d4a041345d5478916f4d2c5c3c382fca5758f..9c1b670a986f4507aab124225e7da68fef6e156c 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -568,7 +568,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
+ 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 - 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 79020edc9fac79e8b186d0f57f956d2189d3dc8e..b7e6d8441e8444c36919c126a8adeaeed02df08c 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
@@ -321,6 +321,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 5de5209e04d631bd6a50e28e8d3abebf148252c1..19b3f4fa7678a038bf25efc2a8b46ddad0193fde 100644
--- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java
+++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java
@@ -85,7 +85,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
ChunkPos chunkPos = chunk.getPos();
DebugPackets.sendPoiPacketsForChunk(world, chunkPos);
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 2e5b01cc9c438a2bee7eb770e4c266425a9d287f..35fd928c87403d98a80b6c13fa9df83496576c8f 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -178,6 +178,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
// Paper end
+ 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;
@@ -196,7 +197,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
+ 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 - 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
this.generator = gen;
@@ -282,6 +283,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper
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
@@ -547,6 +549,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 d9d2bf31a0e7ad374aa62197c71626a1abfbaf71..4a5491a79281b7cddf5ad5f060567f174b89d14b 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 2c3ea88bac229df67bd742e16d2106d80bcc8889..4eeb719b40ff1c18a7cdda7ecc6b135dbedd626e 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 1219200cd915d6239a32a2bd09d325cd8fa9b346..98e720a21432fff7fce8653095a56e2ef56f1d30 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");
@@ -42,14 +43,19 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
this.threadingDetector.checkAndUnlock();
}
- 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 int onResize(int newBits, T object) {
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 void write(FriendlyByteBuf buf) {
+ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int chunkSectionIndex) {
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 982fb3ef071d6a66f16744717e8e146bef6d9e8c..02beedb84a8bec001270116c6ce496db012e6b8a 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";
@@ -162,16 +162,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
@@ -184,7 +188,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
@@ -412,7 +416,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 545b14f02ac72dda30891d681eba585d19fd5e1d..6dc7e23e96aaffb912611a9dbd41459cb8489056 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 83242862a49e39c40dd8467f1d2a0ac74ced8343..64d827c7e7f9cf76f9db900333b987a626ab53cc 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2374,7 +2374,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
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 4b3d04d891edda8c02470bae189fbf17eb4e8a36..fcf2571a7aa9e93a82171d4a8ae8c11ee7452994 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -422,11 +422,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 a8b23d7eae867be95835bb76f9739a3d1d729973..5fbb9d2c4e419a3f31a8420437978606d75e5b62 100644
--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
@@ -26,8 +26,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;
@@ -175,7 +180,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;
}