From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: stonar96 Date: Mon, 20 Aug 2018 03:03:58 +0200 Subject: [PATCH] Anti-Xray 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 1278d09f70c1e97607ef20d87a178dc252c7f723..c45493e88bf7e8811be2759ff9ac19e3fe9d938a 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -1,7 +1,9 @@ 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; @@ -461,4 +463,38 @@ public class PaperWorldConfig { private void maxAutoSaveChunksPerTick() { maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); } + + 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..8fb63441fbf9afb6f11e1185a9f29528e1950546 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java @@ -0,0 +1,45 @@ +package com.destroystokyo.paper.antixray; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.protocol.game.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, int chunkSectionSelector) { + return false; + } + + public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) { + 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..6d41628444e880dea5c96ad5caf557f4c56dea46 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -0,0 +1,649 @@ +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.isEntityBlock()) { + 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, int chunkSectionSelector) { + return !usePermission || !entityPlayer.getBukkitEntity().hasPermission("paper.antixray.bypass"); + } + + @Override + public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) { + // 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, chunkSectionSelector, 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.world; + ((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().world.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().world.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..dc04ffc76e11ab63cd98a84cf95c58dc5cd1efdb --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java @@ -0,0 +1,81 @@ +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 final int chunkSectionSelector; + 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, int chunkSectionSelector) { + this.packetPlayOutMapChunk = packetPlayOutMapChunk; + this.chunk = chunk; + this.chunkSectionSelector = chunkSectionSelector; + } + + public ClientboundLevelChunkPacket getPacketPlayOutMapChunk() { + return packetPlayOutMapChunk; + } + + public LevelChunk getChunk() { + return chunk; + } + + public int getChunkSectionSelector() { + return chunkSectionSelector; + } + + 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..7345f1dc7c5c05f2e1ee09b94f4ebf56dd59bc55 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java @@ -0,0 +1,30 @@ +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, int chunkSectionSelector, + ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { + super(packetPlayOutMapChunk, chunk, chunkSectionSelector); + 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..298ea423084dbcc1b61f991bcd82b8ae51bf0977 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java @@ -0,0 +1,51 @@ +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..333763936897befda5bb6c077944d2667f922799 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java @@ -0,0 +1,79 @@ +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 b587f774c8f88f2a1c3ea489f7e4fe0bbdeb5a41..10dd582b0fff4df27f1113e41c8ee3e274c6fb65 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java @@ -1,5 +1,6 @@ package net.minecraft.network.protocol.game; +import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info import com.google.common.collect.Lists; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -16,6 +17,7 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.SkullBlockEntity; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkBiomeContainer; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; @@ -33,7 +35,13 @@ public class ClientboundLevelChunkPacket implements Packet blockEntitiesTags; private boolean fullChunk; - public ClientboundLevelChunkPacket() {} + // Paper start - Async-Anti-Xray - Set the ready flag to true + private volatile boolean ready; // Paper - Async-Anti-Xray - Ready flag for the network manager + public ClientboundLevelChunkPacket() { + this.ready = true; + } + // Paper end + // Paper start private final java.util.List extraPackets = new java.util.ArrayList<>(); private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); @@ -43,12 +51,16 @@ public class ClientboundLevelChunkPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i) : null; + // Paper end ChunkPos chunkcoordintpair = chunk.getPos(); this.x = chunkcoordintpair.x; this.z = chunkcoordintpair.z; - this.fullChunk = includedSectionsMask == 65535; + this.fullChunk = i == 65535; this.heightmaps = new CompoundTag(); Iterator iterator = chunk.getHeightmaps().iterator(); @@ -65,8 +77,13 @@ public class ClientboundLevelChunkPacket implements Packet> 4; - if (this.isFullChunk() || (includedSectionsMask & 1 << j) != 0) { + if (this.isFullChunk() || (i & 1 << j) != 0) { // Paper start - improve oversized chunk data packet handling if (++totalTileEntities > TE_LIMIT) { ClientboundBlockEntityDataPacket updatePacket = tileentity.getUpdatePacket(); @@ -93,8 +110,19 @@ public class ClientboundLevelChunkPacket implements Packet chunkPacketInfo) { return this.a(packetDataSerializer, chunk, chunkSectionSelector, chunkPacketInfo); } // OBFHELPER + public int a(FriendlyByteBuf packetdataserializer, LevelChunk chunk, int i, ChunkPacketInfo chunkPacketInfo) { + // Paper end int j = 0; LevelChunkSection[] achunksection = chunk.getSections(); int k = 0; @@ -169,9 +201,9 @@ public class ClientboundLevelChunkPacket implements Packet[] apacket, LevelChunk chunk) { this.playerLoadedChunk(entityplayer, apacket, chunk); } // Paper - OBFHELPER private void playerLoadedChunk(ServerPlayer player, Packet[] packets, LevelChunk chunk) { if (packets[0] == null) { - packets[0] = new ClientboundLevelChunkPacket(chunk, 65535); + packets[0] = new ClientboundLevelChunkPacket(chunk, 65535, chunk.world.chunkPacketBlockController.shouldModify(player, chunk, 65535)); // Paper - Anti-Xray - Bypass packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, true); } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index fd7ee4badb383ffb4347d62c00ea2dfa3d76fd12..7a09bc921827958f58290bd3d6f19984bb34a8f6 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -204,7 +204,7 @@ 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) { - 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(); convertable = convertable_conversionsession; 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 d97607f2ded4977b253d3afa3bafcbe6d7f98837..af048ab682612233c01f7087d7b8afbf7e58945b 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -308,6 +308,8 @@ 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 eb88d830fb45a6b8c990e8bdc1943d80f63c8b93..1377465e3dc062f34be25cac10aa018776fb22e7 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -2,6 +2,8 @@ package net.minecraft.world.level; import co.aikar.timings.Timing; import co.aikar.timings.Timings; +import com.destroystokyo.paper.antixray.ChunkPacketBlockController; // Paper - Anti-Xray +import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; // Paper - Anti-Xray import com.destroystokyo.paper.event.server.ServerExceptionEvent; import com.destroystokyo.paper.exception.ServerInternalException; import com.google.common.base.MoreObjects; @@ -144,6 +146,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 ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPos lastPhysicsProblem; // Spigot @@ -165,9 +168,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return 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 ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, env); this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit @@ -433,6 +437,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); // 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/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java index e369730ac6909ff5343468bd685c9ea2b6b3cfed..2c19d147710a3bbe2e980114161f1cdf81760947 100644 --- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java @@ -8,6 +8,7 @@ import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.data.worldgen.biome.Biomes; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkHolder; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -28,7 +29,7 @@ public class EmptyLevelChunk extends LevelChunk { }); public EmptyLevelChunk(Level world, ChunkPos pos) { - super(world, pos, new ChunkBiomeContainer(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), EmptyLevelChunk.BIOMES)); + super(world, pos, new ChunkBiomeContainer(MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), EmptyLevelChunk.BIOMES)); // Paper - world isnt ready yet for anti xray use here, use server singleton for registry } // Paper start diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java index 17fa8b23d1000ae53f2b4f1a6e8817c1005c1c81..56ab660e29a0dc7d22eeaa41cc8f50e8a96717ef 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java @@ -27,7 +27,7 @@ public class ImposterProtoChunk extends ProtoChunk { private final LevelChunk wrapped; public ImposterProtoChunk(LevelChunk wrapped) { - super(wrapped.getPos(), UpgradeData.EMPTY); + super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.world); // Paper - Anti-Xray - Add parameter this.wrapped = wrapped; } 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 419b4bf0549d798d52d73fbbd9de59313fc05eb1..85861545ec4620a6cfd06876dad091637bd29b0b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -464,7 +464,7 @@ public class LevelChunk implements ChunkAccess { return null; } - chunksection = new LevelChunkSection(j >> 4 << 4); + chunksection = new LevelChunkSection(j >> 4 << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters this.sections[j >> 4] = 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 f5db97fb0dac78e1d9aa68d0417aa13f39914f52..38c7c5f18fc84d4a1de2da1ddc6d3ac37c25f341 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -1,9 +1,11 @@ package net.minecraft.world.level.chunk; import java.util.function.Predicate; +import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info import javax.annotation.Nullable; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.FriendlyByteBuf; +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; @@ -18,16 +20,22 @@ 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 i, ChunkAccess chunk, Level world, boolean initializeBlocks) { + this(i, (short) 0, (short) 0, (short) 0, chunk, world, initializeBlocks); + // Paper end } - public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) { - this.bottomBlockY = yOffset; - this.nonEmptyBlockCount = nonEmptyBlockCount; - this.tickingBlockCount = randomTickableBlockCount; - this.tickingFluidCount = nonEmptyFluidCount; - this.states = new PalettedContainer<>(LevelChunkSection.GLOBAL_BLOCKSTATE_PALETTE, Block.BLOCK_STATE_REGISTRY, NbtUtils::readBlockState, NbtUtils::writeBlockState, Blocks.AIR.defaultBlockState()); + // Paper start - Anti-Xray - Add parameters + @Deprecated public LevelChunkSection(int yOffset, short nonEmptyBlockCount, short randomTickableBlockCount, short nonEmptyFluidCount) { this(yOffset, nonEmptyBlockCount, randomTickableBlockCount, nonEmptyFluidCount, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public LevelChunkSection(int i, short short0, short short1, short short2, ChunkAccess chunk, Level world, boolean initializeBlocks) { + // Paper end + this.bottomBlockY = i; + this.nonEmptyBlockCount = short0; + this.tickingBlockCount = short1; + this.tickingFluidCount = short2; + this.states = new PalettedContainer<>(LevelChunkSection.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 final BlockState getBlockState(int x, int y, int z) { // Paper @@ -139,10 +147,14 @@ public class LevelChunkSection { return this.states; } - public void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER - public void write(FriendlyByteBuf packetdataserializer) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public final void writeChunkSection(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere + @Deprecated public final void write(FriendlyByteBuf packetdataserializer) { this.writeChunkSection(packetdataserializer, null); } // Notice for updates: Please make sure this method isn't used anywhere + public final void writeChunkSection(FriendlyByteBuf packetDataSerializer, ChunkPacketInfo chunkPacketInfo) { this.b(packetDataSerializer, chunkPacketInfo); } // OBFHELPER + public void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo chunkPacketInfo) { + // Paper end packetdataserializer.writeShort(this.nonEmptyBlockCount); - this.states.write(packetdataserializer); + this.states.writeDataPaletteBlock(packetdataserializer, 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 917b0a64083ebbe24321089b784b91f3af4918b9..dd252372e1e380674b1191e9ea265cbb10de437b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -1,6 +1,7 @@ package net.minecraft.world.level.chunk; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info import java.util.Arrays; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; @@ -26,6 +27,7 @@ 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 @@ -50,14 +52,47 @@ public class PalettedContainer implements PaletteResize { //this.j.unlock(); // Paper - disable this } - public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { - 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 + @Deprecated public PalettedContainer(Palette fallbackPalette, IdMapper idList, Function elementDeserializer, Function elementSerializer, T defaultElement) { this(fallbackPalette, idList, elementDeserializer, elementSerializer, defaultElement, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public PalettedContainer(Palette datapalette, IdMapper registryblockid, Function function, Function function1, T t0, T[] predefinedObjects, boolean initialize) { + // Paper end + this.globalPalette = datapalette; + this.registry = registryblockid; + this.reader = function; + this.writer = function1; + this.defaultValue = t0; + // 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.getDataPalette() != this.getDataPaletteGlobal()) { + for (int i = 0; i < this.predefinedObjects.length; i++) { + this.getDataPalette().getOrCreateIdFor(this.predefinedObjects[i]); + } + } } + // Paper end private static int getIndex(int x, int y, int z) { return y << 8 | z << 4 | x; @@ -92,6 +127,7 @@ public class PalettedContainer implements PaletteResize { int j; + this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects for (j = 0; j < databits.getSize(); ++j) { T t1 = datapalette.valueFor(databits.get(j)); @@ -141,24 +177,38 @@ public class PalettedContainer implements PaletteResize { return t0 == null ? this.defaultValue : t0; } - public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // Paper - OBFHELPER - public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer) { this.write(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere + @Deprecated public void write(FriendlyByteBuf buf) { this.writeDataPaletteBlock(buf, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere + public void writeDataPaletteBlock(FriendlyByteBuf packetDataSerializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { this.b(packetDataSerializer, chunkPacketInfo, chunkSectionIndex); } // OBFHELPER + public synchronized void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - synchronize + // Paper end this.acquire(); - buf.writeByte(this.bits); - this.palette.write(buf); - buf.writeLongArray(this.storage.getRaw()); + packetdataserializer.writeByte(this.bits); + this.palette.write(packetdataserializer); + // Paper start - Anti-Xray - Add chunk packet info + if (chunkPacketInfo != null) { + chunkPacketInfo.setBitsPerObject(chunkSectionIndex, this.getBitsPerObject()); + chunkPacketInfo.setDataPalette(chunkSectionIndex, this.getDataPalette()); + chunkPacketInfo.setDataBitsIndex(chunkSectionIndex, packetdataserializer.writerIndex() + FriendlyByteBuf.countBytes(this.getDataBits().getDataBits().length)); + chunkPacketInfo.setPredefinedObjects(chunkSectionIndex, this.predefinedObjects); + } + // Paper end + packetdataserializer.writeLongArray(this.storage.getRaw()); this.release(); } public synchronized void read(ListTag paletteTag, long[] data) { // Paper - synchronize this.acquire(); - int i = Math.max(4, Mth.ceillog2(paletteTag.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)? + int i = Math.max(4, Mth.ceillog2(paletteTag.size() + (this.predefinedObjects == null ? 0 : this.predefinedObjects.length))); // Paper - Anti-Xray - Calculate the size with predefined objects - if (i != this.bits) { + if (true || i != this.bits) { // Paper - Anti-Xray - Not initialized yet this.setBits(i); } this.palette.read(paletteTag); + this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects int j = data.length * 64 / 4096; if (this.palette == this.globalPalette) { 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 d8b7b210484079c9ca2c34831c84102cba6692f5..87fd585141ad9818fca0b697cb4c87248fe7ce11 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java @@ -64,16 +64,24 @@ public class ProtoChunk implements ChunkAccess { private long inhabitedTime; private final Map carvingMasks; private volatile boolean isLightCorrect; + private final Level world; // Paper - Anti-Xray - Add world - public ProtoChunk(ChunkPos pos, UpgradeData upgradeData) { - this(pos, upgradeData, (LevelChunkSection[]) null, new ProtoTickList<>((block) -> { + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData) { this(pos, upgradeData, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkPos chunkcoordintpair, UpgradeData chunkconverter, Level world) { + // Paper end + this(chunkcoordintpair, chunkconverter, (LevelChunkSection[]) null, new ProtoTickList<>((block) -> { return block == null || block.defaultBlockState().isAir(); - }, pos), new ProtoTickList<>((fluidtype) -> { + }, chunkcoordintpair), new ProtoTickList<>((fluidtype) -> { return fluidtype == null || fluidtype == Fluids.EMPTY; - }, pos)); + }, chunkcoordintpair), world); // Paper - Anti-Xray - Add world } - public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler) { + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler) { this(pos, upgradeData, sections, blockTickScheduler, fluidTickScheduler, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkPos chunkcoordintpair, UpgradeData chunkconverter, @Nullable LevelChunkSection[] achunksection, ProtoTickList protochunkticklist, ProtoTickList protochunkticklist1, Level world) { + this.world = world; + // Paper end this.heightmaps = Maps.newEnumMap(Heightmap.Types.class); this.status = ChunkStatus.EMPTY; this.blockEntities = Maps.newHashMap(); @@ -85,15 +93,15 @@ public class ProtoChunk implements ChunkAccess { this.structureStarts = Maps.newHashMap(); this.structuresRefences = Maps.newHashMap(); this.carvingMasks = new Object2ObjectArrayMap(); - this.chunkPos = pos; - this.upgradeData = upgradeData; - this.blockTicks = blockTickScheduler; - this.liquidTicks = fluidTickScheduler; - if (sections != null) { - if (this.sections.length == sections.length) { - System.arraycopy(sections, 0, this.sections, 0, this.sections.length); + this.chunkPos = chunkcoordintpair; + this.upgradeData = chunkconverter; + this.blockTicks = protochunkticklist; + this.liquidTicks = protochunkticklist1; + if (achunksection != null) { + if (this.sections.length == achunksection.length) { + System.arraycopy(achunksection, 0, this.sections, 0, this.sections.length); } else { - ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", sections.length, this.sections.length); + ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", achunksection.length, this.sections.length); } } @@ -228,7 +236,7 @@ public class ProtoChunk implements ChunkAccess { public LevelChunkSection getOrCreateSection(int y) { if (this.sections[y] == LevelChunk.EMPTY_SECTION) { - this.sections[y] = new LevelChunkSection(y << 4); + this.sections[y] = new LevelChunkSection(y << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters } return this.sections[y]; 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 969130442b529eaac6f708107ff129f89cc0af90..8dbd1dc2de400ad0c6c2be49ba09dfc03216ffd2 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 @@ -101,7 +101,7 @@ public class ChunkSerializer { byte b0 = nbttagcompound2.getByte("Y"); if (nbttagcompound2.contains("Palette", 9) && nbttagcompound2.contains("BlockStates", 12)) { - LevelChunkSection chunksection = new LevelChunkSection(b0 << 4); + LevelChunkSection chunksection = new LevelChunkSection(b0 << 4, null, world, false); // Paper - Anti-Xray - Add parameters chunksection.getStates().read(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates")); chunksection.recalcBlockCounts(); @@ -165,7 +165,7 @@ public class ChunkSerializer { // CraftBukkit end }); } else { - ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1); + ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world); // Paper - Anti-Xray - Add parameter protochunk.setBiomes(biomestorage); object = protochunk; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index 423594177fe78600755d913f169f28dd1bfa2b37..74bad15034d9d55fb70931f38868f812160c6305 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -43,7 +43,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) { @@ -287,7 +287,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 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 a94c65f4d63a06be099fd67b0b7756c5b45b84a0..8d72cd6a44cf462cfe3adac9bf99a16883a587df 100644 --- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java @@ -21,9 +21,11 @@ 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.getMaxHeight()); + this.world = world; // Paper - Anti-Xray - Add world } /* pp for tests */ CraftChunkData(int maxHeight) { @@ -157,7 +159,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { private LevelChunkSection getChunkSection(int y, boolean create) { LevelChunkSection section = sections[y >> 4]; if (create && section == null) { - sections[y >> 4] = section = new LevelChunkSection(y >> 4 << 4); + sections[y >> 4] = section = new LevelChunkSection(y >> 4 << 4, null, world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) world).getHandle() : null, true); // Paper - Anti-Xray - Add parameters } return section; }