diff --git a/patches/server/Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server/Allow-delegation-to-vanilla-chunk-gen.patch index 380be09acb..b7b202f478 100644 --- a/patches/server/Allow-delegation-to-vanilla-chunk-gen.patch +++ b/patches/server/Allow-delegation-to-vanilla-chunk-gen.patch @@ -52,8 +52,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - private final LevelChunkSection[] sections; + private LevelChunkSection[] sections; // Paper - remove final private Set tiles; + private World world; // Paper - Anti-Xray - Add world - public CraftChunkData(World world) { @@ -0,0 +0,0 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { return this.sections; } diff --git a/patches/server/Anti-Xray.patch b/patches/server/Anti-Xray.patch new file mode 100644 index 0000000000..81dc32f054 --- /dev/null +++ b/patches/server/Anti-Xray.patch @@ -0,0 +1,1484 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Mon, 20 Aug 2018 03:03:58 +0200 +Subject: [PATCH] Anti-Xray +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +stonar96 — Today at 15:49 +I'm just here to watch you suffer :smile: +You can skip it if you want and I can do it later. + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -0,0 +0,0 @@ + package com.destroystokyo.paper; + ++import java.util.Arrays; + import java.util.List; + ++import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; + import org.bukkit.Bukkit; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; +@@ -0,0 +0,0 @@ public class PaperWorldConfig { + private void lightQueueSize() { + lightQueueSize = getInt("light-queue-size", lightQueueSize); + } ++ ++ public boolean antiXray; ++ public EngineMode engineMode; ++ public int maxChunkSectionIndex; ++ 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; ++ maxChunkSectionIndex = getInt("anti-xray.max-chunk-section-index", 3); ++ maxChunkSectionIndex = maxChunkSectionIndex > 15 ? 15 : maxChunkSectionIndex; ++ 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("gold_ore", "iron_ore", "coal_ore", "lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "redstone_ore", "clay", "emerald_ore", "ender_chest")); ++ replacementBlocks = getList("anti-xray.replacement-blocks", Arrays.asList("stone", "oak_planks")); ++ 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 " + ((maxChunkSectionIndex + 1) * 16) + " 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/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +@@ -0,0 +0,0 @@ ++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.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[] getPredefinedBlockData(Level world, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) { ++ return null; ++ } ++ ++ public boolean shouldModify(ServerPlayer entityPlayer, LevelChunk chunk) { ++ return false; ++ } ++ ++ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk) { ++ return null; ++ } ++ ++ public void modifyBlocks(ClientboundLevelChunkPacket packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ packetPlayOutMapChunk.setReady(true); ++ } ++ ++ public void onBlockChange(Level world, BlockPos blockPosition, BlockState newBlockData, BlockState oldBlockData, int flag) { ++ ++ } ++ ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode playerInteractManager, BlockPos blockPosition, Direction enumDirection) { ++ ++ } ++} +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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.antixray; ++ ++import java.util.ArrayList; ++import java.util.LinkedHashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ThreadLocalRandom; ++import java.util.function.IntSupplier; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.Registry; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; ++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.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.EmptyLevelChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.Palette; ++import org.bukkit.Bukkit; ++import org.bukkit.World.Environment; ++ ++import com.destroystokyo.paper.PaperWorldConfig; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private final Executor executor; ++ private final EngineMode engineMode; ++ private final int maxChunkSectionIndex; ++ private final int updateRadius; ++ private final boolean usePermission; ++ private final BlockState[] predefinedBlockData; ++ private final BlockState[] predefinedBlockDataFull; ++ private final BlockState[] predefinedBlockDataStone; ++ private final BlockState[] predefinedBlockDataNetherrack; ++ private final BlockState[] predefinedBlockDataEndStone; ++ private final int[] predefinedBlockDataBitsGlobal; ++ private final int[] predefinedBlockDataBitsStoneGlobal; ++ private final int[] predefinedBlockDataBitsNetherrackGlobal; ++ private final int[] predefinedBlockDataBitsEndStoneGlobal; ++ 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_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION, LevelChunk.EMPTY_CHUNK_SECTION}; ++ private final int maxBlockYUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(Level world, Executor executor) { ++ PaperWorldConfig paperWorldConfig = world.paperConfig; ++ engineMode = paperWorldConfig.engineMode; ++ maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ ++ this.executor = executor; ++ ++ List toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ predefinedBlockData = null; ++ predefinedBlockDataFull = null; ++ predefinedBlockDataStone = new BlockState[] {Blocks.STONE.defaultBlockState()}; ++ predefinedBlockDataNetherrack = new BlockState[] {Blocks.NETHERRACK.defaultBlockState()}; ++ predefinedBlockDataEndStone = new BlockState[] {Blocks.END_STONE.defaultBlockState()}; ++ predefinedBlockDataBitsGlobal = null; ++ predefinedBlockDataBitsStoneGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.STONE.defaultBlockState())}; ++ predefinedBlockDataBitsNetherrackGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.NETHERRACK.defaultBlockState())}; ++ predefinedBlockDataBitsEndStoneGlobal = new int[] {LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(Blocks.END_STONE.defaultBlockState())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List predefinedBlockDataList = new LinkedList(); ++ ++ for (String id : paperWorldConfig.hiddenBlocks) { ++ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); ++ ++ if (block != null && !(block instanceof net.minecraft.world.level.block.EntityBlock)) { ++ toObfuscate.add(id); ++ predefinedBlockDataList.add(block.defaultBlockState()); ++ } ++ } ++ ++ // The doc of the LinkedHashSet(Collection c) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation ++ Set predefinedBlockDataSet = new LinkedHashSet(); ++ // Therefore addAll(Collection c) is used, which guarantees this order in the doc ++ predefinedBlockDataSet.addAll(predefinedBlockDataList); ++ predefinedBlockData = predefinedBlockDataSet.size() == 0 ? new BlockState[] {Blocks.DIAMOND_ORE.defaultBlockState()} : predefinedBlockDataSet.toArray(new BlockState[0]); ++ predefinedBlockDataFull = predefinedBlockDataSet.size() == 0 ? new BlockState[] {Blocks.DIAMOND_ORE.defaultBlockState()} : predefinedBlockDataList.toArray(new BlockState[0]); ++ predefinedBlockDataStone = null; ++ predefinedBlockDataNetherrack = null; ++ predefinedBlockDataEndStone = null; ++ predefinedBlockDataBitsGlobal = new int[predefinedBlockDataFull.length]; ++ ++ for (int i = 0; i < predefinedBlockDataFull.length; i++) { ++ predefinedBlockDataBitsGlobal[i] = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ ++ predefinedBlockDataBitsStoneGlobal = null; ++ predefinedBlockDataBitsNetherrackGlobal = null; ++ predefinedBlockDataBitsEndStoneGlobal = 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 ++ // No OBFHELPER for nms.BlockStateList#a() due to too many decompile errors ++ // The OBFHELPER should be getBlockDataList() ++ for (BlockState blockData : block.getStateDefinition().getPossibleStates()) { ++ obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)] = true; ++ } ++ } ++ } ++ ++ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(world, new ChunkPos(0, 0)); ++ BlockPos zeroPos = new BlockPos(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ BlockState blockData = LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getObject(i); ++ ++ if (blockData != null) { ++ solidGlobal[i] = blockData.isRedstoneConductor(emptyChunk, zeroPos) ++ && blockData.getBlock() != Blocks.SPAWNER && blockData.getBlock() != Blocks.BARRIER && blockData.getBlock() != Blocks.SHULKER_BOX && blockData.getBlock() != Blocks.SLIME_BLOCK || paperWorldConfig.lavaObscures && blockData == Blocks.LAVA.defaultBlockState(); ++ // Comparing blockData == Blocks.LAVA.getBlockData() instead of blockData.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used ++ // shulker box checks TE. ++ } ++ } ++ ++ this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1; ++ } ++ ++ private int getPredefinedBlockDataFullLength() { ++ return engineMode == EngineMode.HIDE ? 1 : predefinedBlockDataFull.length; ++ } ++ ++ @Override ++ public BlockState[] getPredefinedBlockData(Level world, ChunkAccess chunk, LevelChunkSection chunkSection, boolean initializeBlocks) { ++ // Return the block data which should be added to the data palettes so that they can be used for the obfuscation ++ if (chunkSection.bottomBlockY() >> 4 <= maxChunkSectionIndex) { ++ switch (engineMode) { ++ case HIDE: ++ switch (world.getWorld().getEnvironment()) { ++ case NETHER: ++ return predefinedBlockDataNetherrack; ++ case THE_END: ++ return predefinedBlockDataEndStone; ++ default: ++ return predefinedBlockDataStone; ++ } ++ default: ++ return predefinedBlockData; ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public boolean shouldModify(ServerPlayer entityPlayer, LevelChunk chunk) { ++ return !usePermission || !entityPlayer.getBukkitEntity().hasPermission("paper.antixray.bypass"); ++ } ++ ++ @Override ++ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, 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 ++ // Note: As of 1.14 this has to be moved later due to the chunk system. ++ ChunkPacketInfoAntiXray chunkPacketInfoAntiXray = new ChunkPacketInfoAntiXray(packetPlayOutMapChunk, chunk, this); ++ return chunkPacketInfoAntiXray; ++ } ++ ++ @Override ++ public void modifyBlocks(ClientboundLevelChunkPacket packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { ++ if (chunkPacketInfo == null) { ++ packetPlayOutMapChunk.setReady(true); ++ return; ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ // plugins? ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ this.modifyBlocks(packetPlayOutMapChunk, chunkPacketInfo); ++ }); ++ return; ++ } ++ ++ LevelChunk chunk = chunkPacketInfo.getChunk(); ++ int x = chunk.getPos().x; ++ int z = chunk.getPos().z; ++ ServerLevel world = (ServerLevel)chunk.level; ++ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks( ++ (LevelChunk) world.getChunkIfLoadedImmediately(x - 1, z), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x + 1, z), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x, z - 1), ++ (LevelChunk) world.getChunkIfLoadedImmediately(x, z + 1)); ++ ++ executor.execute((ChunkPacketInfoAntiXray) 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 predefinedBlockDataBits = ThreadLocal.withInitial(() -> new int[getPredefinedBlockDataFullLength()]); ++ 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[] predefinedBlockDataBits = this.predefinedBlockDataBits.get(); ++ boolean[] solid = this.solid.get(); ++ boolean[] obfuscate = this.obfuscate.get(); ++ boolean[][] current = this.current.get(); ++ boolean[][] next = this.next.get(); ++ boolean[][] nextNext = this.nextNext.get(); ++ // dataBitsReader, dataBitsWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it ++ DataBitsReader dataBitsReader = new DataBitsReader(); ++ DataBitsWriter dataBitsWriter = new DataBitsWriter(); ++ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; ++ boolean[] solidTemp = null; ++ boolean[] obfuscateTemp = null; ++ dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData()); ++ dataBitsWriter.setDataBits(chunkPacketInfoAntiXray.getData()); ++ int numberOfBlocks = predefinedBlockDataBits.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.getPredefinedObjects(chunkSectionIndex) != null) { ++ int[] predefinedBlockDataBitsTemp; ++ ++ if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) { ++ predefinedBlockDataBitsTemp = engineMode == EngineMode.HIDE ? chunkPacketInfoAntiXray.getChunk().level.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().level.getWorld().getEnvironment() == Environment.THE_END ? predefinedBlockDataBitsEndStoneGlobal : predefinedBlockDataBitsStoneGlobal : predefinedBlockDataBitsGlobal; ++ } else { ++ // If it's this.predefinedBlockData, use this.predefinedBlockDataFull instead ++ BlockState[] predefinedBlockDataFull = chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex) == predefinedBlockData ? this.predefinedBlockDataFull : chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex); ++ predefinedBlockDataBitsTemp = predefinedBlockDataBits; ++ ++ for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) { ++ predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(predefinedBlockDataFull[i]); ++ } ++ } ++ ++ dataBitsWriter.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ ++ // Check if the chunk section below was not obfuscated ++ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex - 1) == null) { ++ // If so, initialize some stuff ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(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 = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex - 1]) == LevelChunk.EMPTY_CHUNK_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.getOrCreateIdFor(belowChunkSection.getBlockState(x, 15, z))]; ++ } ++ } ++ ++ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section ++ dataBitsWriter.setBitsPerObject(0); ++ obfuscateLayer(-1, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); ++ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? LevelChunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? LevelChunk.EMPTY_CHUNK_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, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ // Check if the chunk section above doesn't need obfuscation ++ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPredefinedObjects(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 != 15 && (aboveChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex + 1]) != LevelChunk.EMPTY_CHUNK_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.getOrCreateIdFor(aboveChunkSection.getBlockState(x, 0, z))]) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ dataBitsReader.setBitsPerObject(0); ++ solid[0] = true; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solid, obfuscateTemp, predefinedBlockDataBitsTemp, 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 ++ dataBitsReader.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex + 1)); ++ dataBitsReader.setIndex(chunkPacketInfoAntiXray.getDataBitsIndex(chunkSectionIndex + 1)); ++ solidTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), solid, solidGlobal); ++ obfuscateTemp = readDataPalette(chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(15, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ dataBitsWriter.finish(); ++ } ++ } ++ ++ chunkPacketInfoAntiXray.getPacketPlayOutMapChunk().setReady(true); ++ } ++ ++ private void obfuscateLayer(int y, DataBitsReader dataBitsReader, DataBitsWriter dataBitsWriter, boolean[] solid, boolean[] obfuscate, int[] predefinedBlockDataBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { ++ // First block of first line ++ int dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][1] = true; ++ next[1][0] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(0, y, 15))] || nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, 0))] || current[0][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][0] = true; ++ } ++ ++ // First line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][x - 1] = true; ++ next[0][x + 1] = true; ++ next[1][x] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(x, y, 15))] || current[0][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][x] = true; ++ } ++ } ++ ++ // Last block of first line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[0][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[0][14] = true; ++ next[1][15] = true; ++ } else { ++ if (nearbyChunkSections[2] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getBlockState(15, y, 15))] || nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, 0))] || current[0][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[0][15] = true; ++ } ++ ++ // All inner lines ++ for (int z = 1; z < 15; z++) { ++ // First block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][1] = true; ++ next[z - 1][0] = true; ++ next[z + 1][0] = true; ++ } else { ++ if (nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, z))] || current[z][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][0] = true; ++ } ++ ++ // All inner blocks ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][x] = !solid[dataBits]) { ++ dataBitsWriter.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]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][x] = true; ++ } ++ } ++ ++ // Last block ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[z][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[z][14] = true; ++ next[z - 1][15] = true; ++ next[z + 1][15] = true; ++ } else { ++ if (nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, z))] || current[z][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[z][15] = true; ++ } ++ } ++ ++ // First block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][0] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][1] = true; ++ next[14][0] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(0, y, 0))] || nearbyChunkSections[0] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getBlockState(15, y, 15))] || current[15][0]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][0] = true; ++ } ++ ++ // Last line ++ for (int x = 1; x < 15; x++) { ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][x] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][x - 1] = true; ++ next[15][x + 1] = true; ++ next[14][x] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(x, y, 0))] || current[15][x]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][x] = true; ++ } ++ } ++ ++ // Last block of last line ++ dataBits = dataBitsReader.read(); ++ ++ if (nextNext[15][15] = !solid[dataBits]) { ++ dataBitsWriter.skip(); ++ next[15][14] = true; ++ next[14][15] = true; ++ } else { ++ if (nearbyChunkSections[3] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getBlockState(15, y, 0))] || nearbyChunkSections[1] == LevelChunk.EMPTY_CHUNK_SECTION || !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getBlockState(0, y, 15))] || current[15][15]) { ++ dataBitsWriter.skip(); ++ } else { ++ dataBitsWriter.write(predefinedBlockDataBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[dataBits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean[] readDataPalette(Palette dataPalette, boolean[] temp, boolean[] global) { ++ if (dataPalette == LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE) { ++ return global; ++ } ++ ++ BlockState blockData; ++ ++ for (int i = 0; (blockData = dataPalette.getObject(i)) != null; i++) { ++ temp[i] = global[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)]; ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(Level world, BlockPos blockPosition, BlockState newBlockData, BlockState oldBlockData, int flag) { ++ if (oldBlockData != null && solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(oldBlockData)] && !solidGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(newBlockData)] && blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(world, blockPosition); ++ } ++ } ++ ++ @Override ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode playerInteractManager, BlockPos blockPosition, Direction enumDirection) { ++ if (blockPosition.getY() <= maxBlockYUpdatePosition) { ++ updateNearbyBlocks(playerInteractManager.level, blockPosition); ++ } ++ } ++ ++ private void updateNearbyBlocks(Level world, BlockPos blockPosition) { ++ if (updateRadius >= 2) { ++ BlockPos temp = blockPosition.west(); ++ updateBlock(world, temp); ++ updateBlock(world, temp.west()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.east()); ++ updateBlock(world, temp.east()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.below()); ++ updateBlock(world, temp.below()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.above()); ++ updateBlock(world, temp.above()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp.south()); ++ updateBlock(world, temp = blockPosition.north()); ++ updateBlock(world, temp.north()); ++ updateBlock(world, temp = blockPosition.south()); ++ updateBlock(world, temp.south()); ++ } else if (updateRadius == 1) { ++ updateBlock(world, blockPosition.west()); ++ updateBlock(world, blockPosition.east()); ++ updateBlock(world, blockPosition.below()); ++ updateBlock(world, blockPosition.above()); ++ updateBlock(world, blockPosition.north()); ++ updateBlock(world, blockPosition.south()); ++ } else { ++ // Do nothing if updateRadius <= 0 (test mode) ++ } ++ } ++ ++ private void updateBlock(Level world, BlockPos blockPosition) { ++ BlockState blockData = world.getTypeIfLoaded(blockPosition); ++ ++ if (blockData != null && obfuscateGlobal[LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE.getOrCreateIdFor(blockData)]) { ++ // world.notify(blockPosition, blockData, blockData, 3); ++ ((ServerLevel)world).getChunkSource().blockChanged(blockPosition); // We only need to re-send to client ++ } ++ } ++ ++ 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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +@@ -0,0 +0,0 @@ ++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 packetPlayOutMapChunk; ++ private final LevelChunk chunk; ++ private byte[] data; ++ private final int[] bitsPerObject = new int[16]; ++ private final Object[] dataPalettes = new Object[16]; ++ private final int[] dataBitsIndexes = new int[16]; ++ private final Object[][] predefinedObjects = new Object[16][]; ++ ++ public ChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk) { ++ this.packetPlayOutMapChunk = packetPlayOutMapChunk; ++ this.chunk = chunk; ++ } ++ ++ public ClientboundLevelChunkPacket getPacketPlayOutMapChunk() { ++ return packetPlayOutMapChunk; ++ } ++ ++ public LevelChunk getChunk() { ++ return chunk; ++ } ++ ++ public byte[] getData() { ++ return data; ++ } ++ ++ public void setData(byte[] data) { ++ this.data = data; ++ } ++ ++ public int getBitsPerObject(int chunkSectionIndex) { ++ return bitsPerObject[chunkSectionIndex]; ++ } ++ ++ public void setBitsPerObject(int chunkSectionIndex, int bitsPerObject) { ++ this.bitsPerObject[chunkSectionIndex] = bitsPerObject; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public Palette getDataPalette(int chunkSectionIndex) { ++ return (Palette) dataPalettes[chunkSectionIndex]; ++ } ++ ++ public void setDataPalette(int chunkSectionIndex, Palette dataPalette) { ++ dataPalettes[chunkSectionIndex] = dataPalette; ++ } ++ ++ public int getDataBitsIndex(int chunkSectionIndex) { ++ return dataBitsIndexes[chunkSectionIndex]; ++ } ++ ++ public void setDataBitsIndex(int chunkSectionIndex, int dataBitsIndex) { ++ dataBitsIndexes[chunkSectionIndex] = dataBitsIndex; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T[] getPredefinedObjects(int chunkSectionIndex) { ++ return (T[]) predefinedObjects[chunkSectionIndex]; ++ } ++ ++ public void setPredefinedObjects(int chunkSectionIndex, T[] predefinedObjects) { ++ this.predefinedObjects[chunkSectionIndex] = predefinedObjects; ++ } ++ ++ public boolean isWritten(int chunkSectionIndex) { ++ return bitsPerObject[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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +@@ -0,0 +0,0 @@ ++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 LevelChunk[] nearbyChunks; ++ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; ++ ++ public ChunkPacketInfoAntiXray(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, ++ ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { ++ super(packetPlayOutMapChunk, 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/com/destroystokyo/paper/antixray/DataBitsReader.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsReader { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private int mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ } ++ ++ public int read() { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ int value = (int) (current >>> bitInLongIndex) & mask; ++ bitInLongIndex += bitsPerObject; ++ return value; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class DataBitsWriter { ++ ++ private byte[] dataBits; ++ private int bitsPerObject; ++ private long mask; ++ private int longInDataBitsIndex; ++ private int bitInLongIndex; ++ private long current; ++ private boolean dirty; ++ ++ public void setDataBits(byte[] dataBits) { ++ this.dataBits = dataBits; ++ } ++ ++ public void setBitsPerObject(int bitsPerObject) { ++ this.bitsPerObject = bitsPerObject; ++ mask = (1 << bitsPerObject) - 1; ++ } ++ ++ public void setIndex(int index) { ++ this.longInDataBitsIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (dataBits.length > longInDataBitsIndex + 7) { ++ current = ((((long) dataBits[longInDataBitsIndex]) << 56) ++ | (((long) dataBits[longInDataBitsIndex + 1] & 0xff) << 48) ++ | (((long) dataBits[longInDataBitsIndex + 2] & 0xff) << 40) ++ | (((long) dataBits[longInDataBitsIndex + 3] & 0xff) << 32) ++ | (((long) dataBits[longInDataBitsIndex + 4] & 0xff) << 24) ++ | (((long) dataBits[longInDataBitsIndex + 5] & 0xff) << 16) ++ | (((long) dataBits[longInDataBitsIndex + 6] & 0xff) << 8) ++ | (((long) dataBits[longInDataBitsIndex + 7] & 0xff))); ++ } ++ ++ dirty = false; ++ } ++ ++ public void finish() { ++ if (dirty && dataBits.length > longInDataBitsIndex + 7) { ++ dataBits[longInDataBitsIndex] = (byte) (current >> 56 & 0xff); ++ dataBits[longInDataBitsIndex + 1] = (byte) (current >> 48 & 0xff); ++ dataBits[longInDataBitsIndex + 2] = (byte) (current >> 40 & 0xff); ++ dataBits[longInDataBitsIndex + 3] = (byte) (current >> 32 & 0xff); ++ dataBits[longInDataBitsIndex + 4] = (byte) (current >> 24 & 0xff); ++ dataBits[longInDataBitsIndex + 5] = (byte) (current >> 16 & 0xff); ++ dataBits[longInDataBitsIndex + 6] = (byte) (current >> 8 & 0xff); ++ dataBits[longInDataBitsIndex + 7] = (byte) (current & 0xff); ++ } ++ } ++ ++ public void write(int value) { ++ if (bitInLongIndex + bitsPerObject > 64) { ++ finish(); ++ bitInLongIndex = 0; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ ++ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; ++ dirty = true; ++ bitInLongIndex += bitsPerObject; ++ } ++ ++ public void skip() { ++ bitInLongIndex += bitsPerObject; ++ ++ if (bitInLongIndex > 64) { ++ finish(); ++ bitInLongIndex = bitsPerObject; ++ longInDataBitsIndex += 8; ++ init(); ++ } ++ } ++} +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +@@ -0,0 +0,0 @@ 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; +@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet chunkPacketInfo) { ++ // Paper end + BitSet bitSet = new BitSet(); + LevelChunkSection[] levelChunkSections = chunk.getSections(); + int i = 0; +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // 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) { + // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error +- super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env); ++ super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, env, executor); // Paper - 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -0,0 +0,0 @@ import org.bukkit.event.player.PlayerInteractEvent; + public class ServerPlayerGameMode { + + private static final Logger LOGGER = LogManager.getLogger(); +- protected ServerLevel level; ++ public ServerLevel level; // Paper - protected->public + protected final ServerPlayer player; + private GameType gameModeForPlayer; + @Nullable +@@ -0,0 +0,0 @@ public class ServerPlayerGameMode { + } + + } ++ ++ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, direction); // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -0,0 +0,0 @@ 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 +@@ -0,0 +0,0 @@ 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.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.World.Environment env, java.util.concurrent.Executor executor) { // Paper + 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.chunkPacketBlockController = this.paperConfig.antiXray ? ++ new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) ++ : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, env); + this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit +@@ -0,0 +0,0 @@ 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); // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -0,0 +0,0 @@ public interface ChunkAccess extends BlockGetter, FeatureAccess { + default LevelChunkSection getOrCreateSection(int yIndex) { + LevelChunkSection[] levelChunkSections = this.getSections(); + if (levelChunkSections[yIndex] == LevelChunk.EMPTY_SECTION) { ++ // Paper - diff on change in ProtoChunk + levelChunkSections[yIndex] = new LevelChunkSection(this.getSectionYFromSectionIndex(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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -0,0 +0,0 @@ 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 - world isnt ready yet for anti xray use here, 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -0,0 +0,0 @@ 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.server.level.ServerLevel world, boolean initializeBlocks) { ++ // Paper end ++ this(yOffset, (short) 0, (short) 0, (short) 0, chunk, world, initializeBlocks); + } + +- 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.server.level.ServerLevel world, 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(), ++ world == null ? null : world.chunkPacketBlockController.getPredefinedBlockData(world, chunk, this, initializeBlocks), initializeBlocks); // Paper - Anti-Xray - Add predefined block data + } + + public static int getBottomBlockY(int chunkPos) { +@@ -0,0 +0,0 @@ public class LevelChunkSection { + this.states.read(buf); + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start ++ @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 >> 4); // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { + private final Function reader; + private final Function writer; + private final T defaultValue; ++ private final T[] predefinedObjects; // Paper - Anti-Xray - Add predefined objects + protected BitStorage storage; public final BitStorage getDataBits() { return this.storage; } // Paper - OBFHELPER + private Palette palette; private Palette getDataPalette() { return this.palette; } // Paper - OBFHELPER + private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER +@@ -0,0 +0,0 @@ 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 predefined objects ++ @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[] predefinedObjects, 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 predefined objects ++ this.predefinedObjects = predefinedObjects; ++ ++ if (initialize) { ++ if (predefinedObjects == null) { ++ // Default ++ this.initialize(4); ++ } else { ++ // MathHelper.d() is trailingBits(roundCeilPow2(n)), alternatively; (int)ceil(log2(n)); however it's trash, use numberOfLeadingZeros instead ++ // Count the bits of the maximum array index to initialize a data palette with enough space from the beginning ++ // The length of the array is used because air is also added to the data palette from the beginning ++ // Start with at least 4 ++ int maxIndex = predefinedObjects.length >> 4; ++ int bitCount = (32 - Integer.numberOfLeadingZeros(Math.max(16, maxIndex) - 1)); ++ ++ // Initialize with at least 15 free indixes ++ this.initialize((1 << bitCount) - predefinedObjects.length < 16 ? bitCount + 1 : bitCount); ++ this.addPredefinedObjects(); ++ } ++ } ++ // Paper end + } + ++ // Paper start - Anti-Xray - Add predefined objects ++ private void addPredefinedObjects() { ++ if (this.predefinedObjects != null && this.palette != this.globalPalette) { ++ for (T predefinedObject : this.predefinedObjects) { ++ this.palette.getOrCreateIdFor(predefinedObject); ++ } ++ } ++ } ++ // Paper end ++ + private static int getIndex(int x, int y, int z) { + return y << 8 | z << 4 | x; + } +@@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { + Palette palette = this.palette; + this.setBits(newSize); + ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + for(int i = 0; i < bitStorage.getSize(); ++i) { + T object = palette.valueFor(bitStorage.get(i)); + if (object != null) { +@@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { + } + + public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated public void write(FriendlyByteBuf buf) { ++ write(buf, null, 0); ++ } ++ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { ++ // Paper end + try { + this.acquire(); + buf.writeByte(this.bits); + this.palette.write(buf); ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBitsPerObject(chunkSectionIndex, this.bits); ++ chunkPacketInfo.setDataPalette(chunkSectionIndex, this.palette); ++ chunkPacketInfo.setDataBitsIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getDataBits().length)); ++ chunkPacketInfo.setPredefinedObjects(chunkSectionIndex, this.predefinedObjects); ++ } ++ // Paper end + buf.writeLongArray(this.storage.getRaw()); + } finally { + this.release(); +@@ -0,0 +0,0 @@ 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.predefinedObjects.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.predefinedObjects == null ? 0 : this.predefinedObjects.length))); // Paper - Anti-Xray - Calculate the size with predefined objects ++ if (true || i != this.bits) { // Paper - Anti-Xray - Not initialized yet + this.setBits(i); + } + + this.palette.read(paletteNbt); ++ this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects + 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/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -0,0 +0,0 @@ public class ProtoChunk implements ChunkAccess { + private long inhabitedTime; + private final Map carvingMasks = new Object2ObjectArrayMap<>(); + private volatile boolean isLightCorrect; +- final net.minecraft.world.level.Level level; // Paper - Add level ++ final net.minecraft.server.level.ServerLevel level; // Paper - Add level + + // Paper start - add level + @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world) { this(pos, upgradeData, world, null); } +@@ -0,0 +0,0 @@ public class ProtoChunk implements ChunkAccess { + public int getHeight() { + return this.levelHeightAccessor.getHeight(); + } ++ ++ @Override ++ public net.minecraft.world.level.chunk.LevelChunkSection getOrCreateSection(final int yIndex) { ++ LevelChunkSection[] levelChunkSections = this.getSections(); ++ if (levelChunkSections[yIndex] == LevelChunk.EMPTY_SECTION) { ++ // Paper - diff on change in ProtoChunk ++ levelChunkSections[yIndex] = new LevelChunkSection(this.getSectionYFromSectionIndex(yIndex), this, this.level, true); // Paper - Anti-Xray - Add parameters ++ } ++ ++ return levelChunkSections[yIndex]; ++ } + } +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 +@@ -0,0 +0,0 @@ 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 + + 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -0,0 +0,0 @@ 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) { +@@ -0,0 +0,0 @@ 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 predefined block data 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/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -0,0 +0,0 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + private final int maxHeight; + private final LevelChunkSection[] sections; + private Set tiles; ++ private World world; // Paper - Anti-Xray - Add world + + public CraftChunkData(World world) { + this(world.getMinHeight(), world.getMaxHeight()); ++ this.world = world; // Paper - Anti-Xray - Add world + } + + /* pp for tests */ CraftChunkData(int minHeight, int maxHeight) { +@@ -0,0 +0,0 @@ public final class CraftChunkData 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) world).getHandle() : null, true); // Paper - Anti-Xray - Add parameters + } + return section; + } diff --git a/patches/server/Configurable-door-breaking-difficulty.patch b/patches/server/Configurable-door-breaking-difficulty.patch index b080c3cf0a..9cfc9dffb7 100644 --- a/patches/server/Configurable-door-breaking-difficulty.patch +++ b/patches/server/Configurable-door-breaking-difficulty.patch @@ -8,18 +8,18 @@ diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -@@ -0,0 +0,0 @@ import java.util.EnumMap; - import java.util.HashMap; +@@ -0,0 +0,0 @@ package com.destroystokyo.paper; + + import java.util.Arrays; import java.util.List; - import java.util.Map; - +import java.util.stream.Collectors; +import net.minecraft.world.Difficulty; +import net.minecraft.world.entity.monster.Vindicator; +import net.minecraft.world.entity.monster.Zombie; + import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode; import org.bukkit.Bukkit; - import org.bukkit.Material; - import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.file.YamlConfiguration; @@ -0,0 +0,0 @@ public class PaperWorldConfig { disableMobSpawnerSpawnEggTransformation = getBoolean("game-mechanics.disable-mob-spawner-spawn-egg-transformation", disableMobSpawnerSpawnEggTransformation); } diff --git a/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch index 444f391c08..5ac8eb7310 100644 --- a/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch +++ b/patches/server/Delay-Chunk-Unloads-based-on-Player-Movement.patch @@ -34,7 +34,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + public boolean altItemDespawnRateEnabled; - public Map altItemDespawnRateMap; + public java.util.Map altItemDespawnRateMap; private void altItemDespawnRate() { diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 diff --git a/patches/server/Fix-and-optimise-world-force-upgrading.patch b/patches/server/Fix-and-optimise-world-force-upgrading.patch index 492fea82ec..eb822e536e 100644 --- a/patches/server/Fix-and-optimise-world-force-upgrading.patch +++ b/patches/server/Fix-and-optimise-world-force-upgrading.patch @@ -301,8 +301,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.typeKey; - } + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here + // Paper start - fix and optimise world upgrading + // copied from below @@ -312,9 +312,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }); + } + // Paper end - fix and optimise world upgrading - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { - 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 ++ + public CraftWorld getWorld() { + return this.world; + } diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java diff --git a/patches/server/Implement-alternative-item-despawn-rate.patch b/patches/server/Implement-alternative-item-despawn-rate.patch index d15f1c29e8..e81a742cd7 100644 --- a/patches/server/Implement-alternative-item-despawn-rate.patch +++ b/patches/server/Implement-alternative-item-despawn-rate.patch @@ -8,41 +8,26 @@ diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -@@ -0,0 +0,0 @@ - package com.destroystokyo.paper; - -+import java.util.EnumMap; -+import java.util.HashMap; - import java.util.List; -+import java.util.Map; - - import org.bukkit.Bukkit; -+import org.bukkit.Material; -+import org.bukkit.configuration.ConfigurationSection; - import org.bukkit.configuration.file.YamlConfiguration; - import org.spigotmc.SpigotWorldConfig; - @@ -0,0 +0,0 @@ public class PaperWorldConfig { - private void viewDistance() { this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); } -+ + + public boolean altItemDespawnRateEnabled; -+ public Map altItemDespawnRateMap; ++ public java.util.Map altItemDespawnRateMap; + private void altItemDespawnRate() { + String path = "alt-item-despawn-rate"; + + altItemDespawnRateEnabled = getBoolean(path + ".enabled", false); + -+ Map altItemDespawnRateMapDefault = new EnumMap<>(Material.class); -+ altItemDespawnRateMapDefault.put(Material.COBBLESTONE, 300); -+ for (Material key : altItemDespawnRateMapDefault.keySet()) { ++ java.util.Map altItemDespawnRateMapDefault = new java.util.EnumMap<>(org.bukkit.Material.class); ++ altItemDespawnRateMapDefault.put(org.bukkit.Material.COBBLESTONE, 300); ++ for (org.bukkit.Material key : altItemDespawnRateMapDefault.keySet()) { + config.addDefault("world-settings.default." + path + ".items." + key, altItemDespawnRateMapDefault.get(key)); + } + -+ Map rawMap = new HashMap<>(); ++ java.util.Map rawMap = new java.util.HashMap<>(); + try { -+ ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items"); ++ org.bukkit.configuration.ConfigurationSection mapSection = config.getConfigurationSection("world-settings." + worldName + "." + path + ".items"); + if (mapSection == null) { + mapSection = config.getConfigurationSection("world-settings.default." + path + ".items"); + } @@ -56,26 +41,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + altItemDespawnRateEnabled = false; + } + -+ altItemDespawnRateMap = new EnumMap<>(Material.class); ++ altItemDespawnRateMap = new java.util.EnumMap<>(org.bukkit.Material.class); + if (!altItemDespawnRateEnabled) { + return; + } + + for(String key : rawMap.keySet()) { + try { -+ altItemDespawnRateMap.put(Material.valueOf(key), rawMap.get(key)); ++ altItemDespawnRateMap.put(org.bukkit.Material.valueOf(key), rawMap.get(key)); + } catch (Exception e) { + logError("Could not add item " + key + " to altItemDespawnRateMap: " + e.getMessage()); + } + } + if(altItemDespawnRateEnabled) { -+ for(Material key : altItemDespawnRateMap.keySet()) { ++ for(org.bukkit.Material key : altItemDespawnRateMap.keySet()) { + log("Alternative item despawn rate of " + key + ": " + altItemDespawnRateMap.get(key)); + } + } + } - } - ++ + public boolean antiXray; + public EngineMode engineMode; + public int maxChunkSectionIndex; diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java diff --git a/patches/server/No-Tick-view-distance-implementation.patch b/patches/server/No-Tick-view-distance-implementation.patch index aa88c188ea..6b5dd263b8 100644 --- a/patches/server/No-Tick-view-distance-implementation.patch +++ b/patches/server/No-Tick-view-distance-implementation.patch @@ -27,16 +27,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +0,0 @@ public class PaperWorldConfig { - private void lightQueueSize() { lightQueueSize = getInt("light-queue-size", lightQueueSize); } -+ + + public int noTickViewDistance; + private void viewDistance() { + this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1); + } - } - ++ + public boolean antiXray; + public EngineMode engineMode; + public int maxChunkSectionIndex; diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java @@ -486,7 +487,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public void playerLoadedChunk(ServerPlayer player, Packet[] packets, LevelChunk chunk) { // Paper - private -> public 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/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 diff --git a/patches/server/Optimise-random-block-ticking.patch b/patches/server/Optimise-random-block-ticking.patch index 2087c4a0c7..56159e89cc 100644 --- a/patches/server/Optimise-random-block-ticking.patch +++ b/patches/server/Optimise-random-block-ticking.patch @@ -318,8 +318,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 final PalettedContainer states; // Paper - package-private + public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper - 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 @@ -0,0 +0,0 @@ public class LevelChunkSection { --this.nonEmptyBlockCount; if (blockState.isRandomlyTicking()) { diff --git a/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLo.patch b/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLo.patch index 43b02bbc14..2319cffcb7 100644 --- a/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLo.patch +++ b/patches/server/Synchronize-PalettedContainer-instead-of-ReentrantLo.patch @@ -35,7 +35,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + //this.lock.release(); // Paper - disable this } - public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { + // Paper start - Anti-Xray - Add predefined objects @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { return this.palette.idFor(objectAdded); } @@ -64,14 +64,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.acquire(); int i = buf.readByte(); @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { + @Deprecated public void write(FriendlyByteBuf buf) { + write(buf, null, 0); } - - public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER -- public void write(FriendlyByteBuf buf) { -+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize +- public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { ++ public synchronized void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - synchronize + // Paper end try { this.acquire(); - buf.writeByte(this.bits); @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { } @@ -80,7 +80,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public synchronized void read(ListTag paletteNbt, long[] data) { // Paper - synchronize try { this.acquire(); - int i = Math.max(4, Mth.ceillog2(paletteNbt.size())); + // Paper - Anti-Xray - TODO: Should this.predefinedObjects.length just be added here (faster) or should the contents be compared to calculate the size (less RAM)? @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize { } diff --git a/patches/server/implement-optional-per-player-mob-spawns.patch b/patches/server/implement-optional-per-player-mob-spawns.patch index 2d8b8d5c80..86b17a39c1 100644 --- a/patches/server/implement-optional-per-player-mob-spawns.patch +++ b/patches/server/implement-optional-per-player-mob-spawns.patch @@ -29,7 +29,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +0,0 @@ public class PaperWorldConfig { - } + Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues"); } } + @@ -548,14 +548,6 @@ diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/j index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ import net.minecraft.util.thread.ProcessorMailbox; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.MobCategory; - import net.minecraft.world.entity.ai.village.poi.PoiManager; - import net.minecraft.world.entity.boss.EnderDragonPart; - import net.minecraft.world.level.ChunkPos; @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider private final Long2ByteMap chunkTypeCache; private final Queue unloadQueue; @@ -590,7 +582,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ public int getMobCountNear(ServerPlayer entityPlayer, MobCategory enumCreatureType) { ++ public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory enumCreatureType) { + return entityPlayer.mobCounts[enumCreatureType.ordinal()]; + } + // Paper end