From 7d74eb0c099c8316b4dd1ee20a3a43d9599fab84 Mon Sep 17 00:00:00 2001 From: D4rkr34lm Date: Fri, 22 May 2026 17:40:33 +0200 Subject: [PATCH] add old techhider to skip supporting bausystem for now --- .../features/techhider/TechHiderCommand.java | 1 + .../features/world/SpectatorListener.java | 1 + .../bausystem/features/xray/XrayCommand.java | 1 + .../steamwar/techhider/legacy/BlockIds.java | 68 ++++ .../steamwar/techhider/legacy/ChunkHider.java | 317 ++++++++++++++++++ .../techhider/legacy/ProtocolUtils.java | 203 +++++++++++ .../techhider/legacy/ProtocolWrapper.java | 95 ++++++ .../steamwar/techhider/legacy/TechHider.java | 181 ++++++++++ 8 files changed, 867 insertions(+) create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java create mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java index 4820b072..4744e41d 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java @@ -38,6 +38,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index 3b02bffc..92a5c59e 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -39,6 +39,7 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.*; import org.bukkit.util.Vector; +import de.steamwar.techhider.legacy.TechHider; import java.util.HashSet; import java.util.Set; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java index 1ddfe6f7..55bcbeeb 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java @@ -40,6 +40,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; +import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.function.BiFunction; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java new file mode 100644 index 00000000..0d10088f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java @@ -0,0 +1,68 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import net.minecraft.core.IdMapper; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.util.HashSet; +import java.util.Set; + +public class BlockIds { + public static final BlockIds impl = new BlockIds(); + + public int materialToId(Material material) { + return getCombinedId(getBlock(material).defaultBlockState()); + } + + private static final FluidState water = Fluids.WATER.getSource(false); + private static final Iterable registryBlockId = (Iterable) Reflection.getField(TechHider.block, IdMapper.class, 0).get(null); + + public Set materialToAllIds(Material material) { + Set ids = new HashSet<>(); + for (BlockState data : getBlock(material).getStateDefinition().getPossibleStates()) { + ids.add(getCombinedId(data)); + } + + if (material == Material.WATER) { + for (BlockState data : registryBlockId) { + if (data.getFluidState() == water) { + ids.add(getCombinedId(data)); + } + } + } + + return ids; + } + + private Block getBlock(Material material) { + return CraftMagicNumbers.getBlock(material); + } + + public int getCombinedId(BlockState blockData) { + return Block.getId(blockData); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java new file mode 100644 index 00000000..9394d1b0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -0,0 +1,317 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +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.entity.BlockEntityType; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +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; + } + + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + + 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 dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); + private static final Reflection.Field tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + + 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; + } + + packet = chunkPacketCloner.apply(packet); + Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + + Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); + tileEntities.set(dataWrapper, ((List) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + + 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(); + } + + if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) { + section.skipNewDataArray(4096); + return; + } + + SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); + + 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; + + TechHider.State test = section.test(x, y, z); + + switch (test) { + case SKIP: + break; + case CHECK: + if (!section.getObfuscate().contains(values.get(pos))) { + break; + } + case HIDE: + values.set(pos, section.getTarget()); + break; + case HIDE_AIR: + default: + values.set(pos, section.getAir()); + } + } + } + } + + section.writeDataArray(values.getRaw()); + } + + private void biomes(SectionHider section) { + section.copyBitsPerBlock(); + if (section.getBitsPerBlock() == 0) { + section.copyVarInt(); + } else if (section.getBitsPerBlock() < 6) { + section.skipPalette(); + section.skipNewDataArray(64); + } else { + // Direct (global) biome IDs – no palette present + section.skipNewDataArray(64); + } + } + + @Getter + class SectionHider { + private final Player player; + private final TechHider techHider; + private final ByteBuf in; + private final ByteBuf out; + + private final int chunkX; + private final int chunkY; + private final int chunkZ; + private final int offsetX; + private final int offsetY; + private final int offsetZ; + + private final boolean skipSection; + + private boolean paletted; + private int bitsPerBlock; + private int blockCount; + private int air; + private int target; + private Set obfuscate; + + public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) { + this.player = player; + this.techHider = techHider; + this.in = in; + this.out = out; + this.chunkX = chunkX; + this.chunkY = chunkY; + this.chunkZ = chunkZ; + this.offsetX = 16 * chunkX; + this.offsetY = 16 * chunkY; + this.offsetZ = 16 * chunkZ; + this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ); + + this.paletted = false; + this.bitsPerBlock = 0; + this.air = TechHider.AIR_ID; + this.target = techHider.getObfuscationTargetId(); + this.obfuscate = techHider.getObfuscateIds(); + } + + public boolean blockPrecise() { + return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ); + } + + public TechHider.State test(int x, int y, int z) { + return techHider.getLocationEvaluator().check(player, offsetX + x, offsetY + y, offsetZ + z); + } + + public void copyBlockCount() { + this.blockCount = in.readShort(); + out.writeShort(blockCount); + } + + public void copyBitsPerBlock() { + bitsPerBlock = in.readByte(); + out.writeByte(bitsPerBlock); + } + + public int copyVarInt() { + int value = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, value); + return value; + } + + public void skipPalette() { + int paletteLength = copyVarInt(); + for (int i = 0; i < paletteLength; i++) { + copyVarInt(); + } + } + + public void processPalette() { + if (skipSection) { + skipPalette(); + return; + } + + int paletteLength = copyVarInt(); + if (paletteLength == 0) return; + + paletted = true; + air = 0; + target = 0; + + for (int i = 0; i < paletteLength; i++) { + int entry = ProtocolUtils.readVarInt(in); + if (obfuscate.contains(entry)) { + entry = techHider.getObfuscationTargetId(); + } + + if (entry == TechHider.AIR_ID) { + air = i; + } else if (entry == techHider.getObfuscationTargetId()) { + target = i; + } + + ProtocolUtils.writeVarInt(out, entry); + } + obfuscate = Collections.emptySet(); + } + + public void skipDataArray() { + int dataArrayLength = copyVarInt(); + out.writeBytes(in, dataArrayLength * 8); + } + + public void skipNewDataArray(int entries) { + if (bitsPerBlock == 0) { + return; + } + + char valuesPerLong = (char) (64 / bitsPerBlock); + int i1 = (entries + valuesPerLong - 1) / valuesPerLong; + out.writeBytes(in, i1 * Long.BYTES); + } + + public long[] readDataArray() { + long[] array = new long[copyVarInt()]; + for (int i = 0; i < array.length; i++) { + array[i] = in.readLong(); + } + + return array; + } + + public long[] readNewDataArray(int entries) { + if (bitsPerBlock == 0) { + return new long[entries]; + } + + char valuesPerLong = (char) (64 / bitsPerBlock); + int i1 = (entries + valuesPerLong - 1) / valuesPerLong; + long[] array = new long[i1]; + for (int i = 0; i < i1; i++) { + array[i] = in.readLong(); + } + + return array; + } + + public void writeDataArray(long[] array) { + for (long l : array) { + out.writeLong(l); + } + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java new file mode 100644 index 00000000..5e9a7c78 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java @@ -0,0 +1,203 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import com.google.common.primitives.Bytes; +import de.steamwar.Reflection; +import io.netty.buffer.ByteBuf; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public class ProtocolUtils { + private ProtocolUtils() { + } + + @Deprecated + public static BiFunction, Object> arrayCloneGenerator(Class elementClass) { + return (array, worker) -> { + int length = Array.getLength(array); + Object result = Array.newInstance(elementClass, length); + + for (int i = 0; i < length; i++) { + Array.set(result, i, worker.apply(Array.get(array, i))); + } + + return result; + }; + } + + public static UnaryOperator shallowCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); + + return source -> { + Object clone = Reflection.newInstance(clazz); + filler.accept(source, clone); + return clone; + }; + } + + public static UnaryOperator shallowTypedCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); + + return source -> { + Object clone = Reflection.newInstance(clazz); + filler.accept(source, clone); + return (T) clone; + }; + } + + private static BiConsumer shallowFill(Class clazz) { + if (clazz == null) { + return (source, clone) -> { + }; + } + + BiConsumer superFiller = shallowFill(clazz.getSuperclass()); + + Field[] fds = clazz.getDeclaredFields(); + List fields = new ArrayList<>(); + for (Field field : fds) { + if (Modifier.isStatic(field.getModifiers())) continue; + + field.setAccessible(true); + fields.add(field); + } + + return (source, clone) -> { + superFiller.accept(source, clone); + try { + for (Field field : fields) { + field.set(clone, field.get(source)); + } + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not set field", e); + } + + }; + } + + public static int posToChunk(int c) { + int chunk = c / 16; + if (c < 0) chunk--; + return chunk; + } + + @Deprecated + public static int readVarInt(byte[] array, int startPos) { + int numRead = 0; + int result = 0; + byte read; + do { + read = array[startPos + numRead]; + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return result; + } + + public static int readVarInt(ByteBuf buf) { + int numRead = 0; + int result = 0; + byte read; + do { + read = buf.readByte(); + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + if (++numRead > 5) throw new SecurityException("VarInt too long"); + } while ((read & 0b10000000) != 0); + + return result; + } + + public static void writeVarInt(ByteBuf buf, int value) { + do { + int temp = value & 0b01111111; + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buf.writeByte(temp); + } while (value != 0); + } + + @Deprecated + public static int readVarIntLength(byte[] array, int startPos) { + int numRead = 0; + byte read; + do { + read = array[startPos + numRead]; + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return numRead; + } + + @Deprecated + public static byte[] writeVarInt(int value) { + List buffer = new ArrayList<>(5); + do { + byte temp = (byte) (value & 0b01111111); + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buffer.add(temp); + } while (value != 0); + return Bytes.toArray(buffer); + } + + @Deprecated + public static class ChunkPos { + final int x; + final int z; + + public ChunkPos(int x, int z) { + this.x = x; + this.z = z; + } + + public final int x() { + return x; + } + + public final int z() { + return z; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java new file mode 100644 index 00000000..9b6ac216 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java @@ -0,0 +1,95 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.function.BiFunction; + +public class ProtocolWrapper { + public static final ProtocolWrapper impl = new ProtocolWrapper(); + + private static final Reflection.Field multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0); + private static final Reflection.Field multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0); + private static final Reflection.Field multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0); + + public BiFunction multiBlockChangeGenerator(TechHider techHider) { + return (p, packet) -> { + TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator(); + Object chunkCoords = multiBlockChangeChunk.get(packet); + int chunkX = TechHider.blockPositionX.get(chunkCoords); + int chunkY = TechHider.blockPositionY.get(chunkCoords); + int chunkZ = TechHider.blockPositionZ.get(chunkCoords); + if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) { + return packet; + } + + packet = TechHider.multiBlockChangeCloner.apply(packet); + final short[] oldPos = multiBlockChangePos.get(packet); + final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet); + ArrayList poss = new ArrayList<>(oldPos.length); + ArrayList blocks = new ArrayList<>(oldPos.length); + for (int i = 0; i < oldPos.length; i++) { + short pos = oldPos[i]; + BlockState block = oldBlocks[i]; + switch (locationEvaluator.check(p, 16 * chunkX + (pos >> 8 & 0xF), 16 * chunkY + (pos & 0xF), 16 * chunkZ + (pos >> 4 & 0xF))) { + case SKIP: + poss.add(pos); + blocks.add(block); + break; + case CHECK: + poss.add(pos); + blocks.add(techHider.iBlockDataHidden(block) ? (BlockState) techHider.getObfuscationTarget() : block); + break; + default: + break; + } + } + + if (blocks.isEmpty()) return null; + + short[] newPos = new short[poss.size()]; + for (int i = 0; i < newPos.length; i++) { + newPos[i] = poss.get(i); + } + + multiBlockChangePos.set(packet, newPos); + multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0])); + return packet; + }; + } + + private static final Reflection.Field tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, BlockEntityType.class, 0); + private static final BlockEntityType signType = Reflection.getField(BlockEntityType.class, BlockEntityType.class, 0, SignBlockEntity.class).get(null); + + public boolean unfilteredTileEntityDataAction(Object packet) { + return tileEntityType.get(packet) != signType; + } + + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, TechHider techHider) { + return null; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java new file mode 100644 index 00000000..006b50d0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -0,0 +1,181 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.Reflection; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.network.protocol.game.*; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class TechHider { + + public static final Class blockPosition = BlockPos.class; + private static final Class baseBlockPosition = Vec3i.class; + public static final Reflection.Field blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0); + public static final Reflection.Field blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1); + public static final Reflection.Field blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2); + + public static final Class iBlockData = BlockState.class; + public static final Class block = Block.class; + + public boolean iBlockDataHidden(BlockState iBlockData) { + return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData)); + } + + public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState(); + public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR); + + private final Map, BiFunction> techhiders = new HashMap<>(); + @Getter + private final LocationEvaluator locationEvaluator; + @Getter + private final Object obfuscationTarget; + @Getter + private final int obfuscationTargetId; + @Getter + private final Set obfuscateIds; + @Getter + private final Set hiddenBlockEntities; + + public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + this.locationEvaluator = locationEvaluator; + this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); + this.hiddenBlockEntities = hiddenBlockEntities; + this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); + this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); + + techhiders.put(blockActionPacket, this::blockActionHider); + techhiders.put(blockChangePacket, this::blockChangeHider); + techhiders.put(tileEntityDataPacket, this::tileEntityDataHider); + techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this)); + techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this)); + techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); + techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); + + } + + public void enable() { + techhiders.forEach(TinyProtocol.instance::addFilter); + } + + public void disable() { + techhiders.forEach(TinyProtocol.instance::removeFilter); + } + + public static final Class multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + + private static final Class blockChangePacket = ClientboundBlockUpdatePacket.class; + private static final Function blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); + private static final Reflection.Field blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0); + private static final Reflection.Field blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0); + + private Object blockChangeHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) { + return packet; + } + case HIDE: + packet = blockChangeCloner.apply(packet); + blockChangeBlockData.set(packet, obfuscationTarget); + return packet; + case HIDE_AIR: + default: + packet = blockChangeCloner.apply(packet); + blockChangeBlockData.set(packet, AIR); + return packet; + } + } + + private static final Class blockActionPacket = ClientboundBlockEventPacket.class; + private static final Reflection.Field blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); + + private Object blockActionHider(Player p, Object packet) { + if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) { + return packet; + } + return null; + } + + public static final Class tileEntityDataPacket = ClientboundBlockEntityDataPacket.class; + private static final Reflection.Field tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); + + private Object tileEntityDataHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) { + return packet; + } + default: + return null; + } + } + + public enum State { + SKIP, + CHECK, + HIDE, + HIDE_AIR + } + + public interface LocationEvaluator { + default boolean suppressInteractions(Player player) { + return false; + } + + boolean skipChunk(Player player, int x, int z); + + default boolean skipChunkSection(Player player, int x, int y, int z) { + return skipChunk(player, x, z); + } + + default State check(Player player, int x, int y, int z) { + return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; + } + + default State checkBlockPos(Player player, Object pos) { + return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos)); + } + + default boolean blockPrecise(Player player, int x, int y, int z) { + return false; + } + } +}