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 extends PacketListener>>> 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 extends PacketListener>> tossPacket = (p, packet) -> null;
+ BiFunction, Packet extends PacketListener>> 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 extends PacketListener>) packet);
- }
- else {
+ } else if (packetProcessors.containsKey(packet.getClass())) {
+ return packetProcessors.get(packet.getClass()).apply(receiver,
+ (Packet extends PacketListener>) 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;
}
+
}