From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: stonar96 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..aabad39d13ead83042ec2e4dd7f4ed4966af650d --- /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 bottomBlockY) { + return null; + } + + public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { + return false; + } + + public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { + return null; + } + + public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { + chunkPacket.setReady(true); + } + + public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { + + } + + public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight) { + + } +} diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java new file mode 100644 index 0000000000000000000000000000000000000000..a5d43901595f864c8a5dd5d013aa42d7f294e489 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -0,0 +1,692 @@ +package com.destroystokyo.paper.antixray; + +import io.papermc.paper.configuration.WorldConfiguration; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.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.*; +import org.bukkit.Bukkit; +import org.spongepowered.configurate.serialize.ScalarSerializer; +import org.spongepowered.configurate.serialize.SerializationException; + +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.IntSupplier; +import java.util.function.Predicate; + +public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { + + private static final Palette 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 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 presetBlockStateList = new LinkedList<>(); + + for (String id : paperWorldConfig.hiddenBlocks) { + Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); + + if (block != null && !(block instanceof EntityBlock)) { + toObfuscate.add(id); + presetBlockStateList.add(block.defaultBlockState()); + } + } + + // The doc of the LinkedHashSet(Collection) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation + Set presetBlockStateSet = new LinkedHashSet<>(); + // Therefore addAll(Collection) 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 (String id : toObfuscate) { + Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); + + // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void + if (block != null && !block.defaultBlockState().isAir()) { + // Replace all block states of a specified block + for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { + obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; + } + } + } + + EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).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 || 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 bottomBlockY) { + // Return the block states to be added to the paletted containers so that they can be used for obfuscation + 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 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 presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]); + private static final ThreadLocal SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); + private static final ThreadLocal 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 CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]); + private static final ThreadLocal NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); + private static final ThreadLocal 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. + IntSupplier random = numberOfBlocks == 1 ? (() -> 0) : new IntSupplier() { + private int state; + + { + while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; + } + + @Override + public int getAsInt() { + // https://en.wikipedia.org/wiki/Xorshift + state ^= state << 13; + state ^= state >>> 17; + state ^= state << 5; + // https://www.pcg-random.org/posts/bounded-rands.html + return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); + } + }; + + for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) { + if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) { + int[] presetBlockStateBitsTemp; + + if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) 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; + 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; + obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); + } + } else { + // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section + bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1)); + bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1)); + solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal); + obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); + boolean[][] temp = current; + current = next; + next = nextNext; + nextNext = temp; + obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); + } + + bitStorageWriter.flush(); + } + } + + chunkPacketInfoAntiXray.getChunkPacket().setReady(true); + } + + private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { + // First block of first line + int bits = bitStorageReader.read(); + + if (nextNext[0][0] = !solid[bits]) { + bitStorageWriter.skip(); + next[0][1] = true; + next[1][0] = true; + } else { + if (current[0][0] || 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 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) { + 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); + } + } + + public enum EngineMode { + + HIDE(1, "hide ores"), + OBFUSCATE(2, "obfuscate"); + + public static final ScalarSerializer SERIALIZER = new Serializer(); + + private final int id; + private final String description; + + EngineMode(int id, String description) { + this.id = id; + this.description = description; + } + + public static EngineMode getById(int id) { + for (EngineMode engineMode : values()) { + if (engineMode.id == id) { + return engineMode; + } + } + + return null; + } + + public int getId() { + return id; + } + + public String getDescription() { + return description; + } + + static class Serializer extends ScalarSerializer { + + Serializer() { + super(EngineMode.class); + } + + @Override + public EngineMode deserialize(Type type, Object obj) throws SerializationException { + if (obj instanceof Integer num) { + return Objects.requireNonNullElse(EngineMode.getById(num), HIDE); + } + throw new SerializationException(obj + " is not of a valid type (" + type + ") for this node"); + } + + @Override + protected Object serialize(EngineMode item, Predicate> typeSupported) { + return item.getId(); + } + } + } +} 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 { + + 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 getPalette(int chunkSectionIndex) { + return (Palette) palettes[chunkSectionIndex]; + } + + public void setPalette(int chunkSectionIndex, Palette 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 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/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index be64437ba7630fe069aaea502932b514c85162f8..922ce2f0f4fa10c585939d6682b1a0a8612de0ea 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -1,5 +1,6 @@ package io.papermc.paper.configuration; +import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; import com.google.common.base.Suppliers; import com.google.common.collect.Table; import com.mojang.logging.LogUtils; @@ -191,6 +192,7 @@ public class PaperConfigurations extends Configurations(new TypeToken>() {}, Registry.ENTITY_TYPE_REGISTRY, true)) .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) 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 7cf356a700e47686e093e2f2f880af919dc0414a..e902b437ee089907b34ae30c0a6bdf1d42e1e674 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 public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { + // Paper end this.heightmaps = new CompoundTag(); for(Map.Entry entry : chunk.getHeightmaps()) { @@ -43,7 +46,13 @@ 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 +112,12 @@ public class ClientboundLevelChunkPacketData { return byteBuf; } - public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } // Notice for updates: Please make sure this function isn't used anywhere + public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { for(LevelChunkSection levelChunkSection : chunk.getSections()) { - levelChunkSection.write(buf); + levelChunkSection.write(buf, chunkPacketInfo); + // 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 7825d6f0fdcfda6212cff8033ec55fb7db236154..2072aa8710f6e285f7c8f76c63b7bcf85cc11030 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 chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; ChunkPos chunkPos = chunk.getPos(); this.x = chunkPos.x; this.z = chunkPos.z; - this.chunkData = new ClientboundLevelChunkPacketData(chunk); + this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); + // Paper end this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits, nonEdge); + 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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index c4af6e0f38ac9271247ed657b8ee6b48822417b5..7996247c00bf6ea4399322d089821432333ca6c4 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -991,7 +991,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider completablefuture1.thenAcceptAsync((either) -> { either.ifLeft((chunk) -> { this.tickingGenerated.getAndIncrement(); - MutableObject mutableobject = new MutableObject(); + MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { this.playerLoadedChunk(entityplayer, mutableobject, chunk); @@ -1170,7 +1170,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider while (objectiterator.hasNext()) { ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); ChunkPos chunkcoordintpair = playerchunk.getPos(); - MutableObject mutableobject = new MutableObject(); + MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { SectionPos sectionposition = entityplayer.getLastSectionPos(); @@ -1184,7 +1184,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } - protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { + protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass if (player.level == this.level) { if (newWithinViewDistance && !oldWithinViewDistance) { ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); @@ -1721,12 +1721,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } - private void playerLoadedChunk(ServerPlayer player, MutableObject cachedDataPacket, LevelChunk chunk) { - if (cachedDataPacket.getValue() == null) { - cachedDataPacket.setValue(new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true)); + // Paper start - Anti-Xray - Bypass + private void playerLoadedChunk(ServerPlayer player, MutableObject> cachedDataPackets, LevelChunk chunk) { + if (cachedDataPackets.getValue() == null) { + cachedDataPackets.setValue(new java.util.HashMap<>()); } - player.trackChunk(chunk.getPos(), (Packet) cachedDataPacket.getValue()); + Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); + player.trackChunk(chunk.getPos(), cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> { + return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s); + })); + // Paper end DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); List list = Lists.newArrayList(); List list1 = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 0b8260bd1e18a332063fc0f5ffcf2cf804fdad9c..9e80a76b4d6b6c1b9b0fa99ac8fbacf101e54a79 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -401,7 +401,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error - super(iworlddataserver, resourcekey, worlddimension.typeHolder(), 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))); // Paper + super(iworlddataserver, resourcekey, worlddimension.typeHolder(), 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)), 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 3fadf6b46cc722ad81cf810c0761cf717e9f9b78..312768054e02847bbc7d2ec7fa6198dad52b86d2 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 @@ -318,6 +318,8 @@ public class ServerPlayerGameMode { } } + + this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight); // Paper - Anti-Xray } public void destroyAndAck(BlockPos pos, int sequence, String reason) { diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 4c1d34bd274d8e2a4003a286536652367da9488a..c57f400bf1fcfa1a24259ced1821aa43c428d90b 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -173,6 +173,8 @@ 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; @@ -191,7 +193,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier 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 paperWorldConfigCreator) { // Paper + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier 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 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; @@ -275,6 +277,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); this.tileLimiter = new org.spigotmc.TickLimiter(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 @@ -455,6 +458,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 0d815a39d50bb8c06f81e3386764db6a00d84985..a5160f0336f1ab50e415bddaa958616e8a08dfee 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java @@ -109,7 +109,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom private static void replaceMissingSections(LevelHeightAccessor world, Registry biome, LevelChunkSection[] sectionArray) { for (int i = 0; i < sectionArray.length; ++i) { if (sectionArray[i] == null) { - sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome); + sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome, null, world instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) world : null); // Paper - 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 e9fae214f60fe682087d41cfaa55a1b25e5f4331..1a28b3a60bd568cba7c96152fa8dd2a64dd56801 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -93,7 +93,7 @@ public class LevelChunk extends ChunkAccess { } public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { - super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); + super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isnt ready yet, use server singleton for registry this.tickersInLevel = Maps.newHashMap(); this.clientLightReady = false; this.level = (ServerLevel) world; // CraftBukkit - type 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 ae37e97e52557b48f129cc02eeea395378a48444..ba4da27861236eb62d208f2a660e232a143232ac 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -36,10 +36,13 @@ public class LevelChunkSection { this.recalcBlockCounts(); } - public LevelChunkSection(int chunkPos, Registry biomeRegistry) { + // Paper start - Anti-Xray - Add parameters + @Deprecated public LevelChunkSection(int chunkPos, Registry biomeRegistry) { this(chunkPos, biomeRegistry, null, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public LevelChunkSection(int chunkPos, Registry biomeRegistry, net.minecraft.world.level.ChunkPos pos, net.minecraft.world.level.Level level) { + // Paper end this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos); - 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); + 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, pos, this.bottomBlockY())); // 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 static int getBottomBlockY(int chunkPos) { @@ -177,10 +180,13 @@ public class LevelChunkSection { this.biomes = datapaletteblock; } - public void write(FriendlyByteBuf buf) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public void write(FriendlyByteBuf buf) { this.write(buf, null); } // Notice for updates: Please make sure this method isn't used anywhere + public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { buf.writeShort(this.nonEmptyBlockCount); - this.states.write(buf); - this.biomes.write(buf); + this.states.write(buf, chunkPacketInfo, this.bottomBlockY()); + this.biomes.write(buf, null, this.bottomBlockY()); + // 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 1152bf7f7a7784457c778b215db91b9e02066fba..0a174b07e58f638e75a013552c964a9fb833d4cb 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -29,6 +29,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer return 0; }; public final IdMap registry; + private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values private volatile PalettedContainer.Data data; private final PalettedContainer.Strategy strategy; private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); @@ -41,43 +42,82 @@ public class PalettedContainer implements PaletteResize, PalettedContainer this.threadingDetector.checkAndUnlock(); } - public static Codec> codecRW(IdMap idMap, Codec codec, PalettedContainer.Strategy strategy, T object) { - PalettedContainerRO.Unpacker> unpacker = PalettedContainer::unpack; - return codec(idMap, codec, strategy, object, unpacker); + // Paper start + public interface UnpackerPaper> { + DataResult read(IdMap idMap, PalettedContainer.Strategy strategy, PalettedContainerRO.PackedData packedData, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues); } + // Paper end - public static Codec> codecRO(IdMap idMap, Codec codec, PalettedContainer.Strategy strategy, T object) { - PalettedContainerRO.Unpacker> unpacker = (idMapx, strategyx, packedData) -> { - return unpack(idMapx, strategyx, packedData).map((palettedContainer) -> { + public static Codec> codecRW(IdMap idMap, Codec codec, PalettedContainer.Strategy strategy, T object, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - add preset values, paper unpacker + UnpackerPaper> unpacker = PalettedContainer::unpack; // Paper - add preset values, paper unpacker + return codec(idMap, codec, strategy, object, unpacker, presetValues); // Paper - add preset values, paper unpacker + } + + public static Codec> codecRO(IdMap idMap, Codec codec, PalettedContainer.Strategy strategy, T object) { // Paper - add preset values, paper unpacker + UnpackerPaper> unpacker = (idMapx, strategyx, packedData, object2, presetvalues) -> { // Paper - add preset values, paper unpacker + return unpack(idMapx, strategyx, packedData, object, presetvalues).map((palettedContainer) -> { // Paper - add preset values, paper unpacker return palettedContainer; }); }; - return codec(idMap, codec, strategy, object, unpacker); + return codec(idMap, codec, strategy, object, unpacker, null); // Paper - add preset values, paper unpacker } - private static > Codec codec(IdMap idMap, Codec entryCodec, PalettedContainer.Strategy provider, T object, PalettedContainerRO.Unpacker unpacker) { + private static > Codec codec(IdMap idMap, Codec entryCodec, PalettedContainer.Strategy provider, T object, UnpackerPaper unpacker, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - add preset values, paper unpacker return RecordCodecBuilder.>create((instance) -> { // Paper - decompile fix return instance.group(entryCodec.mapResult(ExtraCodecs.orElsePartial(object)).listOf().fieldOf("palette").forGetter(PalettedContainerRO.PackedData::paletteEntries), Codec.LONG_STREAM.optionalFieldOf("data").forGetter(PalettedContainerRO.PackedData::storage)).apply(instance, PalettedContainerRO.PackedData::new); }).comapFlatMap((packedData) -> { - return unpacker.read(idMap, provider, packedData); + return unpacker.read(idMap, provider, packedData, object ,presetValues); // Paper - add preset values }, (palettedContainerRO) -> { return palettedContainerRO.pack(idMap, provider); }); } - public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { + // Paper start - Anti-Xray - Add preset values + @Deprecated public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List 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 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 idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { + // Paper start - Anti-Xray - Add preset values + private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data, T @org.jetbrains.annotations.Nullable [] presetValues) { + this.presetValues = presetValues; + // Paper end this.registry = idList; this.strategy = paletteProvider; this.data = data; } - public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { + // Paper start - Anti-Xray - Add preset values + @Deprecated public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public PalettedContainer(IdMap 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)null, 0); @@ -92,11 +132,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer @Override public int onResize(int newBits, T object) { PalettedContainer.Data 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 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 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(); @@ -166,25 +228,34 @@ public class PalettedContainer implements PaletteResize, PalettedContainer data.palette.read(buf); buf.readLongArray(data.storage.getRaw()); this.data = data; + this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this not used by the server) } finally { this.release(); } } - @Override - public void write(FriendlyByteBuf buf) { + // Paper start - Anti-Xray - Add chunk packet info + @Override @Deprecated public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere + public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { this.acquire(); try { - this.data.write(buf); + this.data.write(buf, chunkPacketInfo, bottomBlockY); + + if (chunkPacketInfo != null) { + // Bottom block to 0 based chunk section index + int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); + chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); + } + // Paper end } finally { this.release(); } } - private static DataResult> unpack(IdMap idMap, PalettedContainer.Strategy strategy, PalettedContainerRO.PackedData packedData) { + private static DataResult> unpack(IdMap idMap, PalettedContainer.Strategy strategy, PalettedContainerRO.PackedData packedData, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values) { List list = packedData.paletteEntries(); int i = strategy.size(); int j = strategy.calculateBitsForSerialization(idMap, list.size()); @@ -220,7 +291,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer } } - return DataResult.success(new PalettedContainer<>(idMap, strategy, configuration, bitStorage, list)); + return DataResult.success(new PalettedContainer<>(idMap, strategy, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values } @Override @@ -280,7 +351,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer } public PalettedContainer 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 @@ -329,9 +400,20 @@ public class PalettedContainer implements PaletteResize, PalettedContainer return 1 + this.palette.getSerializedSize() + FriendlyByteBuf.getVarIntSize(this.storage.getSize()) + 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 chunkPacketInfo, int bottomBlockY) { buf.writeByte(this.storage.getBits()); this.palette.write(buf); + + if (chunkPacketInfo != null) { + // Bottom block to 0 based chunk section index + int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); + chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); + chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); + chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getRaw().length)); + } + // Paper end + buf.writeLongArray(this.storage.getRaw()); } 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 864e591b10360b0f12fe5c5a650da372555ebd10..f26a08f81495dde6205b34254d159b042e5a6ea9 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 @@ -69,7 +69,7 @@ import org.slf4j.Logger; public class ChunkSerializer { - public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); + public static final Codec> 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"; @@ -149,16 +149,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 << 4); if (nbttagcompound1.contains("block_states", 10)) { - dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { + Codec> 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>) 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 @@ -171,7 +175,7 @@ public class ChunkSerializer { Objects.requireNonNull(logger); object = ((DataResult>>) 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(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write @@ -439,7 +443,7 @@ public class ChunkSerializer { // CraftBukkit start - read/write private static Codec>> makeBiomeCodecRW(Registry 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 cf48c93d89da53e0ec771e5c2c8582e30b35e3f5..518dfbb7dbd4221937636cf46d27109de6f431a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -54,7 +54,7 @@ public class CraftChunk implements Chunk { private final ServerLevel worldServer; private final int x; private final int z; - private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); + private static final PalettedContainer 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[] emptyLight = new byte[2048]; public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) { @@ -392,7 +392,7 @@ public class CraftChunk implements Chunk { empty[i] = true; if (biome != null) { - biome[i] = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); + biome[i] = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 45d923dafdc7dad8b35b8bfd97ce7934abf7efde..2dfcb0fcae636043fa3aeeb20aaa0091a73b578c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -2204,7 +2204,7 @@ public final class CraftServer implements Server { public ChunkGenerator.ChunkData createChunkData(World world) { Validate.notNull(world, "World cannot be null"); ServerLevel handle = ((CraftWorld) world).getHandle(); - return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)); + return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), 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 65edea6420256384a108663761ac9619ba110a8e..759370eff9dd53f41d7b00b8372154acf43908e6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -401,11 +401,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); if (playersInRange.isEmpty()) return; - ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, true); + // Paper start - Anti-Xray - Bypass + Map 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, true, (Boolean) s); + })); + // Paper end } }); }); diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java index 960405935e395a31c0300773c41413801cf0d290..6f6bf950cd15b34031618782c82824cf0b191ff8 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 biomes; private Set tiles; private final Set lights = new HashSet<>(); + // Paper start - Anti-Xray - Add parameters + private final World world; - public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { + @Deprecated public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { this(minHeight, maxHeight, biomes, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes, 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(offset + (this.minHeight >> 4), this.biomes); + this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes, null, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null); // Paper - Anti-Xray - Add parameters } return section; }