papermc/patches/removed/1.17/0364-Anti-Xray.patch
2021-06-13 17:17:44 +02:00

1626 lines
88 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: stonar96 <minecraft.stonar96@gmail.com>
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<String> hiddenBlocks;
+ public List<String> 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<BlockState> getChunkPacketInfo(ClientboundLevelChunkPacket packetPlayOutMapChunk, LevelChunk chunk, int chunkSectionSelector) {
+ return null;
+ }
+
+ public void modifyBlocks(ClientboundLevelChunkPacket packetPlayOutMapChunk, ChunkPacketInfo<BlockState> 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<String> 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<BlockState> predefinedBlockDataList = new LinkedList<BlockState>();
+
+ 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<? extends E> 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<BlockState> predefinedBlockDataSet = new LinkedHashSet<BlockState>();
+ // Therefore addAll(Collection<? extends E> 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<BlockState> 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<int[]> predefinedBlockDataBits = ThreadLocal.withInitial(() -> new int[getPredefinedBlockDataFullLength()]);
+ private static final ThreadLocal<boolean[]> solid = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+ private static final ThreadLocal<boolean[]> obfuscate = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
+ private static final ThreadLocal<boolean[][]> current = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private static final ThreadLocal<boolean[][]> next = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private static final ThreadLocal<boolean[][]> 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<BlockState> 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<T> {
+
+ 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<T> getDataPalette(int chunkSectionIndex) {
+ return (Palette<T>) dataPalettes[chunkSectionIndex];
+ }
+
+ public void setDataPalette(int chunkSectionIndex, Palette<T> 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<BlockState> 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<ClientGamePacketListe
private List<CompoundTag> 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<Packet> 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<ClientGamePacketListe
return extraPackets;
}
// Paper end
- public ClientboundLevelChunkPacket(LevelChunk chunk, int includedSectionsMask) {
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public ClientboundLevelChunkPacket(LevelChunk chunk, int includedSectionsMask) { this(chunk, includedSectionsMask, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
+ public ClientboundLevelChunkPacket(LevelChunk chunk, int i, boolean modifyBlocks) {
+ ChunkPacketInfo<BlockState> 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<ClientGamePacketListe
this.biomes = chunk.getBiomes().writeBiomes();
}
- this.buffer = new byte[this.calculateChunkSize(chunk, includedSectionsMask)];
- this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, includedSectionsMask);
+ this.buffer = new byte[this.calculateChunkSize(chunk, i)];
+ // Paper start - Anti-Xray - Add chunk packet info
+ if (chunkPacketInfo != null) {
+ chunkPacketInfo.setData(this.getData());
+ }
+ this.availableSections = this.writeChunk(new FriendlyByteBuf(this.getWriteBuffer()), chunk, i, chunkPacketInfo);
+ // Paper end
this.blockEntitiesTags = Lists.newArrayList();
iterator = chunk.getBlockEntities().entrySet().iterator();
int totalTileEntities = 0; // Paper
@@ -77,7 +94,7 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
BlockEntity tileentity = (BlockEntity) entry.getValue();
int j = blockposition.getY() >> 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<ClientGamePacketListe
this.blockEntitiesTags.add(nbttagcompound);
}
}
+ chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
+ }
+ // Paper start - Async-Anti-Xray - Getter and Setter for the ready flag
+ @Override
+ public boolean isReady() {
+ return this.ready;
+ }
+
+ public void setReady(boolean ready) {
+ this.ready = ready;
}
+ // Paper end
@Override
public void read(FriendlyByteBuf buf) throws IOException {
@@ -160,8 +188,12 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
return bytebuf;
}
- public int writeChunk(FriendlyByteBuf packetDataSerializer, LevelChunk chunk, int chunkSectionSelector) { return this.extractChunkData(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER
- public int extractChunkData(FriendlyByteBuf packetdataserializer, LevelChunk chunk, int includedSectionsMask) {
+ // Paper start - Anti-Xray - Add chunk packet info
+ @Deprecated public int writeChunk(FriendlyByteBuf packetDataSerializer, LevelChunk chunk, int chunkSectionSelector) { return this.extractChunkData(packetDataSerializer, chunk, chunkSectionSelector); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere
+ @Deprecated public int extractChunkData(FriendlyByteBuf packetdataserializer, LevelChunk chunk, int includedSectionsMask) { return this.writeChunk(packetdataserializer, chunk, includedSectionsMask, null); } // Notice for updates: Please make sure this method isn't used anywhere
+ public int writeChunk(FriendlyByteBuf packetDataSerializer, LevelChunk chunk, int chunkSectionSelector, ChunkPacketInfo<BlockState> chunkPacketInfo) { return this.a(packetDataSerializer, chunk, chunkSectionSelector, chunkPacketInfo); } // OBFHELPER
+ public int a(FriendlyByteBuf packetdataserializer, LevelChunk chunk, int i, ChunkPacketInfo<BlockState> chunkPacketInfo) {
+ // Paper end
int j = 0;
LevelChunkSection[] achunksection = chunk.getSections();
int k = 0;
@@ -169,9 +201,9 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
for (int l = achunksection.length; k < l; ++k) {
LevelChunkSection chunksection = achunksection[k];
- if (chunksection != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !chunksection.isEmpty()) && (includedSectionsMask & 1 << k) != 0) {
+ if (chunksection != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !chunksection.isEmpty()) && (i & 1 << k) != 0) {
j |= 1 << k;
- chunksection.write(packetdataserializer);
+ chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info
}
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index cfec04e12dfaeb8852dc129a6a7e68c61dac54b6..b2d668607c2b5122d06fa75f77b3cef44100fe28 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -654,7 +654,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
this.markPositionReplaceable(pos);
- return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY));
+ return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level)); // Paper - Anti-Xray - Add parameter
}, this.mainThreadExecutor);
}
@@ -1402,9 +1402,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
+ private final void sendChunk(ServerPlayer entityplayer, 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<net.minecraft.world.level.Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> 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<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) {
+ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, final DimensionType dimensionmanager, Supplier<ProfilerFiller> 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<BlockState> 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<BlockState> chunkPacketInfo) { this.b(packetDataSerializer, chunkPacketInfo); } // OBFHELPER
+ public void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo<BlockState> 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<T> implements PaletteResize<T> {
private final Function<CompoundTag, T> reader;
private final Function<T, CompoundTag> 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<T> palette; private Palette<T> getDataPalette() { return this.palette; } // Paper - OBFHELPER
private int bits; private int getBitsPerObject() { return this.bits; } // Paper - OBFHELPER
@@ -50,14 +52,47 @@ public class PalettedContainer<T> implements PaletteResize<T> {
//this.j.unlock(); // Paper - disable this
}
- public PalettedContainer(Palette<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> 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<T> fallbackPalette, IdMapper<T> idList, Function<CompoundTag, T> elementDeserializer, Function<T, CompoundTag> 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<T> datapalette, IdMapper<T> registryblockid, Function<CompoundTag, T> function, Function<T, CompoundTag> 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<T> implements PaletteResize<T> {
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<T> implements PaletteResize<T> {
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<T> chunkPacketInfo, int chunkSectionIndex) { this.b(packetDataSerializer, chunkPacketInfo, chunkSectionIndex); } // OBFHELPER
+ public synchronized void b(FriendlyByteBuf packetdataserializer, ChunkPacketInfo<T> 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<GenerationStep.Carving, BitSet> 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<Block> blockTickScheduler, ProtoTickList<Fluid> fluidTickScheduler) {
+ // Paper start - Anti-Xray - Add world
+ @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoTickList<Block> blockTickScheduler, ProtoTickList<Fluid> 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<Block> protochunkticklist, ProtoTickList<Fluid> 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<net.minecraft.world.level.block.state.BlockState> emptyBlockIDs = new LevelChunkSection(0).getStates();
+ private static final PalettedContainer<net.minecraft.world.level.block.state.BlockState> 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<BlockPos> 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;
}