1638 lines
88 KiB
Diff
1638 lines
88 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: stonar96 <minecraft.stonar96@gmail.com>
|
|
Date: Thu, 25 Nov 2021 13:27:51 +0100
|
|
Subject: [PATCH] Anti-Xray
|
|
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index c81364946460a6366bf70457c2025d4275f50f77..b18d35bc67f3124ca8f4a67e3dadabef52e2588a 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -1,11 +1,13 @@
|
|
package com.destroystokyo.paper;
|
|
|
|
+import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import java.util.stream.Collectors;
|
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|
import net.minecraft.world.entity.MobCategory;
|
|
+import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray.EngineMode;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.configuration.file.YamlConfiguration;
|
|
import org.spigotmc.SpigotWorldConfig;
|
|
@@ -523,5 +525,40 @@ public class PaperWorldConfig {
|
|
private void lightQueueSize() {
|
|
lightQueueSize = getInt("light-queue-size", lightQueueSize);
|
|
}
|
|
+
|
|
+ public boolean antiXray;
|
|
+ public EngineMode engineMode;
|
|
+ public int maxBlockHeight;
|
|
+ public int updateRadius;
|
|
+ public boolean lavaObscures;
|
|
+ public boolean usePermission;
|
|
+ public List<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;
|
|
+ maxBlockHeight = getInt("anti-xray.max-block-height", 64);
|
|
+ updateRadius = getInt("anti-xray.update-radius", 2);
|
|
+ lavaObscures = getBoolean("anti-xray.lava-obscures", false);
|
|
+ usePermission = getBoolean("anti-xray.use-permission", false);
|
|
+ hiddenBlocks = getList("anti-xray.hidden-blocks", Arrays.asList("copper_ore", "deepslate_copper_ore", "gold_ore", "deepslate_gold_ore", "iron_ore", "deepslate_iron_ore",
|
|
+ "coal_ore", "deepslate_coal_ore", "lapis_ore", "deepslate_lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "deepslate_diamond_ore",
|
|
+ "redstone_ore", "deepslate_redstone_ore", "clay", "emerald_ore", "deepslate_emerald_ore", "ender_chest"));
|
|
+ replacementBlocks = getList("anti-xray.replacement-blocks", Arrays.asList("stone", "oak_planks", "deepslate"));
|
|
+ if (PaperConfig.version < 19) {
|
|
+ hiddenBlocks.remove("lit_redstone_ore");
|
|
+ int index = replacementBlocks.indexOf("planks");
|
|
+ if (index != -1) {
|
|
+ replacementBlocks.set(index, "oak_planks");
|
|
+ }
|
|
+ set("anti-xray.hidden-blocks", hiddenBlocks);
|
|
+ set("anti-xray.replacement-blocks", replacementBlocks);
|
|
+ }
|
|
+ log("Anti-Xray: " + (antiXray ? "enabled" : "disabled") + " / Engine Mode: " + engineMode.getDescription() + " / Up to " + ((maxBlockHeight >> 4) << 4) + " blocks / Update Radius: " + updateRadius);
|
|
+ if (antiXray && usePermission) {
|
|
+ Bukkit.getLogger().warning("You have enabled permission-based Anti-Xray checking - depending on your permission plugin, this may cause performance issues");
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java
|
|
@@ -0,0 +1,51 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+public final class BitStorageReader {
|
|
+
|
|
+ private byte[] buffer;
|
|
+ private int bits;
|
|
+ private int mask;
|
|
+ private int longInBufferIndex;
|
|
+ private int bitInLongIndex;
|
|
+ private long current;
|
|
+
|
|
+ public void setBuffer(byte[] buffer) {
|
|
+ this.buffer = buffer;
|
|
+ }
|
|
+
|
|
+ public void setBits(int bits) {
|
|
+ this.bits = bits;
|
|
+ mask = (1 << bits) - 1;
|
|
+ }
|
|
+
|
|
+ public void setIndex(int index) {
|
|
+ longInBufferIndex = index;
|
|
+ bitInLongIndex = 0;
|
|
+ init();
|
|
+ }
|
|
+
|
|
+ private void init() {
|
|
+ if (buffer.length > longInBufferIndex + 7) {
|
|
+ current = ((((long) buffer[longInBufferIndex]) << 56)
|
|
+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
|
|
+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
|
|
+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
|
|
+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
|
|
+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
|
|
+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
|
|
+ | (((long) buffer[longInBufferIndex + 7] & 0xff)));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int read() {
|
|
+ if (bitInLongIndex + bits > 64) {
|
|
+ bitInLongIndex = 0;
|
|
+ longInBufferIndex += 8;
|
|
+ init();
|
|
+ }
|
|
+
|
|
+ int value = (int) (current >>> bitInLongIndex) & mask;
|
|
+ bitInLongIndex += bits;
|
|
+ return value;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java
|
|
@@ -0,0 +1,79 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+public final class BitStorageWriter {
|
|
+
|
|
+ private byte[] buffer;
|
|
+ private int bits;
|
|
+ private long mask;
|
|
+ private int longInBufferIndex;
|
|
+ private int bitInLongIndex;
|
|
+ private long current;
|
|
+ private boolean dirty;
|
|
+
|
|
+ public void setBuffer(byte[] buffer) {
|
|
+ this.buffer = buffer;
|
|
+ }
|
|
+
|
|
+ public void setBits(int bits) {
|
|
+ this.bits = bits;
|
|
+ mask = (1L << bits) - 1;
|
|
+ }
|
|
+
|
|
+ public void setIndex(int index) {
|
|
+ longInBufferIndex = index;
|
|
+ bitInLongIndex = 0;
|
|
+ init();
|
|
+ }
|
|
+
|
|
+ private void init() {
|
|
+ if (buffer.length > longInBufferIndex + 7) {
|
|
+ current = ((((long) buffer[longInBufferIndex]) << 56)
|
|
+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
|
|
+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
|
|
+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
|
|
+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
|
|
+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
|
|
+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
|
|
+ | (((long) buffer[longInBufferIndex + 7] & 0xff)));
|
|
+ }
|
|
+
|
|
+ dirty = false;
|
|
+ }
|
|
+
|
|
+ public void flush() {
|
|
+ if (dirty && buffer.length > longInBufferIndex + 7) {
|
|
+ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff);
|
|
+ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff);
|
|
+ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff);
|
|
+ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff);
|
|
+ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff);
|
|
+ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff);
|
|
+ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff);
|
|
+ buffer[longInBufferIndex + 7] = (byte) (current & 0xff);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void write(int value) {
|
|
+ if (bitInLongIndex + bits > 64) {
|
|
+ flush();
|
|
+ bitInLongIndex = 0;
|
|
+ longInBufferIndex += 8;
|
|
+ init();
|
|
+ }
|
|
+
|
|
+ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex;
|
|
+ dirty = true;
|
|
+ bitInLongIndex += bits;
|
|
+ }
|
|
+
|
|
+ public void skip() {
|
|
+ bitInLongIndex += bits;
|
|
+
|
|
+ if (bitInLongIndex > 64) {
|
|
+ flush();
|
|
+ bitInLongIndex = bits;
|
|
+ longInBufferIndex += 8;
|
|
+ init();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..aabad39d13ead83042ec2e4dd7f4ed4966af650d
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java
|
|
@@ -0,0 +1,45 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.Direction;
|
|
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
|
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.ServerPlayerGameMode;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+
|
|
+public class ChunkPacketBlockController {
|
|
+
|
|
+ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController();
|
|
+
|
|
+ protected ChunkPacketBlockController() {
|
|
+
|
|
+ }
|
|
+
|
|
+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public ChunkPacketInfo<BlockState> getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
|
|
+ chunkPacket.setReady(true);
|
|
+ }
|
|
+
|
|
+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
|
|
+
|
|
+ }
|
|
+
|
|
+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight) {
|
|
+
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..999b39127a42c2ccec0be4110c93453d38bda397
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java
|
|
@@ -0,0 +1,659 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+import com.destroystokyo.paper.PaperWorldConfig;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.Direction;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
|
+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.ServerPlayerGameMode;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.block.EntityBlock;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.chunk.*;
|
|
+import org.bukkit.Bukkit;
|
|
+
|
|
+import java.util.*;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
+import java.util.function.IntSupplier;
|
|
+
|
|
+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController {
|
|
+
|
|
+ private static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
|
|
+ private static final LevelChunkSection EMPTY_SECTION = null;
|
|
+ private final Executor executor;
|
|
+ private final EngineMode engineMode;
|
|
+ private final int maxBlockHeight;
|
|
+ private final int updateRadius;
|
|
+ private final boolean usePermission;
|
|
+ private final BlockState[] presetBlockStates;
|
|
+ private final BlockState[] presetBlockStatesFull;
|
|
+ private final BlockState[] presetBlockStatesStone;
|
|
+ private final BlockState[] presetBlockStatesNetherrack;
|
|
+ private final BlockState[] presetBlockStatesEndStone;
|
|
+ private final int[] presetBlockStateBitsGlobal;
|
|
+ private final int[] presetBlockStateBitsStoneGlobal;
|
|
+ private final int[] presetBlockStateBitsNetherrackGlobal;
|
|
+ private final int[] presetBlockStateBitsEndStoneGlobal;
|
|
+ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
|
|
+ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
|
|
+ private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION};
|
|
+ private final int maxBlockHeightUpdatePosition;
|
|
+
|
|
+ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) {
|
|
+ this.executor = executor;
|
|
+ PaperWorldConfig paperWorldConfig = level.paperConfig;
|
|
+ engineMode = paperWorldConfig.engineMode;
|
|
+ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4;
|
|
+ updateRadius = paperWorldConfig.updateRadius;
|
|
+ usePermission = paperWorldConfig.usePermission;
|
|
+ List<String> toObfuscate;
|
|
+
|
|
+ if (engineMode == EngineMode.HIDE) {
|
|
+ toObfuscate = paperWorldConfig.hiddenBlocks;
|
|
+ presetBlockStates = null;
|
|
+ presetBlockStatesFull = null;
|
|
+ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()};
|
|
+ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()};
|
|
+ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()};
|
|
+ presetBlockStateBitsGlobal = null;
|
|
+ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())};
|
|
+ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())};
|
|
+ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())};
|
|
+ } else {
|
|
+ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks);
|
|
+ List<BlockState> presetBlockStateList = new LinkedList<>();
|
|
+
|
|
+ for (String id : paperWorldConfig.hiddenBlocks) {
|
|
+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
|
|
+
|
|
+ if (block != null && !(block instanceof EntityBlock)) {
|
|
+ toObfuscate.add(id);
|
|
+ presetBlockStateList.add(block.defaultBlockState());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // The doc of the LinkedHashSet(Collection<? extends E>) 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> presetBlockStateSet = new LinkedHashSet<>();
|
|
+ // Therefore addAll(Collection<? extends E>) is used, which guarantees this order in the doc
|
|
+ presetBlockStateSet.addAll(presetBlockStateList);
|
|
+ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]);
|
|
+ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]);
|
|
+ presetBlockStatesStone = null;
|
|
+ presetBlockStatesNetherrack = null;
|
|
+ presetBlockStatesEndStone = null;
|
|
+ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length];
|
|
+
|
|
+ for (int i = 0; i < presetBlockStatesFull.length; i++) {
|
|
+ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]);
|
|
+ }
|
|
+
|
|
+ presetBlockStateBitsStoneGlobal = null;
|
|
+ presetBlockStateBitsNetherrackGlobal = null;
|
|
+ presetBlockStateBitsEndStoneGlobal = null;
|
|
+ }
|
|
+
|
|
+ for (String id : toObfuscate) {
|
|
+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
|
|
+
|
|
+ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
|
|
+ if (block != null && !block.defaultBlockState().isAir()) {
|
|
+ // Replace all block states of a specified block
|
|
+ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
|
|
+ obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0));
|
|
+ BlockPos zeroPos = new BlockPos(0, 0, 0);
|
|
+
|
|
+ for (int i = 0; i < solidGlobal.length; i++) {
|
|
+ BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i);
|
|
+
|
|
+ if (blockState != null) {
|
|
+ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos)
|
|
+ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState();
|
|
+ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used
|
|
+ // shulker box checks TE.
|
|
+ }
|
|
+ }
|
|
+
|
|
+ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1;
|
|
+ }
|
|
+
|
|
+ private int getPresetBlockStatesFullLength() {
|
|
+ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) {
|
|
+ // Return the block states to be added to the paletted containers so that they can be used for obfuscation
|
|
+ if (bottomBlockY < maxBlockHeight) {
|
|
+ if (engineMode == EngineMode.HIDE) {
|
|
+ return switch (level.getWorld().getEnvironment()) {
|
|
+ case NETHER -> presetBlockStatesNetherrack;
|
|
+ case THE_END -> presetBlockStatesEndStone;
|
|
+ default -> presetBlockStatesStone;
|
|
+ };
|
|
+ }
|
|
+
|
|
+ return presetBlockStates;
|
|
+ }
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) {
|
|
+ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
|
|
+ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later
|
|
+ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo<BlockState> chunkPacketInfo) {
|
|
+ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) {
|
|
+ chunkPacket.setReady(true);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!Bukkit.isPrimaryThread()) {
|
|
+ // Plugins?
|
|
+ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ LevelChunk chunk = chunkPacketInfo.getChunk();
|
|
+ int x = chunk.getPos().x;
|
|
+ int z = chunk.getPos().z;
|
|
+ Level level = chunk.getLevel();
|
|
+ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1));
|
|
+ executor.execute((Runnable) chunkPacketInfo);
|
|
+ }
|
|
+
|
|
+ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal)
|
|
+ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
|
|
+ private final ThreadLocal<int[]> presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]);
|
|
+ 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[][]> NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
|
|
+
|
|
+ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
|
|
+ int[] presetBlockStateBits = this.presetBlockStateBits.get();
|
|
+ boolean[] solid = SOLID.get();
|
|
+ boolean[] obfuscate = OBFUSCATE.get();
|
|
+ boolean[][] current = CURRENT.get();
|
|
+ boolean[][] next = NEXT.get();
|
|
+ boolean[][] nextNext = NEXT_NEXT.get();
|
|
+ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
|
|
+ BitStorageReader bitStorageReader = new BitStorageReader();
|
|
+ BitStorageWriter bitStorageWriter = new BitStorageWriter();
|
|
+ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4];
|
|
+ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk();
|
|
+ Level level = chunk.getLevel();
|
|
+ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount() - 1);
|
|
+ boolean[] solidTemp = null;
|
|
+ boolean[] obfuscateTemp = null;
|
|
+ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer());
|
|
+ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer());
|
|
+ int numberOfBlocks = presetBlockStateBits.length;
|
|
+ // Keep the lambda expressions as simple as possible. They are used very frequently.
|
|
+ IntSupplier random = numberOfBlocks == 1 ? (() -> 0) : new IntSupplier() {
|
|
+ private int state;
|
|
+
|
|
+ {
|
|
+ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getAsInt() {
|
|
+ // https://en.wikipedia.org/wiki/Xorshift
|
|
+ state ^= state << 13;
|
|
+ state ^= state >>> 17;
|
|
+ state ^= state << 5;
|
|
+ // https://www.pcg-random.org/posts/bounded-rands.html
|
|
+ return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32);
|
|
+ }
|
|
+ };
|
|
+
|
|
+ for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) {
|
|
+ if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) {
|
|
+ int[] presetBlockStateBitsTemp;
|
|
+
|
|
+ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) {
|
|
+ if (engineMode == EngineMode.HIDE) {
|
|
+ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) {
|
|
+ case NETHER -> presetBlockStateBitsNetherrackGlobal;
|
|
+ case THE_END -> presetBlockStateBitsEndStoneGlobal;
|
|
+ default -> presetBlockStateBitsStoneGlobal;
|
|
+ };
|
|
+ } else {
|
|
+ presetBlockStateBitsTemp = presetBlockStateBitsGlobal;
|
|
+ }
|
|
+ } else {
|
|
+ // If it's presetBlockStates, use this.presetBlockStatesFull instead
|
|
+ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex);
|
|
+ presetBlockStateBitsTemp = presetBlockStateBits;
|
|
+
|
|
+ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) {
|
|
+ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible
|
|
+ // For more details see the comments in the readPalette method
|
|
+ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
|
|
+
|
|
+ // Check if the chunk section below was not obfuscated
|
|
+ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) {
|
|
+ // If so, initialize some stuff
|
|
+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
|
|
+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex));
|
|
+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal);
|
|
+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal);
|
|
+ // Read the blocks of the upper layer of the chunk section below if it exists
|
|
+ LevelChunkSection belowChunkSection = null;
|
|
+ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION;
|
|
+
|
|
+ for (int z = 0; z < 16; z++) {
|
|
+ for (int x = 0; x < 16; x++) {
|
|
+ current[z][x] = true;
|
|
+ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section
|
|
+ bitStorageWriter.setBits(0);
|
|
+ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random);
|
|
+ }
|
|
+
|
|
+ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
|
|
+ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex];
|
|
+ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex];
|
|
+ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex];
|
|
+ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex];
|
|
+
|
|
+ // Obfuscate all layers of the current chunk section except the upper one
|
|
+ for (int y = 0; y < 15; y++) {
|
|
+ boolean[][] temp = current;
|
|
+ current = next;
|
|
+ next = nextNext;
|
|
+ nextNext = temp;
|
|
+ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
|
|
+ }
|
|
+
|
|
+ // Check if the chunk section above doesn't need obfuscation
|
|
+ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) {
|
|
+ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists
|
|
+ LevelChunkSection aboveChunkSection;
|
|
+
|
|
+ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) {
|
|
+ boolean[][] temp = current;
|
|
+ current = next;
|
|
+ next = nextNext;
|
|
+ nextNext = temp;
|
|
+
|
|
+ for (int z = 0; z < 16; z++) {
|
|
+ for (int x = 0; x < 16; x++) {
|
|
+ if (isTransparent(aboveChunkSection, x, 0, z)) {
|
|
+ current[z][x] = true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // There is nothing to read anymore
|
|
+ bitStorageReader.setBits(0);
|
|
+ solid[0] = true;
|
|
+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
|
|
+ }
|
|
+ } else {
|
|
+ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section
|
|
+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1));
|
|
+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1));
|
|
+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal);
|
|
+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal);
|
|
+ boolean[][] temp = current;
|
|
+ current = next;
|
|
+ next = nextNext;
|
|
+ nextNext = temp;
|
|
+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random);
|
|
+ }
|
|
+
|
|
+ bitStorageWriter.flush();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ chunkPacketInfoAntiXray.getChunkPacket().setReady(true);
|
|
+ }
|
|
+
|
|
+ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) {
|
|
+ // First block of first line
|
|
+ int bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[0][0] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[0][1] = true;
|
|
+ next[1][0] = true;
|
|
+ } else {
|
|
+ if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[0][0] = true;
|
|
+ }
|
|
+
|
|
+ // First line
|
|
+ for (int x = 1; x < 15; x++) {
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[0][x] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[0][x - 1] = true;
|
|
+ next[0][x + 1] = true;
|
|
+ next[1][x] = true;
|
|
+ } else {
|
|
+ if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[0][x] = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Last block of first line
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[0][15] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[0][14] = true;
|
|
+ next[1][15] = true;
|
|
+ } else {
|
|
+ if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[0][15] = true;
|
|
+ }
|
|
+
|
|
+ // All inner lines
|
|
+ for (int z = 1; z < 15; z++) {
|
|
+ // First block
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[z][0] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[z][1] = true;
|
|
+ next[z - 1][0] = true;
|
|
+ next[z + 1][0] = true;
|
|
+ } else {
|
|
+ if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[z][0] = true;
|
|
+ }
|
|
+
|
|
+ // All inner blocks
|
|
+ for (int x = 1; x < 15; x++) {
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[z][x] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[z][x - 1] = true;
|
|
+ next[z][x + 1] = true;
|
|
+ next[z - 1][x] = true;
|
|
+ next[z + 1][x] = true;
|
|
+ } else {
|
|
+ if (current[z][x]) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[z][x] = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Last block
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[z][15] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[z][14] = true;
|
|
+ next[z - 1][15] = true;
|
|
+ next[z + 1][15] = true;
|
|
+ } else {
|
|
+ if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[z][15] = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // First block of last line
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[15][0] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[15][1] = true;
|
|
+ next[14][0] = true;
|
|
+ } else {
|
|
+ if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[15][0] = true;
|
|
+ }
|
|
+
|
|
+ // Last line
|
|
+ for (int x = 1; x < 15; x++) {
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[15][x] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[15][x - 1] = true;
|
|
+ next[15][x + 1] = true;
|
|
+ next[14][x] = true;
|
|
+ } else {
|
|
+ if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[15][x] = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Last block of last line
|
|
+ bits = bitStorageReader.read();
|
|
+
|
|
+ if (nextNext[15][15] = !solid[bits]) {
|
|
+ bitStorageWriter.skip();
|
|
+ next[15][14] = true;
|
|
+ next[14][15] = true;
|
|
+ } else {
|
|
+ if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) {
|
|
+ bitStorageWriter.skip();
|
|
+ } else {
|
|
+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!obfuscate[bits]) {
|
|
+ next[15][15] = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) {
|
|
+ if (chunkSection == EMPTY_SECTION) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))];
|
|
+ } catch (MissingPaletteEntryException e) {
|
|
+ // Race condition / visibility issue / no happens-before relationship
|
|
+ // We don't care and treat the block as transparent
|
|
+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean[] readPalette(Palette<BlockState> palette, boolean[] temp, boolean[] global) {
|
|
+ if (palette instanceof GlobalPalette) {
|
|
+ return global;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ for (int i = 0; i < palette.getSize(); i++) {
|
|
+ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))];
|
|
+ }
|
|
+ } catch (MissingPaletteEntryException e) {
|
|
+ // Race condition / visibility issue / no happens-before relationship
|
|
+ // We don't care because we at least see the state as it was when the chunk packet was created
|
|
+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here
|
|
+ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data
|
|
+ }
|
|
+
|
|
+ return temp;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
|
|
+ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) {
|
|
+ updateNearbyBlocks(level, blockPos);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight) {
|
|
+ if (blockPos.getY() <= maxBlockHeightUpdatePosition) {
|
|
+ updateNearbyBlocks(serverPlayerGameMode.level, blockPos);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void updateNearbyBlocks(Level level, BlockPos blockPos) {
|
|
+ if (updateRadius >= 2) {
|
|
+ BlockPos temp = blockPos.west();
|
|
+ updateBlock(level, temp);
|
|
+ updateBlock(level, temp.west());
|
|
+ updateBlock(level, temp.below());
|
|
+ updateBlock(level, temp.above());
|
|
+ updateBlock(level, temp.north());
|
|
+ updateBlock(level, temp.south());
|
|
+ updateBlock(level, temp = blockPos.east());
|
|
+ updateBlock(level, temp.east());
|
|
+ updateBlock(level, temp.below());
|
|
+ updateBlock(level, temp.above());
|
|
+ updateBlock(level, temp.north());
|
|
+ updateBlock(level, temp.south());
|
|
+ updateBlock(level, temp = blockPos.below());
|
|
+ updateBlock(level, temp.below());
|
|
+ updateBlock(level, temp.north());
|
|
+ updateBlock(level, temp.south());
|
|
+ updateBlock(level, temp = blockPos.above());
|
|
+ updateBlock(level, temp.above());
|
|
+ updateBlock(level, temp.north());
|
|
+ updateBlock(level, temp.south());
|
|
+ updateBlock(level, temp = blockPos.north());
|
|
+ updateBlock(level, temp.north());
|
|
+ updateBlock(level, temp = blockPos.south());
|
|
+ updateBlock(level, temp.south());
|
|
+ } else if (updateRadius == 1) {
|
|
+ updateBlock(level, blockPos.west());
|
|
+ updateBlock(level, blockPos.east());
|
|
+ updateBlock(level, blockPos.below());
|
|
+ updateBlock(level, blockPos.above());
|
|
+ updateBlock(level, blockPos.north());
|
|
+ updateBlock(level, blockPos.south());
|
|
+ } else {
|
|
+ // Do nothing if updateRadius <= 0 (test mode)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void updateBlock(Level level, BlockPos blockPos) {
|
|
+ BlockState blockState = level.getTypeIfLoaded(blockPos);
|
|
+
|
|
+ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) {
|
|
+ ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public enum EngineMode {
|
|
+
|
|
+ HIDE(1, "hide ores"),
|
|
+ OBFUSCATE(2, "obfuscate");
|
|
+
|
|
+ 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..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java
|
|
@@ -0,0 +1,80 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import net.minecraft.world.level.chunk.Palette;
|
|
+
|
|
+public class ChunkPacketInfo<T> {
|
|
+
|
|
+ private final ClientboundLevelChunkWithLightPacket chunkPacket;
|
|
+ private final LevelChunk chunk;
|
|
+ private final int[] bits;
|
|
+ private final Object[] palettes;
|
|
+ private final int[] indexes;
|
|
+ private final Object[][] presetValues;
|
|
+ private byte[] buffer;
|
|
+
|
|
+ public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) {
|
|
+ this.chunkPacket = chunkPacket;
|
|
+ this.chunk = chunk;
|
|
+ int sections = chunk.getSectionsCount();
|
|
+ bits = new int[sections];
|
|
+ palettes = new Object[sections];
|
|
+ indexes = new int[sections];
|
|
+ presetValues = new Object[sections][];
|
|
+ }
|
|
+
|
|
+ public ClientboundLevelChunkWithLightPacket getChunkPacket() {
|
|
+ return chunkPacket;
|
|
+ }
|
|
+
|
|
+ public LevelChunk getChunk() {
|
|
+ return chunk;
|
|
+ }
|
|
+
|
|
+ public byte[] getBuffer() {
|
|
+ return buffer;
|
|
+ }
|
|
+
|
|
+ public void setBuffer(byte[] buffer) {
|
|
+ this.buffer = buffer;
|
|
+ }
|
|
+
|
|
+ public int getBits(int chunkSectionIndex) {
|
|
+ return bits[chunkSectionIndex];
|
|
+ }
|
|
+
|
|
+ public void setBits(int chunkSectionIndex, int bits) {
|
|
+ this.bits[chunkSectionIndex] = bits;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public Palette<T> getPalette(int chunkSectionIndex) {
|
|
+ return (Palette<T>) palettes[chunkSectionIndex];
|
|
+ }
|
|
+
|
|
+ public void setPalette(int chunkSectionIndex, Palette<T> palette) {
|
|
+ palettes[chunkSectionIndex] = palette;
|
|
+ }
|
|
+
|
|
+ public int getIndex(int chunkSectionIndex) {
|
|
+ return indexes[chunkSectionIndex];
|
|
+ }
|
|
+
|
|
+ public void setIndex(int chunkSectionIndex, int index) {
|
|
+ indexes[chunkSectionIndex] = index;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public T[] getPresetValues(int chunkSectionIndex) {
|
|
+ return (T[]) presetValues[chunkSectionIndex];
|
|
+ }
|
|
+
|
|
+ public void setPresetValues(int chunkSectionIndex, T[] presetValues) {
|
|
+ this.presetValues[chunkSectionIndex] = presetValues;
|
|
+ }
|
|
+
|
|
+ public boolean isWritten(int chunkSectionIndex) {
|
|
+ return bits[chunkSectionIndex] != 0;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java
|
|
@@ -0,0 +1,29 @@
|
|
+package com.destroystokyo.paper.antixray;
|
|
+
|
|
+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+
|
|
+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo<BlockState> implements Runnable {
|
|
+
|
|
+ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray;
|
|
+ private LevelChunk[] nearbyChunks;
|
|
+
|
|
+ public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) {
|
|
+ super(chunkPacket, chunk);
|
|
+ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray;
|
|
+ }
|
|
+
|
|
+ public LevelChunk[] getNearbyChunks() {
|
|
+ return nearbyChunks;
|
|
+ }
|
|
+
|
|
+ public void setNearbyChunks(LevelChunk... nearbyChunks) {
|
|
+ this.nearbyChunks = nearbyChunks;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void run() {
|
|
+ chunkPacketBlockControllerAntiXray.obfuscate(this);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
|
index dba11f277f3703e1ee7f5a62f021d319e4ab18fc..0e75764c108c24b3e2c453f2b4f14e798add0eb4 100644
|
|
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
|
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
|
@@ -33,7 +33,10 @@ public class ClientboundLevelChunkPacketData {
|
|
}
|
|
// Paper end
|
|
|
|
- public ClientboundLevelChunkPacketData(LevelChunk chunk) {
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ @Deprecated public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
|
|
+ // Paper end
|
|
this.heightmaps = new CompoundTag();
|
|
|
|
for(Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
|
@@ -43,7 +46,13 @@ public class ClientboundLevelChunkPacketData {
|
|
}
|
|
|
|
this.buffer = new byte[calculateChunkSize(chunk)];
|
|
- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk);
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ if (chunkPacketInfo != null) {
|
|
+ chunkPacketInfo.setBuffer(this.buffer);
|
|
+ }
|
|
+
|
|
+ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo);
|
|
+ // Paper end
|
|
this.blockEntitiesData = Lists.newArrayList();
|
|
int totalTileEntities = 0; // Paper
|
|
|
|
@@ -103,9 +112,12 @@ public class ClientboundLevelChunkPacketData {
|
|
return byteBuf;
|
|
}
|
|
|
|
- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) {
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ @Deprecated public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } // Notice for updates: Please make sure this function isn't used anywhere
|
|
+ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo) {
|
|
for(LevelChunkSection levelChunkSection : chunk.getSections()) {
|
|
- levelChunkSection.write(buf);
|
|
+ levelChunkSection.write(buf, chunkPacketInfo);
|
|
+ // Paper end
|
|
}
|
|
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
|
index 7825d6f0fdcfda6212cff8033ec55fb7db236154..2072aa8710f6e285f7c8f76c63b7bcf85cc11030 100644
|
|
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
|
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
|
@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
|
|
private final int z;
|
|
private final ClientboundLevelChunkPacketData chunkData;
|
|
private final ClientboundLightUpdatePacketData lightData;
|
|
+ // Paper start - Async-Anti-Xray - Ready flag for the connection
|
|
+ private volatile boolean ready;
|
|
|
|
- public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge) {
|
|
+ @Override
|
|
+ public boolean isReady() {
|
|
+ return this.ready;
|
|
+ }
|
|
+
|
|
+ public void setReady(boolean ready) {
|
|
+ this.ready = ready;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ @Deprecated public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge) { this(chunk, lightProvider, skyBits, blockBits, nonEdge, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge, boolean modifyBlocks) {
|
|
+ com.destroystokyo.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null;
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
this.x = chunkPos.x;
|
|
this.z = chunkPos.z;
|
|
- this.chunkData = new ClientboundLevelChunkPacketData(chunk);
|
|
+ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo);
|
|
+ // Paper end
|
|
this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits, nonEdge);
|
|
+ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
|
|
}
|
|
|
|
public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 8120d74d0016bc9c57ac6f6284b62c14464b9a2c..c5e3c46e7eb44e43f705a912891e6f6b42e52e6c 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -976,7 +976,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
completablefuture1.thenAcceptAsync((either) -> {
|
|
either.ifLeft((chunk) -> {
|
|
this.tickingGenerated.getAndIncrement();
|
|
- MutableObject<ClientboundLevelChunkWithLightPacket> mutableobject = new MutableObject();
|
|
+ MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
|
|
|
|
this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
|
this.playerLoadedChunk(entityplayer, mutableobject, chunk);
|
|
@@ -1143,7 +1143,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
while (objectiterator.hasNext()) {
|
|
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
|
|
ChunkPos chunkcoordintpair = playerchunk.getPos();
|
|
- MutableObject<ClientboundLevelChunkWithLightPacket> mutableobject = new MutableObject();
|
|
+ MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
|
|
|
|
this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
|
boolean flag = ChunkMap.isChunkInEuclideanRange(chunkcoordintpair, entityplayer, true, k);
|
|
@@ -1156,7 +1156,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
}
|
|
|
|
- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<ClientboundLevelChunkWithLightPacket> mutableobject, boolean oldWithinViewDistance, boolean newWithinViewDistance) {
|
|
+ protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass
|
|
if (player.level == this.level) {
|
|
if (newWithinViewDistance && !oldWithinViewDistance) {
|
|
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
|
|
@@ -1679,12 +1679,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
}
|
|
|
|
- private void playerLoadedChunk(ServerPlayer player, MutableObject<ClientboundLevelChunkWithLightPacket> cachedDataPacket, LevelChunk chunk) {
|
|
- if (cachedDataPacket.getValue() == null) {
|
|
- cachedDataPacket.setValue(new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true));
|
|
+ // Paper start - Anti-Xray - Bypass
|
|
+ private void playerLoadedChunk(ServerPlayer player, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> cachedDataPackets, LevelChunk chunk) {
|
|
+ if (cachedDataPackets.getValue() == null) {
|
|
+ cachedDataPackets.setValue(new java.util.HashMap<>());
|
|
}
|
|
|
|
- player.trackChunk(chunk.getPos(), (Packet) cachedDataPacket.getValue());
|
|
+ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk);
|
|
+ player.trackChunk(chunk.getPos(), cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> {
|
|
+ return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s);
|
|
+ }));
|
|
+ // Paper end
|
|
DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos());
|
|
List<Entity> list = Lists.newArrayList();
|
|
List<Entity> list1 = Lists.newArrayList();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index db123ef36fc4cb0206349ac696572714b806454c..d26e42449a1f75898d06aba384178c0ef30d9aa7 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -391,7 +391,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
// Add env and gen to constructor, WorldData -> WorldDataServer
|
|
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<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, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
// Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
|
|
- super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, biomeProvider, env);
|
|
+ super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getProfiler, false, flag, i, gen, biomeProvider, env, executor); // Paper - Async-Anti-Xray - Pass executor
|
|
this.pvpMode = minecraftserver.isPvpAllowed();
|
|
this.convertable = convertable_conversionsession;
|
|
this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelPath.toFile());
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
|
|
index 44e5ab0b545de41b937c7ce304ce643f78a43734..a85b89a0d525e623e154c8b1bea55470d2072dcb 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
|
|
@@ -48,7 +48,7 @@ import org.bukkit.event.player.PlayerInteractEvent;
|
|
public class ServerPlayerGameMode {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- protected ServerLevel level;
|
|
+ public ServerLevel level; // Paper - Anti-Xray - protected -> public
|
|
protected final ServerPlayer player;
|
|
private GameType gameModeForPlayer;
|
|
@Nullable
|
|
@@ -314,6 +314,8 @@ public class ServerPlayerGameMode {
|
|
}
|
|
|
|
}
|
|
+
|
|
+ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight); // Paper - Anti-Xray
|
|
}
|
|
|
|
public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 77e58257dc3fb517aeca8f8436a4279aa133b570..8cde961332f4671bdd361e8f88d0eedd595bf745 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -167,6 +167,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
|
|
|
|
public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper
|
|
+ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
|
|
|
|
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
|
|
public static BlockPos lastPhysicsProblem; // Spigot
|
|
@@ -186,7 +187,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
|
|
public abstract ResourceKey<LevelStem> getTypeKey();
|
|
|
|
- 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.generator.BiomeProvider biomeProvider, 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.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
|
|
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
|
|
this.generator = gen;
|
|
@@ -262,6 +263,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper
|
|
this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime);
|
|
this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime);
|
|
+ this.chunkPacketBlockController = this.paperConfig.antiXray ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
|
|
}
|
|
|
|
// Paper start
|
|
@@ -442,6 +444,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
// CraftBukkit end
|
|
|
|
BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
|
|
+ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray
|
|
|
|
if (iblockdata1 == null) {
|
|
// CraftBukkit start - remove blockstate if failed (or the same)
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
index 96dc50a24821016e53dfeac344c0e61f110a5a53..0a2f9e93666a5b2123f84e5cf409e4355d3cad86 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
|
|
@@ -110,7 +110,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
|
|
private static void replaceMissingSections(LevelHeightAccessor world, Registry<Biome> biome, LevelChunkSection[] sectionArray) {
|
|
for (int i = 0; i < sectionArray.length; ++i) {
|
|
if (sectionArray[i] == null) {
|
|
- sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome);
|
|
+ sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome, null, world instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) world : null); // Paper - Anti-Xray - Add parameters
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
index a009122a8ea9dc2b04a178248f20ef01886e832b..0e89cf3fd6edf28b52ec113af31267642fd2b195 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -93,7 +93,7 @@ public class LevelChunk extends ChunkAccess {
|
|
}
|
|
|
|
public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) {
|
|
- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData);
|
|
+ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isnt ready yet, use server singleton for registry
|
|
this.tickersInLevel = Maps.newHashMap();
|
|
this.clientLightReady = false;
|
|
this.level = (ServerLevel) world; // CraftBukkit - type
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
index 50b6ecfea7a342be0d21e37ae87777a4b4860026..512f53b24de14ea48eab85a0e725556d92def6e9 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
@@ -33,10 +33,13 @@ public class LevelChunkSection {
|
|
this.recalcBlockCounts();
|
|
}
|
|
|
|
- public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry) {
|
|
+ // Paper start - Anti-Xray - Add parameters
|
|
+ @Deprecated public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry) { this(chunkPos, biomeRegistry, null, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public LevelChunkSection(int chunkPos, Registry<Biome> biomeRegistry, net.minecraft.world.level.ChunkPos pos, net.minecraft.world.level.Level level) {
|
|
+ // Paper end
|
|
this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos);
|
|
- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
|
|
- this.biomes = new PalettedContainer<>(biomeRegistry, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
|
|
+ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, pos, this.bottomBlockY())); // Paper - Anti-Xray - Add preset block states
|
|
+ this.biomes = new PalettedContainer<>(biomeRegistry, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
|
|
}
|
|
|
|
public static int getBottomBlockY(int chunkPos) {
|
|
@@ -158,10 +161,13 @@ public class LevelChunkSection {
|
|
this.biomes.read(buf);
|
|
}
|
|
|
|
- public void write(FriendlyByteBuf buf) {
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ @Deprecated public void write(FriendlyByteBuf buf) { this.write(buf, null); } // Notice for updates: Please make sure this method isn't used anywhere
|
|
+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<BlockState> chunkPacketInfo) {
|
|
buf.writeShort(this.nonEmptyBlockCount);
|
|
- this.states.write(buf);
|
|
- this.biomes.write(buf);
|
|
+ this.states.write(buf, chunkPacketInfo, this.bottomBlockY());
|
|
+ this.biomes.write(buf, null, this.bottomBlockY());
|
|
+ // Paper end
|
|
}
|
|
|
|
public int getSerializedSize() {
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
index 911bf2b7bbe627f98d21681b0c6d5b8a5170c8a8..cdd357a8dd82cfd2a8abd45c1b7937b409af4b05 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
@@ -29,6 +29,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
return 0;
|
|
};
|
|
public final IdMap<T> registry;
|
|
+ private final T[] presetValues; // Paper - Anti-Xray - Add preset values
|
|
private volatile PalettedContainer.Data<T> data;
|
|
private final PalettedContainer.Strategy strategy;
|
|
private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
|
|
@@ -41,29 +42,65 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
this.threadingDetector.checkAndUnlock();
|
|
}
|
|
|
|
- public static <T> Codec<PalettedContainer<T>> codec(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy provider, T object) {
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ @Deprecated public static <T> Codec<PalettedContainer<T>> codec(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy provider, T object) { return PalettedContainer.codec(idList, entryCodec, provider, object, null); } // Notice for updates: Please make sure this function isn't used anywhere
|
|
+ public static <T> Codec<PalettedContainer<T>> codec(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy provider, T object, T[] presetValues) {
|
|
return RecordCodecBuilder.create((instance) -> {
|
|
return instance.group(entryCodec.mapResult(ExtraCodecs.orElsePartial(object)).listOf().fieldOf("palette").forGetter(PalettedContainer.DiscData::paletteEntries), Codec.LONG_STREAM.optionalFieldOf("data").forGetter(PalettedContainer.DiscData::storage)).apply(instance, PalettedContainer.DiscData::new);
|
|
}).comapFlatMap((serialized) -> {
|
|
- return read(idList, provider, serialized);
|
|
+ return read(idList, provider, serialized, object, presetValues);
|
|
+ // Paper end
|
|
}, (container) -> {
|
|
return container.write(idList, provider);
|
|
});
|
|
}
|
|
|
|
- public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) {
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ @Deprecated public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration<T> dataProvider, BitStorage storage, List<T> paletteEntries, T defaultValue, T[] presetValues) {
|
|
+ this.presetValues = presetValues;
|
|
this.registry = idList;
|
|
this.strategy = paletteProvider;
|
|
this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries));
|
|
+
|
|
+ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) {
|
|
+ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us
|
|
+ // We readd this here but in a smarter way than it was before
|
|
+ int maxSize = 1 << dataProvider.bits();
|
|
+
|
|
+ for (T presetValue : presetValues) {
|
|
+ if (this.data.palette.getSize() >= maxSize) {
|
|
+ java.util.Set<T> allValues = new java.util.HashSet<>(paletteEntries);
|
|
+ allValues.addAll(Arrays.asList(presetValues));
|
|
+ int newBits = Mth.ceillog2(allValues.size());
|
|
+
|
|
+ if (newBits > dataProvider.bits()) {
|
|
+ this.onResize(newBits, null);
|
|
+ }
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ this.data.palette.idFor(presetValue);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
|
|
- private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data) {
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ private PalettedContainer(IdMap<T> idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data<T> data, T[] presetValues) {
|
|
+ this.presetValues = presetValues;
|
|
+ // Paper end
|
|
this.registry = idList;
|
|
this.strategy = paletteProvider;
|
|
this.data = data;
|
|
}
|
|
|
|
- public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) {
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ @Deprecated public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public PalettedContainer(IdMap<T> idList, T object, PalettedContainer.Strategy paletteProvider, T[] presetValues) {
|
|
+ this.presetValues = presetValues;
|
|
+ // Paper end
|
|
this.strategy = paletteProvider;
|
|
this.registry = idList;
|
|
this.data = this.createOrReuseData((PalettedContainer.Data<T>)null, 0);
|
|
@@ -78,11 +115,33 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
@Override
|
|
public int onResize(int newBits, T object) {
|
|
PalettedContainer.Data<T> data = this.data;
|
|
+
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) {
|
|
+ int duplicates = 0;
|
|
+ List<T> presetValues = Arrays.asList(this.presetValues);
|
|
+ duplicates += presetValues.contains(object) ? 1 : 0;
|
|
+ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0;
|
|
+ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates);
|
|
+ }
|
|
+
|
|
PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits);
|
|
data2.copyFrom(data.palette, data.storage);
|
|
this.data = data2;
|
|
- return data2.palette.idFor(object);
|
|
+ this.addPresetValues();
|
|
+ return object == null ? -1 : data2.palette.idFor(object);
|
|
+ // Paper end
|
|
+ }
|
|
+
|
|
+ // Paper start - Anti-Xray - Add preset values
|
|
+ private void addPresetValues() {
|
|
+ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) {
|
|
+ for (T presetValue : this.presetValues) {
|
|
+ this.data.palette.idFor(presetValue);
|
|
+ }
|
|
+ }
|
|
}
|
|
+ // Paper end
|
|
|
|
public T getAndSet(int x, int y, int z, T value) {
|
|
this.acquire();
|
|
@@ -150,24 +209,34 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
data.palette.read(buf);
|
|
buf.readLongArray(data.storage.getRaw());
|
|
this.data = data;
|
|
+ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this not used by the server)
|
|
} finally {
|
|
this.release();
|
|
}
|
|
|
|
}
|
|
|
|
- public void write(FriendlyByteBuf buf) {
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ @Deprecated public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere
|
|
+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int bottomBlockY) {
|
|
this.acquire();
|
|
|
|
try {
|
|
- this.data.write(buf);
|
|
+ this.data.write(buf, chunkPacketInfo, bottomBlockY);
|
|
+
|
|
+ if (chunkPacketInfo != null) {
|
|
+ // Bottom block to 0 based chunk section index
|
|
+ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection();
|
|
+ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues);
|
|
+ }
|
|
+ // Paper end
|
|
} finally {
|
|
this.release();
|
|
}
|
|
|
|
}
|
|
|
|
- private static <T> DataResult<PalettedContainer<T>> read(IdMap<T> idList, PalettedContainer.Strategy provider, PalettedContainer.DiscData<T> serialized) {
|
|
+ private static <T> DataResult<PalettedContainer<T>> read(IdMap<T> idList, PalettedContainer.Strategy provider, PalettedContainer.DiscData<T> serialized, T defaultValue, T[] presetValues) { // Paper - Anti-Xray - Add preset values
|
|
List<T> list = serialized.paletteEntries();
|
|
int i = provider.size();
|
|
int j = provider.calculateBitsForSerialization(idList, list.size());
|
|
@@ -203,7 +272,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
}
|
|
}
|
|
|
|
- return DataResult.success(new PalettedContainer<>(idList, provider, configuration, bitStorage, list));
|
|
+ return DataResult.success(new PalettedContainer<>(idList, provider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values
|
|
}
|
|
|
|
private PalettedContainer.DiscData<T> write(IdMap<T> idList, PalettedContainer.Strategy provider) {
|
|
@@ -260,7 +329,7 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
}
|
|
|
|
public PalettedContainer<T> copy() {
|
|
- return new PalettedContainer<>(this.registry, this.strategy, new PalettedContainer.Data<>(this.data.configuration(), this.data.storage().copy(), this.data.palette().copy()));
|
|
+ return new PalettedContainer<>(this.registry, this.strategy, new PalettedContainer.Data<>(this.data.configuration(), this.data.storage().copy(), this.data.palette().copy()), this.presetValues); // Paper - Anti-Xray - Add preset values
|
|
}
|
|
|
|
public void count(PalettedContainer.CountConsumer<T> counter) {
|
|
@@ -304,9 +373,20 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
return 1 + this.palette.getSerializedSize() + FriendlyByteBuf.getVarIntSize(this.storage.getSize()) + this.storage.getRaw().length * 8;
|
|
}
|
|
|
|
- public void write(FriendlyByteBuf buf) {
|
|
+ // Paper start - Anti-Xray - Add chunk packet info
|
|
+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo<T> chunkPacketInfo, int bottomBlockY) {
|
|
buf.writeByte(this.storage.getBits());
|
|
this.palette.write(buf);
|
|
+
|
|
+ if (chunkPacketInfo != null) {
|
|
+ // Bottom block to 0 based chunk section index
|
|
+ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection();
|
|
+ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits());
|
|
+ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette);
|
|
+ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getRaw().length));
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
buf.writeLongArray(this.storage.getRaw());
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
index cf86755050632b158576849b786079787db11763..1267e93d1e315d55086a87670fd098db552c3afd 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
|
|
@@ -67,7 +67,7 @@ import org.apache.logging.log4j.Logger;
|
|
|
|
public class ChunkSerializer {
|
|
|
|
- public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
|
|
+ public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private static final String TAG_UPGRADE_DATA = "UpgradeData";
|
|
private static final String BLOCK_TICKS_TAG = "block_ticks";
|
|
@@ -146,16 +146,20 @@ public class ChunkSerializer {
|
|
if (k >= 0 && k < achunksection.length) {
|
|
Logger logger;
|
|
PalettedContainer datapaletteblock;
|
|
+ // Paper start - Anti-Xray - Add preset block states
|
|
+ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0 << 4);
|
|
|
|
if (nbttagcompound1.contains("block_states", 10)) {
|
|
- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> {
|
|
+ Codec<PalettedContainer<BlockState>> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates);
|
|
+ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> {
|
|
ChunkSerializer.logErrors(chunkPos, b0, s);
|
|
});
|
|
logger = ChunkSerializer.LOGGER;
|
|
Objects.requireNonNull(logger);
|
|
datapaletteblock = (PalettedContainer) dataresult.getOrThrow(false, logger::error);
|
|
} else {
|
|
- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
|
|
+ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates);
|
|
+ // Paper end
|
|
}
|
|
|
|
PalettedContainer datapaletteblock1;
|
|
@@ -168,7 +172,7 @@ public class ChunkSerializer {
|
|
Objects.requireNonNull(logger);
|
|
datapaletteblock1 = (PalettedContainer) dataresult.getOrThrow(false, logger::error);
|
|
} else {
|
|
- datapaletteblock1 = new PalettedContainer<>(iregistry, (Biome) iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
|
|
+ datapaletteblock1 = new PalettedContainer<>(iregistry, (Biome) iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
|
|
}
|
|
|
|
LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, datapaletteblock1);
|
|
@@ -421,7 +425,7 @@ public class ChunkSerializer {
|
|
}
|
|
|
|
private static Codec<PalettedContainer<Biome>> makeBiomeCodec(Registry<Biome> biomeRegistry) {
|
|
- return PalettedContainer.codec(biomeRegistry, biomeRegistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS));
|
|
+ return PalettedContainer.codec(biomeRegistry, biomeRegistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes
|
|
}
|
|
|
|
public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
index 7bc1219523eeb0880493e6fb42692f1fdb30c110..d6efa18ba9c032858071f6c87f1bdc0ce2ddb20f 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
|
|
@@ -52,7 +52,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 PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
|
|
+ private static final PalettedContainer<net.minecraft.world.level.block.state.BlockState> emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states
|
|
private static final byte[] emptyLight = new byte[2048];
|
|
|
|
public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) {
|
|
@@ -325,7 +325,7 @@ public class CraftChunk implements Chunk {
|
|
PalettedContainer<Biome>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null;
|
|
|
|
Registry<Biome> iregistry = this.worldServer.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
|
|
- Codec<PalettedContainer<Biome>> biomeCodec = PalettedContainer.codec(iregistry, iregistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS));
|
|
+ Codec<PalettedContainer<Biome>> biomeCodec = PalettedContainer.codec(iregistry, iregistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes
|
|
|
|
for (int i = 0; i < cs.length; i++) {
|
|
CompoundTag data = new CompoundTag();
|
|
@@ -390,7 +390,7 @@ public class CraftChunk implements Chunk {
|
|
|
|
if (biome != null) {
|
|
Registry<Biome> iregistry = world.getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
|
|
- biome[i] = new PalettedContainer<>(iregistry, iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
|
|
+ biome[i] = new PalettedContainer<>(iregistry, iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index 8d1cbefc82f64a120eeea8c6bdd6361ffa513099..6a16faf62f6d93c1ada5942e5886b37bd9f78c83 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -2204,7 +2204,7 @@ public final class CraftServer implements Server {
|
|
public ChunkGenerator.ChunkData createChunkData(World world) {
|
|
Validate.notNull(world, "World cannot be null");
|
|
ServerLevel handle = ((CraftWorld) world).getHandle();
|
|
- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY));
|
|
+ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), world); // Paper - Anti-Xray - Add parameters
|
|
}
|
|
|
|
@Override
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
|
|
index 960405935e395a31c0300773c41413801cf0d290..6f6bf950cd15b34031618782c82824cf0b191ff8 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java
|
|
@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData {
|
|
private final Registry<net.minecraft.world.level.biome.Biome> biomes;
|
|
private Set<BlockPos> tiles;
|
|
private final Set<BlockPos> lights = new HashSet<>();
|
|
+ // Paper start - Anti-Xray - Add parameters
|
|
+ private final World world;
|
|
|
|
- public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes) {
|
|
+ @Deprecated public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes) { this(minHeight, maxHeight, biomes, null); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
+ public OldCraftChunkData(int minHeight, int maxHeight, Registry<net.minecraft.world.level.biome.Biome> biomes, World world) {
|
|
+ this.world = world;
|
|
+ // Paper end
|
|
this.minHeight = minHeight;
|
|
this.maxHeight = maxHeight;
|
|
this.biomes = biomes;
|
|
@@ -176,7 +181,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData {
|
|
int offset = (y - this.minHeight) >> 4;
|
|
LevelChunkSection section = this.sections[offset];
|
|
if (create && section == null) {
|
|
- this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes);
|
|
+ this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes, null, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null); // Paper - Anti-Xray - Add parameters
|
|
}
|
|
return section;
|
|
}
|