From c30aad179b6394bc412350c671712fc3f693b6bd Mon Sep 17 00:00:00 2001 From: Manuel Frohn Date: Wed, 13 May 2026 17:20:32 +0200 Subject: [PATCH] Continue refactor --- .../src/de/steamwar/techhider/ChunkHider.java | 157 +++++++++++++++++- .../src/de/steamwar/techhider/TechHider.java | 7 - .../steamwar/techhider/TechHiderUpdated.java | 123 ++++++++++---- 3 files changed, 247 insertions(+), 40 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index bdc8cdd3..69f0123a 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -29,11 +29,160 @@ import java.util.Collections; import java.util.Set; import java.util.function.BiFunction; -public interface ChunkHider { - ChunkHider impl = VersionDependent.getVersionImpl(Core.getInstance()); +/* + * 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 . + */ - Class mapChunkPacket(); - BiFunction chunkHiderGenerator(TechHider techHider); +package de.steamwar.techhider; + +import de.steamwar.Reflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.util.SimpleBitStorage; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.bukkit.entity.Player; + +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 { + 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 processLevelChunkWithLightPacket(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; + }; + } + + 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); + private static final Class builtInRegestries = Reflection.getClass("net.minecraft.core.registries.BuiltInRegistries"); + private static final Class registry = Reflection.getClass("net.minecraft.core.Registry"); + private static final Reflection.Field nameField = Reflection.getField(builtInRegestries, "BLOCK_ENTITY_TYPE", registry); + private static final Class resourceLocation = Reflection.getClass("net.minecraft.resources.ResourceLocation"); + private static final Reflection.Method getKey = Reflection.getTypedMethod(registry, "getKey", resourceLocation, Object.class); + private static final Reflection.Method getName = Reflection.getTypedMethod(resourceLocation, "getPath", String.class); + protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { + return !hiddenBlockEntities.contains(getName.invoke(getKey.invoke(nameField.get(null), entityType.get(tile)))); + } + + 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 { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 0428c78c..bd8e41d8 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -75,10 +75,6 @@ public class TechHider { this.obfuscationTarget = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, obfuscationTarget)); 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)); if(Core.getVersion() > 12 && Core.getVersion() < 19) { @@ -101,9 +97,6 @@ public class TechHider { techhiders.forEach(TinyProtocol.instance::removeFilter); } - public static final Class multiBlockChangePacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket"); - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); - public enum State { SKIP, CHECK, diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java index b5def8e9..26138c02 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java @@ -1,16 +1,25 @@ package de.steamwar.techhider; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.stream.Collectors; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.Reflection; +import de.steamwar.techhider.ChunkHider.SectionHider; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; @@ -25,6 +34,7 @@ import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; import net.minecraft.network.protocol.game.ClientboundCommandsPacket; import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket; import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket; import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; @@ -57,8 +67,7 @@ public class TechHiderUpdated { private final Map>, BiFunction, Packet>> packetProcessors; private final TinyProtocol interceptor; private final Set blockIdsToObfuscate; - private final BlockState blockToObfuscateTo; - + private final BlockState blockToObfuscateTo; public TechHiderUpdated(Plugin plugin, Set blockIdsToObfuscate, BlockState blockToObfuscateTo) { this.blockIdsToObfuscate = blockIdsToObfuscate; @@ -103,26 +112,28 @@ public class TechHiderUpdated { ClientboundCommandsPacket.class // Command tree for tab-complete ); - BiFunction, Packet> tossPacket = (p, packet) -> null; + BiFunction, Packet> tossPacket = (p, + packet) -> null; this.packetProcessors = Map.of( - ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet), - ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet), - ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet), - ClientboundSectionBlocksUpdatePacket.class, tossPacket, - ClientboundLevelChunkWithLightPacket.class, tossPacket - ); + ClientboundBlockEventPacket.class, + (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet), + ClientboundBlockUpdatePacket.class, + (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet), + ClientboundBlockEntityDataPacket.class, + (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet), + ClientboundSectionBlocksUpdatePacket.class, tossPacket, + ClientboundLevelChunkWithLightPacket.class, tossPacket); this.interceptor = new TinyProtocol(plugin) { @Override public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { if (bypassingPackets.stream().anyMatch(clazz -> clazz.isInstance(packet))) { return packet; - } - else if (packetProcessors.containsKey(packet.getClass())) { - return packetProcessors.get(packet.getClass()).apply(receiver, (Packet) packet); - } - else { + } else if (packetProcessors.containsKey(packet.getClass())) { + return packetProcessors.get(packet.getClass()).apply(receiver, + (Packet) packet); + } else { return null; } @@ -133,11 +144,9 @@ public class TechHiderUpdated { private ClientboundBlockEventPacket processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) { BlockPos blockPos = packet.getPos(); - - if(isPlayerPrivilegedToAccessBlockPos(player, blockPos)) { + if (isPlayerPrivilegedToAccessBlockPos(player, blockPos)) { return packet; - } - else { + } else { return null; } } @@ -145,29 +154,85 @@ public class TechHiderUpdated { private ClientboundBlockUpdatePacket processBlockUpdatePacket(Player p, ClientboundBlockUpdatePacket packet) { int id = BlockIds.impl.getCombinedId(packet.getBlockState()); - if(blockIdsToObfuscate.contains(id)) { + if (blockIdsToObfuscate.contains(id)) { // Return a modified copy of the packet with the obfuscated block state - ClientboundBlockUpdatePacket modifiedPacket = new ClientboundBlockUpdatePacket(packet.getPos(), blockToObfuscateTo); + ClientboundBlockUpdatePacket modifiedPacket = new ClientboundBlockUpdatePacket(packet.getPos(), + blockToObfuscateTo); return modifiedPacket; - } - else { + } else { return packet; } } - private ClientboundBlockEntityDataPacket processBlockEntityDataPacket(Player p, ClientboundBlockEntityDataPacket packet) { - if(isPlayerPrivilegedToAccessBlockPos(p, packet.getPos()) ) { + private ClientboundBlockEntityDataPacket processBlockEntityDataPacket(Player p, + ClientboundBlockEntityDataPacket packet) { + if (isPlayerPrivilegedToAccessBlockPos(p, packet.getPos())) { return packet; - } - else if(ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)){ + } else if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) { return packet; - } - else { + } else { return null; } } - public boolean isPlayerPrivilegedToAccessBlockPos(Player p, BlockPos block) { + private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, + ClientboundSectionBlocksUpdatePacket packet) { + SectionPos sectionPos = packet.sectionPos; + short[] oldPos = packet.positions; + BlockState[] oldStates = packet.states; + + boolean modified = false; + List filteredPos = new ArrayList<>(oldPos.length); + List filteredStates = new ArrayList<>(oldStates.length); + + for (int i = 0; i < oldPos.length; i++) { + short posShort = oldPos[i]; + BlockState state = oldStates[i]; + + int worldX = sectionPos.relativeToBlockX(posShort); + int worldY = sectionPos.relativeToBlockY(posShort); + int worldZ = sectionPos.relativeToBlockZ(posShort); + + BlockPos pos = new BlockPos(worldX, worldY, worldZ); + + if (isPlayerPrivilegedToAccessBlockPos(p, pos)) { + filteredPos.add(posShort); + filteredStates.add(state); + } else { + int id = Block.getId(state); + if (blockIdsToObfuscate.contains(id)) { + filteredPos.add(posShort); + filteredStates.add(blockToObfuscateTo); + modified = true; + } else { + filteredPos.add(posShort); + filteredStates.add(state); + } + } + } + + if (filteredStates.isEmpty()) + return null; + if (!modified) + return packet; + + short[] newPos = new short[filteredPos.size()]; + for (int i = 0; i < newPos.length; i++) + newPos[i] = filteredPos.get(i); + BlockState[] newStates = filteredStates.toArray(new BlockState[0]); + + return new ClientboundSectionBlocksUpdatePacket( + sectionPos, + it.unimi.dsi.fastutil.shorts.ShortSets + .unmodifiable(new it.unimi.dsi.fastutil.shorts.ShortArraySet(newPos)), + newStates); + } + + private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) { + } + + private boolean isPlayerPrivilegedToAccessBlockPos(Player p, BlockPos pos) { return false; } + }