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