Continue refactor

This commit is contained in:
Manuel Frohn
2026-05-13 17:20:32 +02:00
parent d3aad6a198
commit c30aad179b
3 changed files with 247 additions and 40 deletions
@@ -29,11 +29,160 @@ import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction; 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 <https://www.gnu.org/licenses/>.
*/
Class<?> mapChunkPacket(); package de.steamwar.techhider;
BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider 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<Object> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private static final Reflection.Field<Integer> chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0);
private static final Reflection.Field<Integer> chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1);
private static final Reflection.Field<ClientboundLevelChunkPacketData> chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
public BiFunction<Player, Object, Object> 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<String> 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<BlockEntityType> 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<String> 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 @Getter
class SectionHider { class SectionHider {
@@ -75,10 +75,6 @@ public class TechHider {
this.obfuscationTarget = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, obfuscationTarget)); this.obfuscationTarget = getBlockDataByBlock.invoke(getBlockByMaterial.invoke(null, obfuscationTarget));
this.obfuscationTargetId = BlockIds.impl.materialToId(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)); techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this));
if(Core.getVersion() > 12 && Core.getVersion() < 19) { if(Core.getVersion() > 12 && Core.getVersion() < 19) {
@@ -101,9 +97,6 @@ public class TechHider {
techhiders.forEach(TinyProtocol.instance::removeFilter); techhiders.forEach(TinyProtocol.instance::removeFilter);
} }
public static final Class<?> multiBlockChangePacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket");
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket);
public enum State { public enum State {
SKIP, SKIP,
CHECK, CHECK,
@@ -1,16 +1,25 @@
package de.steamwar.techhider; package de.steamwar.techhider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.tinyprotocol.TinyProtocol; 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 io.netty.channel.Channel;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.PacketListener; import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; 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.ClientboundCommandsPacket;
import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket; import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket; 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.ClientboundLevelChunkWithLightPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket; import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
@@ -57,8 +67,7 @@ public class TechHiderUpdated {
private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors; private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;
private final TinyProtocol interceptor; private final TinyProtocol interceptor;
private final Set<Integer> blockIdsToObfuscate; private final Set<Integer> blockIdsToObfuscate;
private final BlockState blockToObfuscateTo; private final BlockState blockToObfuscateTo;
public TechHiderUpdated(Plugin plugin, Set<Integer> blockIdsToObfuscate, BlockState blockToObfuscateTo) { public TechHiderUpdated(Plugin plugin, Set<Integer> blockIdsToObfuscate, BlockState blockToObfuscateTo) {
this.blockIdsToObfuscate = blockIdsToObfuscate; this.blockIdsToObfuscate = blockIdsToObfuscate;
@@ -103,26 +112,28 @@ public class TechHiderUpdated {
ClientboundCommandsPacket.class // Command tree for tab-complete ClientboundCommandsPacket.class // Command tree for tab-complete
); );
BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p, packet) -> null; BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p,
packet) -> null;
this.packetProcessors = Map.of( this.packetProcessors = Map.of(
ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet), ClientboundBlockEventPacket.class,
ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet), (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet),
ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet), ClientboundBlockUpdatePacket.class,
ClientboundSectionBlocksUpdatePacket.class, tossPacket, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet),
ClientboundLevelChunkWithLightPacket.class, tossPacket ClientboundBlockEntityDataPacket.class,
); (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet),
ClientboundSectionBlocksUpdatePacket.class, tossPacket,
ClientboundLevelChunkWithLightPacket.class, tossPacket);
this.interceptor = new TinyProtocol(plugin) { this.interceptor = new TinyProtocol(plugin) {
@Override @Override
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
if (bypassingPackets.stream().anyMatch(clazz -> clazz.isInstance(packet))) { if (bypassingPackets.stream().anyMatch(clazz -> clazz.isInstance(packet))) {
return packet; return packet;
} } else if (packetProcessors.containsKey(packet.getClass())) {
else if (packetProcessors.containsKey(packet.getClass())) { return packetProcessors.get(packet.getClass()).apply(receiver,
return packetProcessors.get(packet.getClass()).apply(receiver, (Packet<? extends PacketListener>) packet); (Packet<? extends PacketListener>) packet);
} } else {
else {
return null; return null;
} }
@@ -133,11 +144,9 @@ public class TechHiderUpdated {
private ClientboundBlockEventPacket processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) { private ClientboundBlockEventPacket processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) {
BlockPos blockPos = packet.getPos(); BlockPos blockPos = packet.getPos();
if (isPlayerPrivilegedToAccessBlockPos(player, blockPos)) {
if(isPlayerPrivilegedToAccessBlockPos(player, blockPos)) {
return packet; return packet;
} } else {
else {
return null; return null;
} }
} }
@@ -145,29 +154,85 @@ public class TechHiderUpdated {
private ClientboundBlockUpdatePacket processBlockUpdatePacket(Player p, ClientboundBlockUpdatePacket packet) { private ClientboundBlockUpdatePacket processBlockUpdatePacket(Player p, ClientboundBlockUpdatePacket packet) {
int id = BlockIds.impl.getCombinedId(packet.getBlockState()); 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 // 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; return modifiedPacket;
} } else {
else {
return packet; return packet;
} }
} }
private ClientboundBlockEntityDataPacket processBlockEntityDataPacket(Player p, ClientboundBlockEntityDataPacket packet) { private ClientboundBlockEntityDataPacket processBlockEntityDataPacket(Player p,
if(isPlayerPrivilegedToAccessBlockPos(p, packet.getPos()) ) { ClientboundBlockEntityDataPacket packet) {
if (isPlayerPrivilegedToAccessBlockPos(p, packet.getPos())) {
return packet; return packet;
} } else if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) {
else if(ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)){
return packet; return packet;
} } else {
else {
return null; 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<Short> filteredPos = new ArrayList<>(oldPos.length);
List<BlockState> 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; return false;
} }
} }