From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: stonar96 Date: Mon, 20 Aug 2018 03:03:58 +0200 Subject: [PATCH] Anti-Xray diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index 48676152152faf7a7b9524ac37d8b4a8c32c4e2c..ca2ac17747d4f2acf1df056759c5c182be050125 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; @@ -502,4 +504,33 @@ public class PaperWorldConfig { private void maxAutoSaveChunksPerTick() { maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24); } + + public boolean antiXray; + public boolean asynchronous; + public EngineMode engineMode; + public int maxChunkSectionIndex; + public int updateRadius; + public List hiddenBlocks; + public List replacementBlocks; + private void antiXray() { + antiXray = getBoolean("anti-xray.enabled", false); + asynchronous = true; + 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); + 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); + } } 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..df7e4183d8842f5be8ae9d0698f8fa90742ff43c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java @@ -0,0 +1,40 @@ +package com.destroystokyo.paper.antixray; + +import net.minecraft.server.BlockPosition; +import net.minecraft.server.Chunk; +import net.minecraft.server.ChunkSection; +import net.minecraft.server.EnumDirection; +import net.minecraft.server.IBlockData; +import net.minecraft.server.IChunkAccess; +import net.minecraft.server.PacketPlayOutMapChunk; +import net.minecraft.server.PlayerInteractManager; +import net.minecraft.server.World; + +public class ChunkPacketBlockController { + + public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); + + protected ChunkPacketBlockController() { + + } + + public IBlockData[] getPredefinedBlockData(World world, IChunkAccess chunk, ChunkSection chunkSection, boolean initializeBlocks) { + return null; + } + + public ChunkPacketInfo getChunkPacketInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) { + return null; + } + + public void modifyBlocks(PacketPlayOutMapChunk packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { + packetPlayOutMapChunk.setReady(true); + } + + public void onBlockChange(World world, BlockPosition blockPosition, IBlockData newBlockData, IBlockData oldBlockData, int flag) { + + } + + public void onPlayerLeftClickBlock(PlayerInteractManager playerInteractManager, BlockPosition blockPosition, EnumDirection 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..9c017eb027ce52ea6a2414af2d0190b3bd52522c --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -0,0 +1,628 @@ +package com.destroystokyo.paper.antixray; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import net.minecraft.server.*; +import org.bukkit.Bukkit; +import org.bukkit.World.Environment; + +import com.destroystokyo.paper.PaperWorldConfig; + +public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { + + private static ExecutorService executorServiceInstance = null; + private final ExecutorService executorService; + private final boolean asynchronous; + private final EngineMode engineMode; + private final int maxChunkSectionIndex; + private final int updateRadius; + private final IBlockData[] predefinedBlockData; + private final IBlockData[] predefinedBlockDataStone; + private final IBlockData[] predefinedBlockDataNetherrack; + private final IBlockData[] predefinedBlockDataEndStone; + private final int[] predefinedBlockDataBitsGlobal; + private final int[] predefinedBlockDataBitsStoneGlobal; + private final int[] predefinedBlockDataBitsNetherrackGlobal; + private final int[] predefinedBlockDataBitsEndStoneGlobal; + private final boolean[] solidGlobal = new boolean[Block.REGISTRY_ID.size()]; + private final boolean[] obfuscateGlobal = new boolean[Block.REGISTRY_ID.size()]; + private final ChunkSection[] emptyNearbyChunkSections = {Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION}; + private final int maxBlockYUpdatePosition; + + public ChunkPacketBlockControllerAntiXray(PaperWorldConfig paperWorldConfig) { + asynchronous = paperWorldConfig.asynchronous; + engineMode = paperWorldConfig.engineMode; + maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex; + updateRadius = paperWorldConfig.updateRadius; + + if (asynchronous) { + executorService = getExecutorServiceInstance(); + } else { + executorService = null; + } + + List toObfuscate; + + if (engineMode == EngineMode.HIDE) { + toObfuscate = paperWorldConfig.hiddenBlocks; + predefinedBlockData = null; + predefinedBlockDataStone = new IBlockData[] {Blocks.STONE.getBlockData()}; + predefinedBlockDataNetherrack = new IBlockData[] {Blocks.NETHERRACK.getBlockData()}; + predefinedBlockDataEndStone = new IBlockData[] {Blocks.END_STONE.getBlockData()}; + predefinedBlockDataBitsGlobal = null; + predefinedBlockDataBitsStoneGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.STONE.getBlockData())}; + predefinedBlockDataBitsNetherrackGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.NETHERRACK.getBlockData())}; + predefinedBlockDataBitsEndStoneGlobal = new int[] {ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(Blocks.END_STONE.getBlockData())}; + } else { + toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); + Set predefinedBlockDataSet = new HashSet(); + + for (String id : paperWorldConfig.hiddenBlocks) { + Block block = IRegistry.BLOCK.getOptional(new MinecraftKey(id)).orElse(null); + + if (block != null && !block.isTileEntity()) { + toObfuscate.add(id); + predefinedBlockDataSet.add(block.getBlockData()); + } + } + + predefinedBlockData = predefinedBlockDataSet.size() == 0 ? new IBlockData[] {Blocks.DIAMOND_ORE.getBlockData()} : predefinedBlockDataSet.toArray(new IBlockData[0]); + predefinedBlockDataStone = null; + predefinedBlockDataNetherrack = null; + predefinedBlockDataEndStone = null; + predefinedBlockDataBitsGlobal = new int[predefinedBlockData.length]; + + for (int i = 0; i < predefinedBlockData.length; i++) { + predefinedBlockDataBitsGlobal[i] = ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(predefinedBlockData[i]); + } + + predefinedBlockDataBitsStoneGlobal = null; + predefinedBlockDataBitsNetherrackGlobal = null; + predefinedBlockDataBitsEndStoneGlobal = null; + } + + for (String id : toObfuscate) { + Block block = IRegistry.BLOCK.getOptional(new MinecraftKey(id)).orElse(null); + + if (block != null) { + obfuscateGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(block.getBlockData())] = true; + } + } + + ChunkEmpty emptyChunk = new ChunkEmpty(null, new ChunkCoordIntPair(0, 0)); + BlockPosition zeroPos = new BlockPosition(0, 0, 0); + + for (int i = 0; i < solidGlobal.length; i++) { + IBlockData blockData = ChunkSection.GLOBAL_PALETTE.getObject(i); + + if (blockData != null) { + solidGlobal[i] = blockData.getBlock().isOccluding(blockData, emptyChunk, zeroPos) + && blockData.getBlock() != Blocks.SPAWNER && blockData.getBlock() != Blocks.BARRIER && blockData.getBlock() != Blocks.SHULKER_BOX; + // shulker box checks TE. + } + } + + this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1; + } + + private static ExecutorService getExecutorServiceInstance() { + if (executorServiceInstance == null) { + executorServiceInstance = Executors.newSingleThreadExecutor(); + } + + return executorServiceInstance; + } + + @Override + public IBlockData[] getPredefinedBlockData(World world, IChunkAccess chunk, ChunkSection 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.getYPosition() >> 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 ChunkPacketInfoAntiXray getChunkPacketInfo(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk 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(PacketPlayOutMapChunk packetPlayOutMapChunk, ChunkPacketInfo chunkPacketInfo) { + if (!Bukkit.isPrimaryThread()) { + // plugins? + MinecraftServer.getServer().scheduleOnMain(() -> { + this.modifyBlocks(packetPlayOutMapChunk, chunkPacketInfo); + }); + return; + } + + Chunk chunk = chunkPacketInfo.getChunk(); + int x = chunk.getPos().x; + int z = chunk.getPos().z; + WorldServer world = (WorldServer)chunk.world; + ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks( + (Chunk) world.getChunkIfLoadedImmediately(x - 1, z), + (Chunk) world.getChunkIfLoadedImmediately(x + 1, z), + (Chunk) world.getChunkIfLoadedImmediately(x, z - 1), + (Chunk) world.getChunkIfLoadedImmediately(x, z + 1)); + + if (asynchronous) { + executorService.submit((ChunkPacketInfoAntiXray) chunkPacketInfo); + } else { + obfuscate((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 + private int[] predefinedBlockDataBits; + private final boolean[] solid = new boolean[Block.REGISTRY_ID.size()]; + private final boolean[] obfuscate = new boolean[Block.REGISTRY_ID.size()]; + // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate + private boolean[][] current = new boolean[16][16]; + private boolean[][] next = new boolean[16][16]; + private boolean[][] nextNext = new boolean[16][16]; + private final DataBitsReader dataBitsReader = new DataBitsReader(); + private final DataBitsWriter dataBitsWriter = new DataBitsWriter(); + private final ChunkSection[] nearbyChunkSections = new ChunkSection[4]; + + public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { + boolean[] solidTemp = null; + boolean[] obfuscateTemp = null; + dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData()); + dataBitsWriter.setDataBits(chunkPacketInfoAntiXray.getData()); + int counter = 0; + + for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) { + if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex) != null) { + int[] predefinedBlockDataBitsTemp; + + if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == ChunkSection.GLOBAL_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 { + predefinedBlockDataBitsTemp = predefinedBlockDataBits == null ? predefinedBlockDataBits = engineMode == EngineMode.HIDE ? new int[1] : new int[predefinedBlockData.length] : predefinedBlockDataBits; + + for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) { + predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex)[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 + ChunkSection belowChunkSection = null; + boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex - 1]) == Chunk.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[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(belowChunkSection.getType(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, counter); + } + + dataBitsWriter.setBitsPerObject(chunkPacketInfoAntiXray.getBitsPerObject(chunkSectionIndex)); + nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; + nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; + nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? Chunk.EMPTY_CHUNK_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; + nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? Chunk.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; + counter = obfuscateLayer(y, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, counter); + } + + // 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 + ChunkSection aboveChunkSection; + + if (chunkSectionIndex != 15 && (aboveChunkSection = chunkPacketInfoAntiXray.getChunk().getSections()[chunkSectionIndex + 1]) != Chunk.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[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(aboveChunkSection.getType(x, 0, z))]) { + current[z][x] = true; + } + } + } + + // There is nothing to read anymore + dataBitsReader.setBitsPerObject(0); + solid[0] = true; + counter = obfuscateLayer(15, dataBitsReader, dataBitsWriter, solid, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, counter); + } + } 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; + counter = obfuscateLayer(15, dataBitsReader, dataBitsWriter, solidTemp, obfuscateTemp, predefinedBlockDataBitsTemp, current, next, nextNext, nearbyChunkSections, counter); + } + + dataBitsWriter.finish(); + } + } + + chunkPacketInfoAntiXray.getPacketPlayOutMapChunk().setReady(true); + } + + private int obfuscateLayer(int y, DataBitsReader dataBitsReader, DataBitsWriter dataBitsWriter, boolean[] solid, boolean[] obfuscate, int[] predefinedBlockDataBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, ChunkSection[] nearbyChunkSections, int counter) { + // 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(0, y, 15))] || nearbyChunkSections[0] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, 0))] || current[0][0]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(x, y, 15))] || current[0][x]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[2].getType(15, y, 15))] || nearbyChunkSections[1] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, 0))] || current[0][15]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, z))] || current[z][0]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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 { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, z))] || current[z][15]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(0, y, 0))] || nearbyChunkSections[0] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[0].getType(15, y, 15))] || current[15][0]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(x, y, 0))] || current[15][x]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + 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] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[3].getType(15, y, 0))] || nearbyChunkSections[1] == Chunk.EMPTY_CHUNK_SECTION || !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(nearbyChunkSections[1].getType(0, y, 15))] || current[15][15]) { + dataBitsWriter.skip(); + } else { + if (counter >= predefinedBlockDataBits.length) { + counter = 0; + } + + dataBitsWriter.write(predefinedBlockDataBits[counter++]); + } + } + + if (!obfuscate[dataBits]) { + next[15][15] = true; + } + + return counter; + } + + private boolean[] readDataPalette(DataPalette dataPalette, boolean[] temp, boolean[] global) { + if (dataPalette == ChunkSection.GLOBAL_PALETTE) { + return global; + } + + IBlockData blockData; + + for (int i = 0; (blockData = dataPalette.getObject(i)) != null; i++) { + temp[i] = global[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(blockData)]; + } + + return temp; + } + + @Override + public void onBlockChange(World world, BlockPosition blockPosition, IBlockData newBlockData, IBlockData oldBlockData, int flag) { + if (oldBlockData != null && solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(oldBlockData)] && !solidGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(newBlockData)] && blockPosition.getY() <= maxBlockYUpdatePosition) { + updateNearbyBlocks(world, blockPosition); + } + } + + @Override + public void onPlayerLeftClickBlock(PlayerInteractManager playerInteractManager, BlockPosition blockPosition, EnumDirection enumDirection) { + if (blockPosition.getY() <= maxBlockYUpdatePosition) { + updateNearbyBlocks(playerInteractManager.world, blockPosition); + } + } + + private void updateNearbyBlocks(World world, BlockPosition blockPosition) { + if (updateRadius >= 2) { + BlockPosition temp = blockPosition.west(); + updateBlock(world, temp); + updateBlock(world, temp.west()); + updateBlock(world, temp.down()); + updateBlock(world, temp.up()); + updateBlock(world, temp.north()); + updateBlock(world, temp.south()); + updateBlock(world, temp = blockPosition.east()); + updateBlock(world, temp.east()); + updateBlock(world, temp.down()); + updateBlock(world, temp.up()); + updateBlock(world, temp.north()); + updateBlock(world, temp.south()); + updateBlock(world, temp = blockPosition.down()); + updateBlock(world, temp.down()); + updateBlock(world, temp.north()); + updateBlock(world, temp.south()); + updateBlock(world, temp = blockPosition.up()); + updateBlock(world, temp.up()); + 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.down()); + updateBlock(world, blockPosition.up()); + updateBlock(world, blockPosition.north()); + updateBlock(world, blockPosition.south()); + } else { + // Do nothing if updateRadius <= 0 (test mode) + } + } + + private void updateBlock(World world, BlockPosition blockPosition) { + IBlockData blockData = world.getTypeIfLoaded(blockPosition); + + if (blockData != null && obfuscateGlobal[ChunkSection.GLOBAL_PALETTE.getOrCreateIdFor(blockData)]) { + // world.notify(blockPosition, blockData, blockData, 3); + ((WorldServer)world).getChunkProvider().flagDirty(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..41618994b463267e41a9eb312db682e497c68e1b --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java @@ -0,0 +1,81 @@ +package com.destroystokyo.paper.antixray; + +import net.minecraft.server.Chunk; +import net.minecraft.server.DataPalette; +import net.minecraft.server.PacketPlayOutMapChunk; + +public class ChunkPacketInfo { + + private final PacketPlayOutMapChunk packetPlayOutMapChunk; + private final Chunk 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(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector) { + this.packetPlayOutMapChunk = packetPlayOutMapChunk; + this.chunk = chunk; + this.chunkSectionSelector = chunkSectionSelector; + } + + public PacketPlayOutMapChunk getPacketPlayOutMapChunk() { + return packetPlayOutMapChunk; + } + + public Chunk 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 DataPalette getDataPalette(int chunkSectionIndex) { + return (DataPalette) dataPalettes[chunkSectionIndex]; + } + + public void setDataPalette(int chunkSectionIndex, DataPalette 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..e61421d87a19bf2f6ce8836b48c445ffdb6772df --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java @@ -0,0 +1,30 @@ +package com.destroystokyo.paper.antixray; + +import net.minecraft.server.Chunk; +import net.minecraft.server.IBlockData; +import net.minecraft.server.PacketPlayOutMapChunk; + +public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { + + private Chunk[] nearbyChunks; + private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; + + public ChunkPacketInfoAntiXray(PacketPlayOutMapChunk packetPlayOutMapChunk, Chunk chunk, int chunkSectionSelector, + ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { + super(packetPlayOutMapChunk, chunk, chunkSectionSelector); + this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; + } + + public Chunk[] getNearbyChunks() { + return nearbyChunks; + } + + public void setNearbyChunks(Chunk... 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..c5a7b186e96901d55680283500f423025ededbe8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsReader.java @@ -0,0 +1,56 @@ +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() { + int value = (int) (current >>> bitInLongIndex) & mask; + bitInLongIndex += bitsPerObject; + + if (bitInLongIndex > 63) { + bitInLongIndex -= 64; + longInDataBitsIndex += 8; + init(); + + if (bitInLongIndex > 0) { + value |= current << bitsPerObject - bitInLongIndex & mask; + } + } + + 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..2eff19f6aaa31245f80910c6fbb541e32c672a31 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/antixray/DataBitsWriter.java @@ -0,0 +1,84 @@ +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) { + current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; + dirty = true; + bitInLongIndex += bitsPerObject; + + if (bitInLongIndex > 63) { + finish(); + bitInLongIndex -= 64; + longInDataBitsIndex += 8; + init(); + + if (bitInLongIndex > 0) { + current = current & ~(mask >>> bitsPerObject - bitInLongIndex) | (value & mask) >>> bitsPerObject - bitInLongIndex; + dirty = true; + } + } + } + + public void skip() { + bitInLongIndex += bitsPerObject; + + if (bitInLongIndex > 63) { + finish(); + bitInLongIndex -= 64; + longInDataBitsIndex += 8; + init(); + } + } +} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index af0d6aff4de78e81fd3feb16719efdccc05ccb90..2604fe9756e90cb70d3d527edddb917772e70973 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -416,7 +416,7 @@ public class Chunk implements IChunkAccess { return null; } - chunksection = new ChunkSection(j >> 4 << 4); + chunksection = new ChunkSection(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/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index 8e4b3e52cbc95e24b1d72aed9ec8c32b94a91561..d287ea55c550dbebbbc1d5f815296ae7ba6315e9 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -57,7 +57,7 @@ public class ChunkRegionLoader { byte b0 = nbttagcompound2.getByte("Y"); if (nbttagcompound2.hasKeyOfType("Palette", 9) && nbttagcompound2.hasKeyOfType("BlockStates", 12)) { - ChunkSection chunksection = new ChunkSection(b0 << 4); + ChunkSection chunksection = new ChunkSection(b0 << 4, null, worldserver, false); // Paper - Anti-Xray - Add parameters chunksection.getBlocks().a(nbttagcompound2.getList("Palette", 10), nbttagcompound2.getLongArray("BlockStates")); chunksection.recalcBlockCounts(); @@ -115,7 +115,7 @@ public class ChunkRegionLoader { loadEntities(nbttagcompound1, chunk); }); } else { - ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1); + ProtoChunk protochunk = new ProtoChunk(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, worldserver); // Paper - Anti-Xray - Add parameter protochunk.a(biomestorage); object = protochunk; diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java index e056fbcb216977401fd2778fcd3ee7ed5f020214..eeb7eee925d31d1ed8e6bbd55888cb5ebe54afa2 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -1,5 +1,6 @@ package net.minecraft.server; +import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray - Add chunk packet info import javax.annotation.Nullable; public class ChunkSection { @@ -11,16 +12,22 @@ public class ChunkSection { private short e; final DataPaletteBlock blockIds; - public ChunkSection(int i) { - this(i, (short) 0, (short) 0, (short) 0); + // Paper start - Anti-Xray - Add parameters + @Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) { + this(i, (short) 0, (short) 0, (short) 0, chunk, world, initializeBlocks); + // Paper end } - public ChunkSection(int i, short short0, short short1, short short2) { + // Paper start - Anti-Xray - Add parameters + @Deprecated public ChunkSection(int i, short short0, short short1, short short2) { this(i, short0, short1, short2, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ChunkSection(int i, short short0, short short1, short short2, IChunkAccess chunk, World world, boolean initializeBlocks) { + // Paper end this.yPos = i; this.nonEmptyBlockCount = short0; this.tickingBlockCount = short1; this.e = short2; - this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::d, GameProfileSerializer::a, Blocks.AIR.getBlockData()); + this.blockIds = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::d, GameProfileSerializer::a, Blocks.AIR.getBlockData(), world == null ? null : world.chunkPacketBlockController.getPredefinedBlockData(world, chunk, this, initializeBlocks), initializeBlocks); // Paper - Anti-Xray - Add predefined block data } public IBlockData getType(int i, int j, int k) { @@ -132,10 +139,14 @@ public class ChunkSection { return this.blockIds; } - public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER - public void b(PacketDataSerializer packetdataserializer) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public void writeChunkSection(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere + @Deprecated public void b(PacketDataSerializer packetdataserializer) { this.writeChunkSection(packetdataserializer, null); } // Notice for updates: Please make sure this method isn't used anywhere + public void writeChunkSection(PacketDataSerializer packetDataSerializer, ChunkPacketInfo chunkPacketInfo) { this.b(packetDataSerializer, chunkPacketInfo); } // OBFHELPER + public void b(PacketDataSerializer packetdataserializer, ChunkPacketInfo chunkPacketInfo) { + // Paper end packetdataserializer.writeShort(this.nonEmptyBlockCount); - this.blockIds.b(packetdataserializer); + this.blockIds.writeDataPaletteBlock(packetdataserializer, chunkPacketInfo, this.yPos >> 4); // Paper - Anti-Xray - Add chunk packet info } public int j() { diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java index 2c1d1b1a5568ab104fb1a0f283f289680d59e81e..2c7872bd0516c820a42682b5281dbbaa1b467beb 100644 --- a/src/main/java/net/minecraft/server/DataPaletteBlock.java +++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java @@ -3,6 +3,7 @@ package net.minecraft.server; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntMap.Entry; +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; @@ -19,6 +20,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { private final Function e; private final Function f; private final T g; + private final T[] predefinedObjects; // Paper - Anti-Xray - Add predefined objects protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER private DataPalette h; private DataPalette getDataPalette() { return this.h; } // Paper - OBFHELPER private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER @@ -46,14 +48,47 @@ public class DataPaletteBlock implements DataPaletteExpandable { this.j.unlock(); } - public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { + // Paper start - Anti-Xray - Add predefined objects + @Deprecated public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0) { this(datapalette, registryblockid, function, function1, t0, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere + public DataPaletteBlock(DataPalette datapalette, RegistryBlockID registryblockid, Function function, Function function1, T t0, T[] predefinedObjects, boolean initialize) { + // Paper end this.b = datapalette; this.d = registryblockid; this.e = function; this.f = function1; this.g = t0; - this.b(4); + // Paper start - Anti-Xray - Add predefined objects + this.predefinedObjects = predefinedObjects; + + if (initialize) { + if (predefinedObjects == null) { + // Default + this.initialize(4); + } else { + // MathHelper.d() is trailingBits(roundCeilPow2(n)), alternatively; (int)ceil(log2(n)); however it's trash, use numberOfLeadingZeros instead + // Count the bits of the maximum array index to initialize a data palette with enough space from the beginning + // The length of the array is used because air is also added to the data palette from the beginning + // Start with at least 4 + int maxIndex = predefinedObjects.length >> 4; + int bitCount = (32 - Integer.numberOfLeadingZeros(Math.max(16, maxIndex) - 1)); + + // Initialize with at least 15 free indixes + this.initialize((1 << bitCount) - predefinedObjects.length < 16 ? bitCount + 1 : bitCount); + this.addPredefinedObjects(); + } + } + // Paper end + } + + // Paper start - Anti-Xray - Add predefined objects + private void addPredefinedObjects() { + if (this.predefinedObjects != null && this.getDataPalette() != this.getDataPaletteGlobal()) { + for (int i = 0; i < this.predefinedObjects.length; i++) { + this.getDataPalette().getOrCreateIdFor(this.predefinedObjects[i]); + } + } } + // Paper end private static int b(int i, int j, int k) { return j << 8 | k << 4 | i; @@ -88,6 +123,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { int j; + this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects for (j = 0; j < databits.b(); ++j) { T t1 = datapalette.a(databits.a(j)); @@ -137,24 +173,38 @@ public class DataPaletteBlock implements DataPaletteExpandable { return t0 == null ? this.g : t0; } - public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // Paper - OBFHELPER - public void b(PacketDataSerializer packetdataserializer) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer) { this.b(packetDataSerializer); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere + @Deprecated public void b(PacketDataSerializer packetdataserializer) { this.writeDataPaletteBlock(packetdataserializer, null, 0); } // Notice for updates: Please make sure this method isn't used anywhere + public void writeDataPaletteBlock(PacketDataSerializer packetDataSerializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { this.b(packetDataSerializer, chunkPacketInfo, chunkSectionIndex); } // OBFHELPER + public void b(PacketDataSerializer packetdataserializer, ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { + // Paper end this.a(); packetdataserializer.writeByte(this.i); this.h.b(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() + PacketDataSerializer.countBytes(this.getDataBits().getDataBits().length)); + chunkPacketInfo.setPredefinedObjects(chunkSectionIndex, this.predefinedObjects); + } + // Paper end packetdataserializer.a(this.a.a()); this.b(); } public void a(NBTTagList nbttaglist, long[] along) { this.a(); - int i = Math.max(4, MathHelper.d(nbttaglist.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, MathHelper.d(nbttaglist.size() + (this.predefinedObjects == null ? 0 : this.predefinedObjects.length))); // Paper - Anti-Xray - Calculate the size with predefined objects - if (i != this.i) { + if (true || i != this.i) { // Paper - Anti-Xray - Not initialized yet this.b(i); } this.h.a(nbttaglist); + this.addPredefinedObjects(); // Paper - Anti-Xray - Add predefined objects int j = along.length * 64 / 4096; if (this.h == this.b) { diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java index 23223f3f45210cf23f44f9012f292db80df781a0..9b608d73869bd2907f705562c8378bc2f205767b 100644 --- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java +++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java @@ -1,5 +1,6 @@ package net.minecraft.server; +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; @@ -20,8 +21,13 @@ public class PacketPlayOutMapChunk implements Packet { private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER private List g; private boolean h; + private volatile boolean ready; // Paper - Async-Anti-Xray - Ready flag for the network manager - public PacketPlayOutMapChunk() {} + // Paper start - Async-Anti-Xray - Set the ready flag to true + public PacketPlayOutMapChunk() { + this.ready = true; + } + // Paper end // Paper start private final java.util.List extraPackets = new java.util.ArrayList<>(); @@ -33,6 +39,7 @@ public class PacketPlayOutMapChunk implements Packet { } // Paper end public PacketPlayOutMapChunk(Chunk chunk, int i) { + ChunkPacketInfo chunkPacketInfo = chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i); // Paper - Anti-Xray - Add chunk packet info ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); this.a = chunkcoordintpair.x; @@ -55,7 +62,12 @@ public class PacketPlayOutMapChunk implements Packet { } this.f = new byte[this.a(chunk, i)]; - this.c = this.a(new PacketDataSerializer(this.j()), chunk, i); + // Paper start - Anti-Xray - Add chunk packet info + if (chunkPacketInfo != null) { + chunkPacketInfo.setData(this.getData()); + } + this.c = this.writeChunk(new PacketDataSerializer(this.j()), chunk, i, chunkPacketInfo); + // Paper end this.g = Lists.newArrayList(); iterator = chunk.getTileEntities().entrySet().iterator(); int totalTileEntities = 0; // Paper @@ -82,8 +94,19 @@ public class PacketPlayOutMapChunk implements Packet { this.g.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 a(PacketDataSerializer packetdataserializer) throws IOException { @@ -149,8 +172,12 @@ public class PacketPlayOutMapChunk implements Packet { return bytebuf; } - public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // Paper - OBFHELPER - public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { + // Paper start - Anti-Xray - Add chunk packet info + @Deprecated public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector) { return this.a(packetDataSerializer, chunk, chunkSectionSelector); } // OBFHELPER // Notice for updates: Please make sure this method isn't used anywhere + @Deprecated public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i) { return this.writeChunk(packetdataserializer, chunk, i, null); } // Notice for updates: Please make sure this method isn't used anywhere + public int writeChunk(PacketDataSerializer packetDataSerializer, Chunk chunk, int chunkSectionSelector, ChunkPacketInfo chunkPacketInfo) { return this.a(packetDataSerializer, chunk, chunkSectionSelector, chunkPacketInfo); } // OBFHELPER + public int a(PacketDataSerializer packetdataserializer, Chunk chunk, int i, ChunkPacketInfo chunkPacketInfo) { + // Paper end int j = 0; ChunkSection[] achunksection = chunk.getSections(); int k = 0; @@ -160,7 +187,7 @@ public class PacketPlayOutMapChunk implements Packet { if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { j |= 1 << k; - chunksection.b(packetdataserializer); + chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info } } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index 9171785ad54a26e95dea8b30509c1e49a5d9b22d..94b0c54d9d4d77b724087be55ffe6ce464a0bbe7 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -604,7 +604,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { PlayerChunkMap.LOGGER.error("Couldn't load chunk {}", chunkcoordintpair, exception); } - return Either.left(new ProtoChunk(chunkcoordintpair, ChunkConverter.a)); + return Either.left(new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.world)); // Paper - Anti-Xray - Add parameter }, this.executor); } diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java index 17b7eddac4fadfd1cc3027fe6fbcd2bd5611fb84..ce66090b8dcd846db4507b99e3ef1a2d6104d19b 100644 --- a/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -266,6 +266,8 @@ public class PlayerInteractManager { } } + + this.world.chunkPacketBlockController.onPlayerLeftClickBlock(this, blockposition, enumdirection); // Paper - Anti-Xray } public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) { diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java index 39339fa27551b06a9bfd8ea67b1ec8c66726f488..deb7fb090196c4a33c0bb224f1c4e899aa3cd8c3 100644 --- a/src/main/java/net/minecraft/server/ProtoChunk.java +++ b/src/main/java/net/minecraft/server/ProtoChunk.java @@ -45,16 +45,24 @@ public class ProtoChunk implements IChunkAccess { private long s; private final Map t; private volatile boolean u; + private final World world; // Paper - Anti-Xray - Add world - public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter) { this(chunkcoordintpair, chunkconverter, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, World world) { + // Paper end this(chunkcoordintpair, chunkconverter, (ChunkSection[]) null, new ProtoChunkTickList<>((block) -> { return block == null || block.getBlockData().isAir(); }, chunkcoordintpair), new ProtoChunkTickList<>((fluidtype) -> { return fluidtype == null || fluidtype == FluidTypes.EMPTY; - }, chunkcoordintpair)); + }, chunkcoordintpair), world); // Paper - Anti-Xray - Add world } - public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1) { + // Paper start - Anti-Xray - Add world + @Deprecated public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1) { this(chunkcoordintpair, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, null); } // Notice for updates: Please make sure this constructor isn't used anywhere + public ProtoChunk(ChunkCoordIntPair chunkcoordintpair, ChunkConverter chunkconverter, @Nullable ChunkSection[] achunksection, ProtoChunkTickList protochunkticklist, ProtoChunkTickList protochunkticklist1, World world) { + this.world = world; + // Paper end this.f = Maps.newEnumMap(HeightMap.Type.class); this.g = ChunkStatus.EMPTY; this.h = Maps.newHashMap(); @@ -207,7 +215,7 @@ public class ProtoChunk implements IChunkAccess { public ChunkSection a(int i) { if (this.j[i] == Chunk.a) { - this.j[i] = new ChunkSection(i << 4); + this.j[i] = new ChunkSection(i << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters } return this.j[i]; diff --git a/src/main/java/net/minecraft/server/ProtoChunkExtension.java b/src/main/java/net/minecraft/server/ProtoChunkExtension.java index 01bf28dc34dd69dbcee5f470cc71ec2fbb2fcc12..caa18b046cd4231b1caa9c70e766ab60e11b3164 100644 --- a/src/main/java/net/minecraft/server/ProtoChunkExtension.java +++ b/src/main/java/net/minecraft/server/ProtoChunkExtension.java @@ -11,7 +11,7 @@ public class ProtoChunkExtension extends ProtoChunk { private final Chunk a; public ProtoChunkExtension(Chunk chunk) { - super(chunk.getPos(), ChunkConverter.a); + super(chunk.getPos(), ChunkConverter.a, chunk.world); // Paper - Anti-Xray - Add parameter this.a = chunk; } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 8cf3c10274b8320bf1938b95dd9f447deeb2c4d0..0bde1717431d770a9a73e83b56388d6f4923e404 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -2,6 +2,8 @@ package net.minecraft.server; 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; @@ -78,6 +80,7 @@ public abstract class World implements GeneratorAccess, 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 BlockPosition lastPhysicsProblem; // Spigot @@ -119,6 +122,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { protected World(WorldData worlddata, DimensionManager dimensionmanager, BiFunction bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper + this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray this.generator = gen; this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit @@ -342,6 +346,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { // CraftBukkit end IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0); // CraftBukkit custom NO_PLACE flag + this.chunkPacketBlockController.onBlockChange(this, blockposition, iblockdata, iblockdata1, i); // Paper - Anti-Xray if (iblockdata1 == null) { // CraftBukkit start - remove blockstate if failed (or the same) diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index a53bb7295c5d510f2976ff3787d68857548e11cc..47f1b970b9ba39f9050ac34a5ac15593c25f8a70 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -38,7 +38,7 @@ public class CraftChunk implements Chunk { private final WorldServer worldServer; private final int x; private final int z; - private static final DataPaletteBlock emptyBlockIDs = new ChunkSection(0).getBlocks(); + private static final DataPaletteBlock emptyBlockIDs = new ChunkSection(0, null, null, true).getBlocks(); // Paper - Anti-Xray - Add parameters private static final byte[] emptyLight = new byte[2048]; public CraftChunk(net.minecraft.server.Chunk chunk) { @@ -260,7 +260,7 @@ public class CraftChunk implements Chunk { NBTTagCompound data = new NBTTagCompound(); cs[i].getBlocks().a(data, "Palette", "BlockStates"); - DataPaletteBlock blockids = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, net.minecraft.server.Block.REGISTRY_ID, GameProfileSerializer::d, GameProfileSerializer::a, Blocks.AIR.getBlockData()); // TODO: snapshot whole ChunkSection + DataPaletteBlock blockids = new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, net.minecraft.server.Block.REGISTRY_ID, GameProfileSerializer::d, GameProfileSerializer::a, Blocks.AIR.getBlockData(), 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.a(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 8191e7c34899e204b8afbb2fd11d235e8ef8db99..bb18740ebdf4a14ced9944efa82103b350b32ba5 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 ChunkSection[] sections; private Set tiles; + private World world; // Paper - Anti-Xray - Add world public CraftChunkData(World world) { this(world.getMaxHeight()); + this.world = world; // Paper - Anti-Xray - Add world } /* pp for tests */ CraftChunkData(int maxHeight) { @@ -157,7 +159,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { private ChunkSection getChunkSection(int y, boolean create) { ChunkSection section = sections[y >> 4]; if (create && section == null) { - sections[y >> 4] = section = new ChunkSection(y >> 4 << 4); + sections[y >> 4] = section = new ChunkSection(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; }