diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index 7c10ec0e..a5e3227d 100644 --- a/SpigotCore/SpigotCore_Main/build.gradle.kts +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -58,7 +58,6 @@ dependencies { compileOnly(libs.netty) compileOnly(libs.brigadier) compileOnly(libs.fastutil) - compileOnly(libs.nms21) implementation(libs.anvilgui) } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index 238d7fdf..e45e5159 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -22,134 +22,240 @@ package de.steamwar.techhider; import de.steamwar.Reflection; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import lombok.Getter; import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.SimpleBitStorage; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Player; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.BiFunction; import java.util.function.UnaryOperator; import java.util.stream.Collectors; -public class ChunkHider { - public static final ChunkHider impl = new ChunkHider(); - public Class mapChunkPacket() { - return ClientboundLevelChunkWithLightPacket.class; - } +public abstract class ChunkHider { + private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + private static final Reflection.Field levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); - private static final Reflection.Field chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); - private static final Reflection.Field chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1); - private static final Reflection.Field chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); + private static final Reflection.Field chunkBlockDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); + private static final Reflection.Field chunkBlockEntitiesDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); - private static final Reflection.Field dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); - private static final Reflection.Field tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + private final int SECTION_SPAN_SIZE = 16; + private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; + private final int BLOCKS_PER_SECTION = 4096; - public BiFunction chunkHiderGenerator(TechHider techHider) { - return (p, packet) -> { - int chunkX = chunkXField.get(packet); - int chunkZ = chunkZField.get(packet); - if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) { - return packet; - } + private final byte BITS_PER_LONG = 64; - packet = chunkPacketCloner.apply(packet); - Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + private long[] readSectionDataFromBuffer(ByteBuf dataSource, byte bitsPerEntry, int entryCount) { + int entriesPerLong = BITS_PER_LONG / bitsPerEntry; + int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong; - Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); - tileEntities.set(dataWrapper, ((List) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + long[] dataArray = new long[dataLengthAsLongCount]; - ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper)); - ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); - for (int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { - SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset / 16, chunkZ); - section.copyBlockCount(); - - blocks(section); - biomes(section); - } - - if (in.readableBytes() != 0) { - throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left"); - } - - byte[] data = new byte[out.readableBytes()]; - out.readBytes(data); - dataField.set(dataWrapper, data); - - chunkData.set(packet, dataWrapper); - return packet; - }; - } - - private static final Registry> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); - private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); - public static final Class tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); - protected static final Reflection.Field entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0); - - protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { - BlockEntityType type = entityType.get(tile); - String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath(); - return !hiddenBlockEntities.contains(path); - } - - private void blocks(SectionHider section) { - section.copyBitsPerBlock(); - - boolean singleValued = section.getBitsPerBlock() == 0; - if (singleValued) { - int value = ProtocolUtils.readVarInt(section.getIn()); - ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value); - return; - } else if (section.getBitsPerBlock() < 9) { - // Indirect (paletted) storage – only present when bitsPerBlock < 9 in 1.21+ - section.processPalette(); + for(int i = 0; i < dataLengthAsLongCount; i++){ + dataArray[i] = dataSource.readLong(); } - if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) { - section.skipNewDataArray(4096); - return; - } + return dataArray; + } - SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); + public ClientboundLevelChunkWithLightPacket chunkHiderGenerator(Player player, ClientboundLevelChunkWithLightPacket packet) { + int chunkX = packet.getX(); + int chunkZ = packet.getZ(); + ClientboundLevelChunkPacketData chunkData = packet.getChunkData(); - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - for (int x = 0; x < 16; x++) { - int pos = (((y * 16) + z) * 16) + x; + ByteBuf in = Unpooled.wrappedBuffer(chunkData.getReadBuffer()); + ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); - TechHider.State test = section.test(x, y, z); + int worldMinHeight = player.getWorld().getMinHeight(); + int worldMaxHeight = player.getWorld().getMaxHeight(); - switch (test) { - case SKIP: - break; - case CHECK: - if (!section.getObfuscate().contains(values.get(pos))) { - break; + for (int yOffset = worldMinHeight; yOffset < worldMaxHeight; yOffset += SECTION_SPAN_SIZE) { + // TODO make configurable + int blockIdUsedForHiding = 121; + + short blockCount = in.readShort(); + + byte bitsPerBlock = in.readByte(); + + if(bitsPerBlock == 0) { + int sectionBlockId = ProtocolUtils.readVarInt(in); + } + else if (bitsPerBlock <= BIT_PER_BLOCK_INDIRECTION_LIMIT) { + int palletLength = ProtocolUtils.readVarInt(in); + int[] pallet = ProtocolUtils.readVarIntArray(in, palletLength); + + long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION); + SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawData); + + int[] resolvedData = new int[BLOCKS_PER_SECTION]; + for(int i = 0; i < BLOCKS_PER_SECTION; i++){ + int palletReference = data.get(i); + resolvedData[i] = pallet[palletReference]; + } + + int[] obfuscatedData = new int[BLOCKS_PER_SECTION]; + for (int sectionY = 0; sectionY < 16; sectionY++) { + for (int sectionZ = 0; sectionZ < 16; sectionZ++) { + for (int sectionX = 0; sectionX < 16; sectionX++) { + int blockDataIndex = (((sectionY * 16) + sectionZ) * 16) + sectionX; + + int worldX = sectionX * chunkX; + int worldY = sectionY * yOffset; + int worldZ = sectionZ * chunkZ; + + int blockId = resolvedData[blockDataIndex]; + Block block = BuiltInRegistries.BLOCK.get(blockId).get().value(); + + if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) { + obfuscatedData[blockDataIndex] = blockId; } - case HIDE: - values.set(pos, section.getTarget()); - break; - case HIDE_AIR: - default: - values.set(pos, section.getAir()); + else { + obfuscatedData[blockDataIndex] = blockIdUsedForHiding; + } + } } } + + Int2IntMap blockIdToPalletIndex = new Int2IntOpenHashMap(); + int[] newPallet = new int[palletLength]; + int runningIndex = 0; + + for(int blockId : obfuscatedData) { + if(!blockIdToPalletIndex.containsKey(blockId)) { + newPallet[runningIndex] = blockId; + blockIdToPalletIndex.put(blockId, runningIndex); + runningIndex++; + } + } + + SimpleBitStorage reEncodedData = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, new long[rawData.length]); + for(int i = 0; i < obfuscatedData.length; i++) { + int blockId = obfuscatedData[i]; + int palletReference = blockIdToPalletIndex.get(blockId); + + reEncodedData.set(i, palletReference); + } + + out.writeByte(blockCount); + out.writeByte(bitsPerBlock); + ProtocolUtils.writeVarInt(out, palletLength); + ProtocolUtils.writeVarIntArray(out, pallet); + for(long rawDataSegment : reEncodedData.getRaw()) { + out.writeLong(rawDataSegment); + } } + else { + long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION); + SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawData); + + int[] resolvedData = new int[BLOCKS_PER_SECTION]; + for(int i = 0; i < BLOCKS_PER_SECTION; i++){ + resolvedData[i] = data.get(i); + } + + int[] obfuscatedData = new int[BLOCKS_PER_SECTION]; + for (int sectionY = 0; sectionY < 16; sectionY++) { + for (int sectionZ = 0; sectionZ < 16; sectionZ++) { + for (int sectionX = 0; sectionX < 16; sectionX++) { + int blockDataIndex = (((sectionY * 16) + sectionZ) * 16) + sectionX; + + int worldX = sectionX * chunkX; + int worldY = sectionY * yOffset; + int worldZ = sectionZ * chunkZ; + + int blockId = resolvedData[blockDataIndex]; + Block block = BuiltInRegistries.BLOCK.get(blockId).get().value(); + + if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) { + obfuscatedData[blockDataIndex] = blockId; + } + else { + obfuscatedData[blockDataIndex] = blockIdUsedForHiding; + } + } + } + } + + SimpleBitStorage reEncodedData = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, new long[rawData.length]); + for(int i = 0; i < obfuscatedData.length; i++) { + int blockId = obfuscatedData[i]; + + reEncodedData.set(i, blockId); + } + + out.writeByte(blockCount); + out.writeByte(bitsPerBlock); + for(long rawDataSegment : reEncodedData.getRaw()) { + out.writeLong(rawDataSegment); + } + } + } - section.writeDataArray(values.getRaw()); + if (in.readableBytes() != 0) { + return null; + } + + byte[] data = new byte[out.readableBytes()]; + out.readBytes(data); + + List blockEntities = chunkBlockEntitiesDataField.get(chunkData); + List filteredBlockEntities = filterBlockEntities(player, blockEntities); + + return buildNewChunkPacket(packet, data, filteredBlockEntities); + + } + + public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ); + public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block); + public abstract boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); + + private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { + ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket); + ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData()); + + chunkBlockDataField.set(clonedPacketChunkData, newBlockDataBuffer); + chunkBlockEntitiesDataField.set(clonedPacketChunkData, newBlockEntities); + + levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData); + + return clonedPacket; + } + + + private static final Class blockEntitiyInfoClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); + + private static final Reflection.Field blockEntityInfoTypeField = Reflection.getField(blockEntitiyInfoClass, BlockEntityType.class, 0); + private static final Reflection.Field packedXZField = Reflection.getField(blockEntitiyInfoClass, Integer.class, 0); + private static final Reflection.Field yField = Reflection.getField(blockEntitiyInfoClass, Integer.class, 1); + private List filterBlockEntities(Player player, List blockEntities) { + return blockEntities.stream() + .filter((blockEntityInfo) -> { + BlockEntityType type = blockEntityInfoTypeField.get(blockEntityInfo); + + int packedXZ = packedXZField.get(blockEntityInfo); + + int y = yField.get(blockEntityInfo); + int x = SectionPos.sectionRelativeX((short) packedXZ); + int z = SectionPos.sectionRelativeZ((short) packedXZ); + + return isPlayerPrivilegedToAccessPosition(player, x, y, z) && isPlayerPrivilegedToAccessBlockEntity(player, x, y, z, type); + }).toList(); } private void biomes(SectionHider section) { @@ -187,7 +293,7 @@ public class ChunkHider { private Set blockIdsToObfuscate; private final int blockIdToObfuscateTo; - public SectionHider(Player player, BlockState blockToObfuscateTo, Set blockIdsToObfuscate, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) { + public SectionHider(Player player, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) { this.player = player; this.in = in; this.out = out; @@ -238,11 +344,6 @@ public class ChunkHider { } public void processPalette() { - if (skipSection) { - skipPalette(); - return; - } - int paletteLength = copyVarInt(); if (paletteLength == 0) return; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index 736027cc..100dac77 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -140,6 +140,16 @@ public class ProtocolUtils { return result; } + public static int[] readVarIntArray(ByteBuf buffer, int length) { + int[] array = new int[length]; + + for(int i = 0; i < length; i++) { + array[i] = readVarInt(buffer); + } + + return array; + } + public static void writeVarInt(ByteBuf buf, int value) { do { int temp = value & 0b01111111; @@ -152,6 +162,12 @@ public class ProtocolUtils { } while (value != 0); } + public static void writeVarIntArray(ByteBuf buf, int[] values){ + for(int varInt : values) { + writeVarInt(buf, varInt); + } + } + @Deprecated public static int readVarIntLength(byte[] array, int startPos) { int numRead = 0;