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 f737af0a..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 @@ -31,7 +31,6 @@ import de.steamwar.command.TypeMapper; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; -import de.steamwar.techhider.TechHider; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -39,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 c9e6a3a6..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 @@ -25,7 +25,6 @@ import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.linkage.Linked; import de.steamwar.sql.BauweltMember; -import de.steamwar.techhider.TechHider; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -40,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 d4caf49d..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 @@ -28,7 +28,6 @@ import de.steamwar.command.SWCommand; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; -import de.steamwar.techhider.TechHider; import net.md_5.bungee.api.ChatMessageType; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.server.level.ServerPlayer; @@ -41,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/FightSystem/FightSystem_Core/build.gradle.kts b/FightSystem/FightSystem_Core/build.gradle.kts index b346e97c..9b2f746b 100644 --- a/FightSystem/FightSystem_Core/build.gradle.kts +++ b/FightSystem/FightSystem_Core/build.gradle.kts @@ -37,4 +37,5 @@ dependencies { compileOnly(libs.authlib) compileOnly(libs.nms) compileOnly(libs.fawe) + compileOnly(libs.datafixer) } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 7b1be233..d3339975 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -108,8 +108,10 @@ public class FightSystem extends JavaPlugin { new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new); Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode)); - techHider = new TechHiderWrapper(); hullHider = new HullHider(); + techHider = new TechHiderWrapper(hullHider); + FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true)); + FileSource.startReplay(); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java index 790a1cec..c8174349 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java @@ -158,6 +158,8 @@ public class FightSchematic extends StateDependent { FreezeWorld freezer = new FreezeWorld(); team.teleportToSpawn(); + // TODO: Implement hull generation based on clipboard content! + FightSystem.getHullHider().fill(team, false); Vector dims = WorldeditWrapper.impl.getDimensions(clipboard); WorldeditWrapper.impl.pasteClipboard( clipboard, diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java index 5663fe33..83c15170 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java @@ -68,6 +68,13 @@ public class FightWorld extends StateDependent { } public static void resetWorld() { + World backup = new WorldCreator(Config.world.getName() + "/backup").createWorld(); + assert backup != null; + Config.ArenaRegion.forEachChunk((x, z) -> { + CraftbukkitWrapper.impl.resetChunk(Config.world, backup, x, z); + }); + Bukkit.unloadWorld(backup, false); + List entities = new ArrayList<>(); Recording.iterateOverEntities(Objects::nonNull, entity -> { if (entity.getType() != EntityType.PLAYER && (!Config.GameModeConfig.Arena.Leaveable || Config.ArenaRegion.inRegion(entity.getLocation()))) { @@ -77,14 +84,12 @@ public class FightWorld extends StateDependent { entities.forEach(Entity::remove); entities.clear(); - World backup = new WorldCreator(Config.world.getName() + "/backup").createWorld(); - assert backup != null; + FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true)); + Config.ArenaRegion.forEachChunk((x, z) -> { - CraftbukkitWrapper.impl.resetChunk(Config.world, backup, x, z); for (Player p : Bukkit.getOnlinePlayers()) { de.steamwar.core.CraftbukkitWrapper.sendChunk(p, x, z); } }); - Bukkit.unloadWorld(backup, false); } } \ No newline at end of file diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ClickAnalyzer.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ClickAnalyzer.java index 3def965c..3e84d916 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ClickAnalyzer.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ClickAnalyzer.java @@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.utils.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; +import net.minecraft.network.protocol.game.ServerboundUseItemPacket; import org.bukkit.entity.Player; import java.io.*; @@ -42,7 +43,7 @@ public class ClickAnalyzer { } public ClickAnalyzer() { - TinyProtocol.instance.addFilter(Recording.blockPlacePacket, this::onBlockPlace); + TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, this::onBlockPlace); TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, this::onBlockPlace); } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PlayerJoinListener.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PlayerJoinListener.java deleted file mode 100644 index 71ecf2a7..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PlayerJoinListener.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.steamwar.fightsystem.listener; - -import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentListener; -import de.steamwar.linkage.Linked; -import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket; -import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.LevelChunk; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; - -@Linked -public class PlayerJoinListener implements Listener { - - public PlayerJoinListener() { - new StateDependentListener(true, FightState.All, this); - } - - @EventHandler() - public void onPlayerJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - World world = player.getWorld(); - - Location loc = player.getLocation(); - int viewDistance = Bukkit.getViewDistance(); - - int chunkX = loc.getChunk().getX(); - int chunkZ = loc.getChunk().getZ(); - - // Iterate through the chunks around the player and force a resend - for (int x = -viewDistance; x <= viewDistance; x++) { - for (int z = -viewDistance; z <= viewDistance; z++) { - Chunk chunk = world.getChunkAt(chunkX + x, chunkZ + z); - this.forceRefreshChunkForPlayer(player, chunk); - } - } - } - - public void forceRefreshChunkForPlayer(Player player, Chunk bukkitChunk) { - ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); - CraftWorld craftWorld = (CraftWorld) bukkitChunk.getWorld(); - - int chunkX = bukkitChunk.getX(); - int chunkZ = bukkitChunk.getZ(); - - LevelChunk nmsChunk = craftWorld.getHandle().getChunkSource().getChunk(chunkX, chunkZ, false); - if (nmsChunk == null) { - // Chunk isn't loaded in memory on the server side; - return; - } - - ClientboundForgetLevelChunkPacket unloadPacket = new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ)); - serverPlayer.connection.send(unloadPacket); - - ClientboundLevelChunkWithLightPacket loadPacket = new ClientboundLevelChunkWithLightPacket( - nmsChunk, - craftWorld.getHandle().getLightEngine(), - null, - null, - true - ); - serverPlayer.connection.send(loadPacket); - } -} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java index 05bed604..0187d089 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java @@ -37,6 +37,7 @@ import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.linkage.Linked; +import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundUseItemPacket; import net.minecraft.world.InteractionHand; @@ -124,7 +125,7 @@ public class Recording implements Listener { @Override public void disable() { - TinyProtocol.instance.removeFilter(blockPlacePacket, place); + TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place); TinyProtocol.instance.removeFilter(blockDigPacket, dig); } }.register(); @@ -142,7 +143,7 @@ public class Recording implements Listener { GlobalRecorder.getInstance().entitySpeed(entity); } - private static final Class blockDigPacket = ServerboundPlayerActionPacket.class; + private static final Class> blockDigPacket = ServerboundPlayerActionPacket.class; private static final Class playerDigType = blockDigPacket.getDeclaredClasses()[0]; private static final Reflection.Field blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0); private static final Object releaseUseItem = playerDigType.getEnumConstants()[5]; @@ -154,8 +155,6 @@ public class Recording implements Listener { return packet; } - public static final Class blockPlacePacket = ServerboundUseItemPacket.class; - private Object blockPlace(Player p, ServerboundUseItemPacket packet) { boolean mainHand = packet.getHand() == InteractionHand.MAIN_HAND; if (!isNotSent(p) && (mainHand ? p.getInventory().getItemInMainHand() : p.getInventory().getItemInOffHand()).getType() == Material.BOW) { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java index 72af266e..3ac2528d 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java @@ -165,6 +165,15 @@ public class Hull { rentities.remove(entity); } + public void fill(boolean visible) { + visibility.set(0, visibility.size(), visible); + occluding.set(0, occluding.size(), !visible); + uncoveredSurface.clear(); + for (BitSet directionalVisibility : visibilityDirections.values()) { + directionalVisibility.set(0, directionalVisibility.size(), visible); + } + } + public void initialize() { visibility.clear(); occluding.clear(); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java index f214f50e..58bfce48 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java @@ -19,8 +19,6 @@ package de.steamwar.fightsystem.utils; -import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.entity.REntity; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; @@ -28,18 +26,9 @@ import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.listener.Recording; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; -import de.steamwar.techhider.TechHider; import lombok.Getter; -import net.minecraft.core.Vec3i; -import net.minecraft.network.protocol.game.ClientboundExplodePacket; -import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; -import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; -import net.minecraft.network.protocol.game.ClientboundSoundPacket; -import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -55,17 +44,12 @@ import org.bukkit.event.player.PlayerQuitEvent; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.function.BiFunction; -import java.util.function.Function; public class HullHider implements Listener { @Getter private final Map hullMap = new HashMap<>(); private final Hull[] hulls; - private final Map, BiFunction> packetHiders = new HashMap<>(); - - private static final Class packetPlayOutExplosion = ClientboundExplodePacket.class; public HullHider() { if (!TechHiderWrapper.ENABLED) { @@ -76,34 +60,20 @@ public class HullHider implements Listener { Fight.teams().forEach(team -> hullMap.put(team, new Hull(team))); hulls = hullMap.values().toArray(new Hull[0]); - packetHiders.put(packetPlayOutWorldEvent, this::worldEventHider); - packetHiders.put(packetPlayOutExplosion, this::explosionHider); - posHiderGenerator(ClientboundLevelParticlesPacket.class, double.class, 1.0); - posHiderGenerator(ClientboundSoundPacket.class, int.class, 8.0); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this); - new StateDependent(TechHiderWrapper.ENABLED, FightState.Schem) { - @Override - public void enable() { - packetHiders.forEach(TinyProtocol.instance::addFilter); - Bukkit.getOnlinePlayers().forEach(HullHider.this::updatePlayer); - } - - @Override - public void disable() { - Bukkit.getOnlinePlayers().forEach(player -> removePlayer(player, true)); - packetHiders.forEach(TinyProtocol.instance::removeFilter); - } - }.register(); new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1); } public void initialize(FightTeam team) { if (!TechHiderWrapper.ENABLED) return; - hullMap.get(team).initialize(); } + public void fill(FightTeam team, boolean visible) { + if (!TechHiderWrapper.ENABLED) return; + hullMap.get(team).fill(visible); + } @EventHandler(priority = EventPriority.HIGH) public void onJoin(PlayerJoinEvent e) { @@ -161,18 +131,6 @@ public class HullHider implements Listener { return false; } - public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) { - if (!TechHiderWrapper.ENABLED) return false; - - for (Hull hull : hulls) { - if (hull.blockPrecise(player, chunkX, chunkY, chunkZ)) { - return true; - } - } - - return false; - } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onSpawn(EntitySpawnEvent e) { for (Hull hull : hulls) { @@ -210,39 +168,4 @@ public class HullHider implements Listener { hull.removeREntity(e); } } - - - private static final Class packetPlayOutWorldEvent = ClientboundLevelEventPacket.class; - private static final Reflection.Field worldEventPosition = Reflection.getField(packetPlayOutWorldEvent, TechHider.blockPosition, 0); - public static final Reflection.Field blockPositionY = Reflection.getField(Vec3i.class, int.class, 1); - - private Object worldEventHider(Player player, Object packet) { - Object baseBlock = worldEventPosition.get(packet); - return packetHider(player, packet, new Location(Config.world, TechHider.blockPositionX.get(baseBlock), blockPositionY.get(baseBlock), TechHider.blockPositionZ.get(baseBlock))); - } - - private Object explosionHider(Player player, Object packet) { - return packet; - } - - private void posHiderGenerator(Class type, Class posType, double factor) { - Function location = posPacketToLocation(type, posType, factor); - packetHiders.put(type, (player, packet) -> packetHider(player, packet, location.apply(packet))); - } - - public static Function posPacketToLocation(Class type, Class posType, double factor) { - Reflection.Field x = Reflection.getField(type, posType, 0); - Reflection.Field y = Reflection.getField(type, posType, 1); - Reflection.Field z = Reflection.getField(type, posType, 2); - - return packet -> new Location(Config.world, x.get(packet).doubleValue() / factor, y.get(packet).doubleValue() / factor, z.get(packet).doubleValue() / factor); - } - - private Object packetHider(Player player, Object packet, Location location) { - for (Hull hull : hulls) { - if (hull.isLocationHidden(player, location)) return null; - } - - return packet; - } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java index b3fae41f..091d5717 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -19,9 +19,9 @@ package de.steamwar.fightsystem.utils; +import de.steamwar.Reflection; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.events.BoardingEvent; import de.steamwar.fightsystem.events.TeamLeaveEvent; import de.steamwar.fightsystem.events.TeamSpawnEvent; @@ -30,56 +30,103 @@ import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependentListener; -import de.steamwar.sql.SteamwarUser; +import de.steamwar.techhider.AccessPrivilegeProvider; import de.steamwar.techhider.TechHider; import lombok.Getter; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; import org.bukkit.GameMode; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; -public class TechHiderWrapper extends StateDependent implements TechHider.LocationEvaluator, Listener { +public class TechHiderWrapper extends StateDependent implements Listener { public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active; @Getter private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); - private final ConcurrentHashMap patterns = new ConcurrentHashMap<>(); - private final TechHider techHider; - private final SecretKey key; - public TechHiderWrapper() { - super(ENABLED, FightState.Schem); - techHider = new TechHider(this, Config.GameModeConfig.Techhider.ObfuscateWith, Config.GameModeConfig.Techhider.HiddenBlocks, Config.GameModeConfig.Techhider.HiddenBlockEntities); + public TechHiderWrapper(HullHider hullHider) { + super(ENABLED, FightState.All); + Set blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream() + .map(CraftMagicNumbers::getBlock) + .collect(Collectors.toUnmodifiableSet()); + Object blockEntityType; try { - key = new SecretKeySpec(Files.readAllBytes(new File(System.getProperty("user.home"), "hullhider.key").toPath()), "AES"); - } catch (IOException e) { - throw new SecurityException(e); + blockEntityType = BuiltInRegistries.class.getDeclaredField("BLOCK_ENTITY_TYPE").get(null); + } catch (Exception e) { + throw new IllegalStateException(e); } + Reflection.Method method = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "get", Optional.class, ResourceLocation.class); + Set> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream() + .map((id) -> { + ResourceLocation loc = ResourceLocation.parse(id); + return ((Optional>>) method.invoke(blockEntityType, loc)).get().value(); + }) + .collect(Collectors.toUnmodifiableSet()); - new StateDependentListener(ENABLED, FightState.Schem, this); + new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() { + @Override + public boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) { + return Fight.teams().stream().map(FightTeam::getExtendRegion).allMatch(region -> region.chunkOutside(chunkX, chunkZ)); + } + + @Override + public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) { + return !hullHider.isBlockHidden(p, blockX, blockY, blockZ); + } + + @Override + public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) { + return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block); + } + + // TODO will require entity tracking on the netty thread to prevent future race conditions + @Override + public boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId); + + if (nmsEntity != null) { + return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ()); + } else { + return true; + } + } + + @Override + public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type) { + return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); + } + + @Override + public boolean isPlayerPrivilegedToPerformAction(Player p) { + return p.getGameMode() != GameMode.SPECTATOR; + } + }); + + new StateDependentListener(ENABLED, FightState.All, this); register(); } @Override public void enable() { - techHider.enable(); } @Override public void disable() { - techHider.disable(); hiddenRegion.clear(); } @@ -118,72 +165,6 @@ public class TechHiderWrapper extends StateDependent implements TechHider.Locati }); } - @Override - public boolean suppressInteractions(Player player) { - return player.getGameMode() == GameMode.SPECTATOR; - } - - @Override - public boolean skipChunk(Player player, int chunkX, int chunkZ) { - return getHiddenRegion(player).chunkOutside(chunkX, chunkZ); - } - - @Override - public boolean skipChunkSection(Player player, int chunkX, int chunkY, int chunkZ) { - return getHiddenRegion(player).chunkSectionOutside(chunkX, chunkY, chunkZ); - } - - @Override - public TechHider.State check(Player player, int x, int y, int z) { - if (hiddenRegion.computeIfAbsent(player, this::getHiddenRegion).inRegion(x, y, z)) { - if (FightSystem.getHullHider().isBlockHidden(player, x, y, z)) { - int id = ((y & 3) << 4) + ((z & 3) << 2) + (x & 3); - return (patterns.computeIfAbsent(player, this::getPattern) & (1L << id)) == 0 ? TechHider.State.HIDE_AIR : TechHider.State.HIDE; - } else { - return TechHider.State.CHECK; - } - } else { - return TechHider.State.SKIP; - } - } - - public long getPattern(Player player) { - long pattern = SteamwarUser.get(player.getUniqueId()).getId(); - - byte[] bytes = new byte[]{ - (byte) (pattern & 0xFF), - (byte) ((pattern >>> 8) & 0xFF), - (byte) ((pattern >>> 16) & 0xFF), - (byte) ((pattern >>> 24) & 0xFF) - }; - - try { - Cipher cipher = Cipher.getInstance("AES_256/CFB/NoPadding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - bytes = cipher.doFinal(bytes); - } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | - InvalidKeyException e) { - throw new SecurityException(e); - } - - pattern = bytes[0] & 0xFFL | - ((bytes[1] & 0xFFL) << 8) | - ((bytes[2] & 0xFFL) << 16) | - ((bytes[3] & 0xFFL) << 24); - - /* XXXO OOOX - * XXXO OOOX - * XXOX OOXO - * OOXX XXOO */ - pattern |= 0b1110_1110_1101_0011___0001_0001_0010_1100___0000_0000_0000_0000___0000_0000_0000_0000L; - return pattern; - } - - @Override - public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) { - return FightSystem.getHullHider().blockPrecise(player, chunkX, chunkY, chunkZ); - } - private Region getHiddenRegion(Player player) { if (Config.isReferee(player)) return Region.EMPTY; diff --git a/FightSystem/build.gradle.kts b/FightSystem/build.gradle.kts index 6f0c40a7..c6b14b8b 100644 --- a/FightSystem/build.gradle.kts +++ b/FightSystem/build.gradle.kts @@ -57,9 +57,10 @@ tasks.register("WarGear21") { description = "Run a WarGear 1.21 Fight Server" dependsOn(":SpigotCore:shadowJar") dependsOn(":FightSystem:shadowJar") + dependsOn(":KotlinCore:shadowJar") template = "WarGear21" worldName = "arenas/Pentraki" - config = "WarGear20.yml" + config = "WarGear21.yml" jar = "/jars/paper-1.21.6.jar" } diff --git a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 9dad8987..f7df16c1 100644 --- a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -1,22 +1,3 @@ -/* - * 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 com.comphenix.tinyprotocol; import com.google.common.collect.MapMaker; @@ -77,6 +58,7 @@ public class TinyProtocol { public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); private final Map, List>> packetFilters = new HashMap<>(); + private final Set> globalClientboundFilters = new HashSet<>(); public static void init() { // enforce init @@ -245,7 +227,7 @@ public class TinyProtocol { } } - public void addFilter(Class packetType, BiFunction filter) { + public > void addFilter(Class packetType, BiFunction filter) { packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add((BiFunction) filter); } @@ -253,6 +235,10 @@ public class TinyProtocol { packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); } + public void addGlobalClientboundFilter(BiFunction, Object> filter) { + globalClientboundFilters.add((BiFunction) filter); + } + /** * Send a packet to a particular player. * @@ -466,6 +452,13 @@ public class TinyProtocol { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { try { msg = filterPacket(player, msg); + + if (msg instanceof Packet) { + for (BiFunction filter : globalClientboundFilters) { + msg = filter.apply(player, msg); + if (msg == null) break; + } + } } catch (Exception e) { plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java index 91b183ad..b5276c00 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java @@ -56,21 +56,15 @@ public class AntiNocom implements Listener { } private void registerUseItem() { - Class movingObjectPositionBlock = BlockHitResult.class; - Reflection.Field useItemPosition = Reflection.getField(ServerboundUseItemOnPacket.class, movingObjectPositionBlock, 0); - Reflection.Field movingBlockPosition = Reflection.getField(movingObjectPositionBlock, TechHider.blockPosition, 0); - - Function getPosition = (packet) -> movingBlockPosition.get(useItemPosition.get(packet)); - TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> { - Object pos = getPosition.apply(packet); - return isValid(player, "UseItem", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null; + BlockPos pos = packet.getHitResult().getBlockPos(); + return isValid(player, "UseItem", pos.getX(), pos.getZ()) ? packet : null; }); } - private Object onDig(Player player, ServerboundPlayerActionPacket packet) { + private ServerboundPlayerActionPacket onDig(Player player, ServerboundPlayerActionPacket packet) { BlockPos pos = packet.getPos(); - return isValid(player, "Dig", TechHider.blockPositionX.get(pos), TechHider.blockPositionZ.get(pos)) ? packet : null; + return isValid(player, "Dig", pos.getX(), pos.getZ()) ? packet : null; } private boolean isValid(Player player, String type, int x, int z) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java new file mode 100644 index 00000000..16df8ab0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -0,0 +1,38 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 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; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.bukkit.entity.Player; + +public interface AccessPrivilegeProvider { + boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ); + + boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ); + + boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block); + + boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId); + + boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); + + boolean isPlayerPrivilegedToPerformAction(Player p); +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java index ec4a5f95..b13295bf 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java @@ -19,8 +19,7 @@ package de.steamwar.techhider; -import de.steamwar.Reflection; -import net.minecraft.core.IdMapper; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; @@ -39,18 +38,20 @@ public class BlockIds { } 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)); + for (Block block : BuiltInRegistries.BLOCK) { + for (BlockState data : block.getStateDefinition().getPossibleStates()) { + if (data.getFluidState() == water) { + ids.add(getCombinedId(data)); + } } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index ea4c809d..0a280bee 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -22,295 +22,278 @@ package de.steamwar.techhider; 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.core.SectionPos; 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 net.minecraft.world.level.block.state.BlockState; 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(); + private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - public Class mapChunkPacket() { - return ClientboundLevelChunkWithLightPacket.class; + private static final Reflection.Field levelChunkPacketDataField = 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 final int SECTION_SPAN_SIZE = 16; + private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; + private final byte BIT_PER_BIOME_INDIRECTION_LIMIT = 3; + + private final int BLOCKS_PER_SECTION = 4096; + private final int BIOMES_PER_SECTION = 64; + + private final int blockIdUsedForHiding; + private final AccessPrivilegeProvider accessPrivilegeProvider; + + public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) { + blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState()); + this.accessPrivilegeProvider = accessPrivilegeProvider; } - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) { + int entriesPerLong = Long.SIZE / bitsPerEntry; + int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong; - 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; - }; + return dataLengthAsLongCount; } - 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); + private long[] readSectionDataFromBuffer(ByteBuf dataSource, short bitsPerEntry, int entryCount) { + int dataLengthAsLongCount = getLongsRequiredToEncodeEntries(bitsPerEntry, entryCount); + long[] dataArray = new long[dataLengthAsLongCount]; - 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; + } + + public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(Player player, ClientboundLevelChunkWithLightPacket packet) { + int chunkX = packet.getX(); + int chunkZ = packet.getZ(); + ClientboundLevelChunkPacketData chunkData = packet.getChunkData(); + + ByteBuf in = Unpooled.wrappedBuffer(chunkData.getReadBuffer()); + ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); + + int worldMinHeight = player.getWorld().getMinHeight(); + int worldMaxHeight = player.getWorld().getMaxHeight(); + + for (int yOffset = worldMinHeight; yOffset < worldMaxHeight; yOffset += SECTION_SPAN_SIZE) { + short blockCount = in.readShort(); + + byte bitsPerBlock = in.readByte(); + + if (bitsPerBlock == 0) { + int sectionBlockId = ProtocolUtils.readVarInt(in); + + out.writeShort(blockCount); + out.writeByte(bitsPerBlock); + ProtocolUtils.writeVarInt(out, sectionBlockId); + } 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[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, resolvedData); + + long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData); + + + out.writeShort(blockCount); + out.writeByte(15); + for (long rawDataSegment : reEncodedData) { + out.writeLong(rawDataSegment); + } + /* + Int2IntMap blockIdToPalletIndex = new Int2IntOpenHashMap(); + IntArrayList newPallet = new IntArrayList(); + + for(int blockId : obfuscatedData) { + if(!blockIdToPalletIndex.containsKey(blockId)) { + newPallet.add(blockId); + blockIdToPalletIndex.put(blockId, newPallet.size()); + } + } + + byte newBitsPerBlock = (byte) getUnsignedBitLength(newPallet.size()); + int[] newPalletRaw = newPallet.toArray(new int[newPallet.size()]); + + 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.writeShort(blockCount); + out.writeByte(newBitsPerBlock); + ProtocolUtils.writeVarInt(out, palletLength); + ProtocolUtils.writeVarIntArray(out, newPalletRaw); + for(long rawDataSegment : reEncodedData.getRaw()) { + out.writeLong(rawDataSegment); + }*/ + + } else { + long[] rawData = readSectionDataFromBuffer(in, bitsPerBlock, BLOCKS_PER_SECTION); + + int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock); + + int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, blockData); + + long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData); + + + out.writeShort(blockCount); + out.writeByte(15); + for (long rawDataSegment : reEncodedData) { + out.writeLong(rawDataSegment); + } + } + + copyOverSectionBiomeData(in, out); } - SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); + if (in.readableBytes() != 0) { + return null; + } - 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; + byte[] data = new byte[out.readableBytes()]; + out.readBytes(data); - TechHider.State test = section.test(x, y, z); + List blockEntities = chunkBlockEntitiesDataField.get(chunkData); + List filteredBlockEntities = filterBlockEntities(player, blockEntities); - 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()); + return buildNewChunkPacket(packet, data, filteredBlockEntities); + + } + + private int[] obfuscateBlockDataArray(Player player, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) { + int[] obfuscatedData = new int[BLOCKS_PER_SECTION]; + for (int sectionY = 0; sectionY < SECTION_SPAN_SIZE; sectionY++) { + for (int sectionZ = 0; sectionZ < SECTION_SPAN_SIZE; sectionZ++) { + for (int sectionX = 0; sectionX < SECTION_SPAN_SIZE; sectionX++) { + int blockDataIndex = (((sectionY * SECTION_SPAN_SIZE) + sectionZ) * SECTION_SPAN_SIZE) + sectionX; + + int worldX = sectionX + (SECTION_SPAN_SIZE * chunkX); + int worldY = sectionY + yOffset; + int worldZ = sectionZ + (SECTION_SPAN_SIZE * chunkZ); + + int blockId = blockDataArray[blockDataIndex]; + BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(blockId); + Block block = blockState.getBlock(); + + + if (accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) { + obfuscatedData[blockDataIndex] = blockId; + } else { + obfuscatedData[blockDataIndex] = blockIdUsedForHiding; } } } } - section.writeDataArray(values.getRaw()); + return obfuscatedData; } - private void biomes(SectionHider section) { - section.copyBitsPerBlock(); - if (section.getBitsPerBlock() == 0) { - section.copyVarInt(); - } else if (section.getBitsPerBlock() < 6) { - section.skipPalette(); - section.skipNewDataArray(64); + private int[] resolveDirectBlockDataArray(long[] rawDataArray, byte bitsPerBlock) { + SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawDataArray); + + int[] resolvedData = new int[BLOCKS_PER_SECTION]; + for (int i = 0; i < BLOCKS_PER_SECTION; i++) { + resolvedData[i] = data.get(i); + } + + return resolvedData; + } + + private long[] encodeDirectBlockDataArray(int[] blockDataArray) { + int longsRequiredToEncodeData = getLongsRequiredToEncodeEntries(15, blockDataArray.length); + + SimpleBitStorage reEncodedData = new SimpleBitStorage(15, BLOCKS_PER_SECTION, new long[longsRequiredToEncodeData]); + for (int i = 0; i < blockDataArray.length; i++) { + int blockId = blockDataArray[i]; + + reEncodedData.set(i, blockId); + } + + return reEncodedData.getRaw(); + } + + 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, int.class, 0); + private static final Reflection.Field yField = Reflection.getField(blockEntitiyInfoClass, int.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 accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, x, y, z) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, x, y, z, type); + }).toList(); + } + + private void copyOverSectionBiomeData(ByteBuf oldData, ByteBuf newData) { + short bitsPerBiome = oldData.readByte(); + newData.writeByte(bitsPerBiome); + if (bitsPerBiome == 0) { + int sectionBiomeId = ProtocolUtils.readVarInt(oldData); + ProtocolUtils.writeVarInt(newData, sectionBiomeId); + } else if (bitsPerBiome <= BIT_PER_BIOME_INDIRECTION_LIMIT) { + int palletLength = ProtocolUtils.readVarInt(oldData); + ProtocolUtils.writeVarInt(newData, palletLength); + + for (int i = 0; i < palletLength; i++) { + int palletEntry = ProtocolUtils.readVarInt(oldData); + ProtocolUtils.writeVarInt(newData, palletEntry); + } + + long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION); + for (long rawDataSegment : rawData) { + newData.writeLong(rawDataSegment); + } } 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); + long[] rawData = readSectionDataFromBuffer(oldData, bitsPerBiome, BIOMES_PER_SECTION); + for (long rawDataSegment : rawData) { + newData.writeLong(rawDataSegment); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index 736027cc..e71b522a 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; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 2c291091..ed18f556 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * Copyright (C) 2026 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 @@ -21,161 +21,534 @@ package de.steamwar.techhider; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.Reflection; -import lombok.Getter; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.shorts.ShortArraySet; +import it.unimi.dsi.fastutil.shorts.ShortSets; import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; +import net.minecraft.core.SectionPos; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.*; +import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket; +import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket; +import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks; +import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket; +import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket; import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.login.*; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.Material; -import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import net.minecraft.world.phys.Vec3; import org.bukkit.entity.Player; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; +import java.util.function.ToIntFunction; +/** + * The TechHider follows the default-deny security principle, + * any packet must be explicitly whitelisted or processed in a + * way that is also compliant with the principle of default-deny. + */ public class TechHider { + private final Set> bypassingPackets; + private final Map>, BiFunction, Packet>> packetProcessors; - 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); + private final BlockState blockStateUsedForObfuscation; + private final ChunkHider chunkHider; - public static final Class iBlockData = BlockState.class; - public static final Class block = Block.class; + private final AccessPrivilegeProvider privilegeProvider; - public boolean iBlockDataHidden(BlockState iBlockData) { - return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData)); - } + // TODO handle packet bundle + public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) { + this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState(); - public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState(); - public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR); + this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider); + this.privilegeProvider = privilegeProvider; - 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; + this.bypassingPackets = new HashSet<>(List.of( + // --- 5.1.x Login Protocol --- + ClientboundLoginDisconnectPacket.class, // 5.1.1 Disconnect + ClientboundHelloPacket.class, // 5.1.2 Encryption Request + ClientboundLoginFinishedPacket.class, // 5.1.3 Login Success + ClientboundLoginCompressionPacket.class, // 5.1.4 Set Compression + ClientboundCustomQueryPacket.class, // 5.1.5 Login Plugin Request + ClientboundCookieRequestPacket.class, // 5.1.6 Cookie Request - 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); + // --- 6.1.x Configuration Protocol --- + ClientboundSelectKnownPacks.class, ClientboundCustomPayloadPacket.class, // 6.1.2 Clientbound Plugin Message + ClientboundFinishConfigurationPacket.class, // 6.1.4 Finish Configuration + ClientboundKeepAlivePacket.class, // 6.1.5 Clientbound Keep Alive + ClientboundPingPacket.class, // 6.1.6 Ping + ClientboundRegistryDataPacket.class, // 6.1.8 Registry Data + ClientboundResourcePackPopPacket.class, // 6.1.9 Remove Resource Pack + ClientboundResourcePackPushPacket.class, // 6.1.10 Add Resource Pack + ClientboundStoreCookiePacket.class, // 6.1.11 Store Cookie + ClientboundTransferPacket.class, // 6.1.12 Transfer + ClientboundUpdateEnabledFeaturesPacket.class, // 6.1.13 Feature Flags + ClientboundCustomReportDetailsPacket.class, // 6.1.16 Custom Report Details + ClientboundServerLinksPacket.class, // 6.1.17 Server Links + ClientboundSystemChatPacket.class, // 6.1.18/19 Dialogs are often handled via System Chat or Custom + // Payloads + ClientboundServerDataPacket.class, // 6.1.20 Code of Conduct is usually in Server Data - 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); + // --- 7.1.x Play Protocol --- - } + // --- Safe packets; Not involved with a critical subdomain --- + ClientboundBundleDelimiterPacket.class, // 7.1.1 Bundle Delimiter + ClientboundBossEventPacket.class, // 7.1.10 Boss Bar + ClientboundChangeDifficultyPacket.class, // 7.1.11 Change Difficulty + ClientboundClearTitlesPacket.class, // 7.1.15 Clear Titles + ClientboundCommandSuggestionsPacket.class, // 7.1.16 Command Suggestions Response + ClientboundCommandsPacket.class, // 7.1.17 Commands + ClientboundCookieRequestPacket.class, // 7.1.22 Cookie Request (play) + ClientboundCooldownPacket.class, // 7.1.23 Set Cooldown + ClientboundCustomChatCompletionsPacket.class, // 7.1.24 Chat Suggestions + ClientboundDeleteChatPacket.class, // 7.1.32 Delete Message + ClientboundDisconnectPacket.class, // 7.1.33 Disconnect (play) + ClientboundDisguisedChatPacket.class, // 7.1.34 Disguised Chat Message + ClientboundGameEventPacket.class, // 7.1.39 Game Event (like ElderGuardian effect, rain, thunder, etc.) + ClientboundHorseScreenOpenPacket.class, // 7.1.41 Open Horse Screen: entity id based container. + ClientboundInitializeBorderPacket.class, // 7.1.43 Initialize World Border + ClientboundKeepAlivePacket.class, // 7.1.44 Clientbound Keep Alive (play) + ClientboundLoginPacket.class, // 7.1.49 Login (play) + ClientboundMerchantOffersPacket.class, // 7.1.51 Merchant Offers + ClientboundOpenBookPacket.class, // 7.1.57 Open Book + ClientboundOpenScreenPacket.class, // 7.1.58 Open Screen + ClientboundPingPacket.class, // 7.1.60 Ping (play) + ClientboundPlaceGhostRecipePacket.class, // 7.1.62 Place Ghost Recipe + ClientboundPlayerAbilitiesPacket.class, // 7.1.63 Player Abilities (clientbound) + ClientboundPlayerChatPacket.class, // 7.1.64 Player Chat Message + ClientboundPlayerCombatEndPacket.class, // 7.1.65 End Combat + ClientboundPlayerCombatEnterPacket.class, // 7.1.66 Enter Combat + ClientboundPlayerLookAtPacket.class, // 7.1.70 Look At (Player owning channel) + ClientboundPlayerPositionPacket.class, // 7.1.71 Synchronize Player Position (Player owning the channel) + ClientboundPlayerRotationPacket.class, // 7.1.72 Player Rotation (Player owning the channel) + ClientboundRecipeBookAddPacket.class, // 7.1.73 Recipe Book Add + ClientboundRecipeBookRemovePacket.class, // 7.1.74 Recipe Book Remove + ClientboundRecipeBookSettingsPacket.class, // 7.1.75 Recipe Book Settings + ClientboundResetScorePacket.class, // 7.1.78 Reset Score + ClientboundResourcePackPopPacket.class, // 7.1.79 Remove Resource Pack (play) + ClientboundResourcePackPushPacket.class, // 7.1.80 Add Resource Pack (play) + ClientboundRespawnPacket.class, // 7.1.81 Respawn + ClientboundSelectAdvancementsTabPacket.class, // 7.1.84 Select Advancements Tab + ClientboundServerDataPacket.class, // 7.1.85 Server Data + ClientboundSetActionBarTextPacket.class, // 7.1.86 Set Action Bar Text + ClientboundSetBorderCenterPacket.class, // 7.1.87 Set Border Center + ClientboundSetBorderLerpSizePacket.class, // 7.1.88 Set Border Lerp Size + ClientboundSetBorderSizePacket.class, // 7.1.89 Set Border Size + ClientboundSetBorderWarningDelayPacket.class, // 7.1.90 Set Border Warning Delay + ClientboundSetBorderWarningDistancePacket.class, // 7.1.91 Set Border Warning Distance + ClientboundSetCameraPacket.class, // 7.1.92 Set Camera + ClientboundSetChunkCacheRadiusPacket.class, // 7.1.94 Set Render Distance + ClientboundSetCursorItemPacket.class, // 7.1.95 Set Cursor Item + ClientboundSetDefaultSpawnPositionPacket.class, // 7.1.96 Set Default Spawn Position + ClientboundSetDisplayObjectivePacket.class, // 7.1.97 Display Objective + ClientboundSetExperiencePacket.class, // 7.1.102 Set Experience + ClientboundSetHealthPacket.class, // 7.1.103 Set Health, Saturation and food (Player owning the channel) + ClientboundSetHeldSlotPacket.class, // 7.1.104 Set Held Item (Player owning the channel) + ClientboundSetObjectivePacket.class, // 7.1.105 Update Objectives + ClientboundSetPlayerInventoryPacket.class, // 7.1.107 Set Player Inventory Slot (Player owning the channel) + ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams + ClientboundSetScorePacket.class, // 7.1.109 Update Score + ClientboundSetSimulationDistancePacket.class, // 7.1.110 Set Simulation Distance + ClientboundSetSubtitleTextPacket.class, // 7.1.111 Set Subtitle Text + ClientboundSetTimePacket.class, // 7.1.112 Update Time + ClientboundSetTitleTextPacket.class, // 7.1.113 Set Title Text + ClientboundSetTitlesAnimationPacket.class, // 7.1.114 Set Title Animation Times + ClientboundStartConfigurationPacket.class, // 7.1.117 Start Configuration + ClientboundStoreCookiePacket.class, // 7.1.119 Store Cookie (play) + ClientboundSystemChatPacket.class, // 7.1.120 System Chat Message + ClientboundTabListPacket.class, // 7.1.121 Set Tab List Header And Footer + ClientboundTickingStatePacket.class, // 7.1.126 Set Ticking State + ClientboundTickingStepPacket.class, // 7.1.127 Step Tick + ClientboundTransferPacket.class, // 7.1.128 Transfer (play) + ClientboundUpdateAdvancementsPacket.class, // 7.1.129 Update Advancements + ClientboundUpdateRecipesPacket.class, // 7.1.132 Update Recipes + ClientboundCustomReportDetailsPacket.class, // 7.1.135 Custom Report Details + ClientboundServerLinksPacket.class, // 7.1.136 Server Links + ClientboundClearDialogPacket.class, // 7.1.138 Clear Dialog (play) + ClientboundShowDialogPacket.class, // 7.1.139 Show Dialog (play) - public void enable() { - techhiders.forEach(TinyProtocol.instance::addFilter); - } + // --- Mostly safe packets; Are involved with critical subdomain, but in not critical way --- + ClientboundBlockChangedAckPacket.class, // 7.1.5 Acknowledge Block Change + ClientboundChunkBatchFinishedPacket.class, // 7.1.12 Chunk Batch Finished (Delimiter) + ClientboundChunkBatchStartPacket.class, // 7.1.13 Chunk Batch Start (Delimiter) + ClientboundChunksBiomesPacket.class, // 7.1.14 Chunk Biomes + ClientboundContainerClosePacket.class, // 7.1.18 Close Container + ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk + ClientboundForgetLevelChunkPacket.class, // 7.1.38 Unload Chunk + ClientboundUpdateTagsPacket.class, // 7.1.133 Update Tags (play) + ClientboundPlayerInfoRemovePacket.class, // 7.1.68 Player Info Remove + ClientboundPlayerInfoUpdatePacket.class, // 7.1.69 Player Info Update + ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in) + ClientboundStopSoundPacket.class, // 7.1.118 Stop Sound: sound state side channel - public void disable() { - techhiders.forEach(TinyProtocol.instance::removeFilter); - } + ClientboundLightUpdatePacket.class, // 7.1.48 Update Light - public static final Class multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + ClientboundContainerSetContentPacket.class, // 7.1.19 Set Container Content + ClientboundContainerSetDataPacket.class, // 7.1.20 Set Container Property + ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot + )); - 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); + BiFunction, Packet> tossPacket = (p, packet) -> null; + Map>, BiFunction, Packet>> processors = new HashMap<>(); - 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; + // --- Entity packets - + // 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions. + processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet)); + // 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled. + processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId)); + // 7.1.35 Entity Event: entity id based signal. + processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(ClientboundEntityEventPacket::getEventId)); + // 7.1.36 Teleport Entity: entity id and absolute position. + processors.put(ClientboundTeleportEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTeleportEntityPacket::id)); + // 7.1.42 Hurt Animation: entity id based signal. + processors.put(ClientboundHurtAnimationPacket.class, this.buildEntityPacketProcessor(ClientboundHurtAnimationPacket::id)); + // 7.1.100 Set Entity Velocity: entity id and movement. + processors.put(ClientboundSetEntityMotionPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityMotionPacket::getId)); + // 7.1.101 Set Equipment: entity equipment can reveal wargear. + processors.put(ClientboundSetEquipmentPacket.class, this.buildEntityPacketProcessor(ClientboundSetEquipmentPacket::getEntity)); + // 7.1.106 Set Passengers: entity relationship signal. + processors.put(ClientboundSetPassengersPacket.class, this.buildEntityPacketProcessor(ClientboundSetPassengersPacket::getVehicle)); + // 7.1.130 Update Attributes: entity id and attribute state. + processors.put(ClientboundUpdateAttributesPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateAttributesPacket::getEntityId)); + // 7.1.131 Entity Effect: entity id and effect state. + processors.put(ClientboundUpdateMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateMobEffectPacket::getEntityId)); + // 7.1.77 Remove Entity Effect: entity id and effect state. + processors.put(ClientboundRemoveMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundRemoveMobEffectPacket::entityId)); + // 7.1.98 Set Entity Metadata: entity state can reveal blocks/items/displays. + processors.put(ClientboundSetEntityDataPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityDataPacket::id)); + // 7.1.26 Damage Event: entity ids and damage source location can leak hide activity. + processors.put(ClientboundDamageEventPacket.class, this.buildEntityPacketProcessor(ClientboundDamageEventPacket::entityId)); + // 7.1.54 Move Minecart Along Track: entity path and position signal. + processors.put(ClientboundMoveMinecartPacket.class, this.buildEntityPacketProcessor(ClientboundMoveMinecartPacket::entityId)); + // 7.1.124 Synchronize Vehicle Position: entity/vehicle position. + processors.put(ClientboundEntityPositionSyncPacket.class, this.buildEntityPacketProcessor(ClientboundEntityPositionSyncPacket::id)); + // 7.1.134 Projectile Power: projectile/entity signal. + processors.put(ClientboundProjectilePowerPacket.class, this.buildEntityPacketProcessor(ClientboundProjectilePowerPacket::getId)); + // 7.1.123 Pickup Item: item/entity ids and pickup activity. + processors.put(ClientboundTakeItemEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTakeItemEntityPacket::getItemId)); + // 7.1.67 Combat Death: entity/player ids and death message context. + processors.put(ClientboundPlayerCombatKillPacket.class, this.buildEntityPacketProcessor(ClientboundPlayerCombatKillPacket::playerId)); + // 7.1.52/53/55 Update Entity Position/Rotation: entity id and movement signal. + processors.put(ClientboundMoveEntityPacket.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket) packet)); + processors.put(ClientboundMoveEntityPacket.Pos.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Pos) packet)); + processors.put(ClientboundMoveEntityPacket.Rot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Rot) packet)); + processors.put(ClientboundMoveEntityPacket.PosRot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.PosRot) packet)); + + // 7.1.82 Set Head Rotation: entity id and rotation. + processors.put(ClientboundRotateHeadPacket.class, (p, packet) -> this.processRotateHeadPacket(p, (ClientboundRotateHeadPacket) packet)); + // 7.1.76 Remove Entities: entity id visibility side channel. + processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet)); + // 7.1.99 Link Entities: entity relationship signal. + processors.put(ClientboundSetEntityLinkPacket.class, (p, packet) -> this.processSetEntityLinkPacket(p, (ClientboundSetEntityLinkPacket) packet)); + + // --- Block entity packets --- + // 7.1.7 Block Entity Data: currently filtered by block position and allowed tile-entity actions. + processors.put(ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet)); + // 7.1.122 Tag Query Response: block-entity query result. + processors.put(ClientboundTagQueryPacket.class, tossPacket); + + // --- Block packets --- + // 7.1.6 Set Block Destroy Stage: block position and mining progress can reveal activity. + processors.put(ClientboundBlockDestructionPacket.class, (p, packet) -> proccessBlockDestructionPacket(p, (ClientboundBlockDestructionPacket) packet)); + // 7.1.8 Block Action: currently filtered by block position. + processors.put(ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet)); + // 7.1.9 Block Update: currently filtered/obfuscated by block position and block id. + processors.put(ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet)); + + // --- Chunk packets --- + // 7.1.45 Chunk Data and Update Light: currently filtered/obfuscated by chunk data processor. + processors.put(ClientboundLevelChunkWithLightPacket.class, (p, packet) -> processChunkWithLight(p, (ClientboundLevelChunkWithLightPacket) packet)); + + // --- Section packets --- + // 7.1.83 Update Section Blocks: currently filtered/obfuscated by section block processor. + processors.put(ClientboundSectionBlocksUpdatePacket.class, (p, packet) -> processSectionUpdate(p, (ClientboundSectionBlocksUpdatePacket) packet)); + + // --- Particle packets --- + // 7.1.47 Particle: particle type and position can reveal hidden machinery. + processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet)); + + // --- Sound packets --- + // 7.1.115 Entity Sound Effect: entity id and sound. + processors.put(ClientboundSoundEntityPacket.class, this.buildEntityPacketProcessor(ClientboundSoundEntityPacket::getId)); + // 7.1.116 Sound Effect: sound type and position. + processors.put(ClientboundSoundPacket.class, this.buildPositionBasedPacketProcessor(packet -> { + return new BlockPos.MutableBlockPos(packet.getX(), packet.getY(), packet.getZ()).immutable(); + })); + + // --- Others --- + // 7.1.137 Waypoint: world/entity tracking signal. + processors.put(ClientboundTrackedWaypointPacket.class, tossPacket); + // 7.1.50 Map Data: map pixels/icons can reveal player/world state. + processors.put(ClientboundMapItemDataPacket.class, tossPacket); + // 7.1.46 World Event: block position/event id can leak activity. + processors.put(ClientboundLevelEventPacket.class, buildPositionBasedPacketProcessor(ClientboundLevelEventPacket::getPos)); + // 7.1.59 Open Sign Editor: block position. + processors.put(ClientboundOpenSignEditorPacket.class, buildPositionBasedPacketProcessor(ClientboundOpenSignEditorPacket::getPos)); + // 7.1.37 Explosion: position, affected blocks, and knockback can reveal TNT/tech. + processors.put(ClientboundExplodePacket.class, (p, rawPacket) -> { + ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket; + Vec3 pos = packet.center(); + int blockX = (int) pos.x; + int blockY = (int) pos.y; + int blockZ = (int) pos.z; + + + return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet); + }); + + this.packetProcessors = processors; + + TinyProtocol.instance.addGlobalClientboundFilter((player, rawPacket) -> { + try { + if (rawPacket instanceof ClientboundBundlePacket bundlePacket) { + List> processedPackets = new ArrayList<>(); + + for (Packet packet : bundlePacket.subPackets()) { + Packet processedPacket = (Packet) processPacket(player, packet); + + if (processedPacket != null) { + processedPackets.add(processedPacket); + } + } + + return new ClientboundBundlePacket(processedPackets); + } else { + return processPacket(player, rawPacket); } - 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; + } catch (Throwable t) { + t.printStackTrace(); + return null; + } + }); + + TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null); + TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null); + } + + private Packet processPacket(Player player, Packet packet) { + if (bypassingPackets.contains(packet.getClass())) { + return packet; + } else if (packetProcessors.containsKey(packet.getClass())) { + return packetProcessors.get(packet.getClass()).apply(player, packet); + } else { + return null; } } - private static final Class blockActionPacket = ClientboundBlockEventPacket.class; - private static final Reflection.Field blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); + private Packet processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) { + return packet; + } else { + return null; + } + } - private Object blockActionHider(Player p, Object packet) { - if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) { + private Packet processEntityPacket(Player player, int entityId, Packet packet) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + return packet; + } else { + return null; + } + } + + private > BiFunction, Packet> buildEntityPacketProcessor(ToIntFunction entityIdExtractor) { + return (p, rawPacket) -> { + TTargetPacket packet = (TTargetPacket) rawPacket; + return processEntityPacket(p, entityIdExtractor.applyAsInt(packet), packet); + }; + } + + private final Reflection.Field moveEntityPacketEntityIdField = Reflection.getField(ClientboundMoveEntityPacket.class, int.class, 0); + + private Packet processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) { + int entityId = moveEntityPacketEntityIdField.get(packet); + + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + return packet; + } else { + return null; + } + } + + private final Reflection.Field rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0); + + private Packet processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) { + int entityId = rotateHeadPacketEntityIdField.get(packet); + + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + return packet; + } else { + return null; + } + } + + private Packet processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) { + IntList entityIdsToRemove = packet.getEntityIds(); + + IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, id)).collect(IntArrayList::new, IntList::add, IntList::addAll); + + return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove); + } + + public Packet processSetEntityLinkPacket(Player player, ClientboundSetEntityLinkPacket packet) { + int fromEntityId = packet.getSourceId(); + int toEntityId = packet.getDestId(); + + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, fromEntityId) && privilegeProvider.isPlayerPrivilegedToAccessEntity(player, toEntityId)) { + return packet; + } else { + return null; + } + } + + private Packet processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) { + BlockPos blockPos = packet.getPos(); + Block block = packet.getBlock(); + int blockX = blockPos.getX(); + int blockY = blockPos.getY(); + int blockZ = blockPos.getZ(); + + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) { + return packet; + } else { + return null; + } + } + + private Packet processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) { + BlockPos blockPos = packet.getPos(); + Block block = packet.getBlockState().getBlock(); + int blockX = blockPos.getX(); + int blockY = blockPos.getY(); + int blockZ = blockPos.getZ(); + + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) { + return packet; + } else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation); + } else { + return null; + } + } + + private Packet processBlockEntityDataPacket(Player player, ClientboundBlockEntityDataPacket packet) { + BlockPos blockPos = packet.getPos(); + BlockEntityType blockEntityType = packet.getType(); + int blockX = blockPos.getX(); + int blockY = blockPos.getY(); + int blockZ = blockPos.getZ(); + + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) { + return packet; + } else { + return null; + } + } + + private Packet proccessBlockDestructionPacket(Player player, ClientboundBlockDestructionPacket packet) { + BlockPos blockPos = packet.getPos(); + int entityBreakingBlockId = packet.getId(); + + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) { + return packet; + } else { + return null; + } + } + + private final Reflection.Field sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0); + private final Reflection.Field oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0); + private final Reflection.Field oldStatesField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, BlockState[].class, 0); + + private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) { + SectionPos sectionPos = sectionPosField.get(packet); + short[] oldPos = oldPosField.get(packet); + BlockState[] oldStates = oldStatesField.get(packet); + + 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]; + Block block = state.getBlock(); + + int worldX = sectionPos.relativeToBlockX(posShort); + int worldY = sectionPos.relativeToBlockY(posShort); + int worldZ = sectionPos.relativeToBlockZ(posShort); + + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block)) { + // TODO statefull !!! + filteredPos.add(posShort); + filteredStates.add(state); + } else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) { + modified = true; + filteredPos.add(posShort); + filteredStates.add(blockStateUsedForObfuscation); + } + } + + if (filteredStates.isEmpty()) { + return null; + } + if (!modified) { return packet; } - return null; + + 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, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates); } - public static final Class tileEntityDataPacket = ClientboundBlockEntityDataPacket.class; - private static final Reflection.Field tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); + private Packet processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) { + int blockX = (int) packet.getX(); + int blockY = (int) packet.getY(); + int blockZ = (int) packet.getZ(); - 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; + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + return packet; + } else { + return null; } } - public enum State { - SKIP, - CHECK, - HIDE, - HIDE_AIR + private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) { + if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) { + return packet; + } else { + return chunkHider.processLevelChunkWithLightPacket(p, packet); + } } - public interface LocationEvaluator { - default boolean suppressInteractions(Player player) { - return false; + private Packet proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet packet) { + if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + return packet; + } else { + return null; } + } - boolean skipChunk(Player player, int x, int z); + private > BiFunction, Packet> buildPositionBasedPacketProcessor(Function positionExtractor) { + return (p, rawPacket) -> { + TTargetPacket packet = (TTargetPacket) rawPacket; + BlockPos pos = positionExtractor.apply(packet); + int blockX = pos.getX(); + int blockY = pos.getY(); + int blockZ = pos.getZ(); - 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; - } + return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet); + }; } } 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..c3df8e6f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -0,0 +1,319 @@ +/* + * 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 de.steamwar.techhider.ProtocolUtils; +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; + +@Deprecated +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/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java similarity index 95% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolWrapper.java rename to SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java index 72469241..00e0dc31 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.techhider; +package de.steamwar.techhider.legacy; import de.steamwar.Reflection; import net.minecraft.core.SectionPos; @@ -29,6 +29,7 @@ import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.function.BiFunction; +@Deprecated public class ProtocolWrapper { public static final ProtocolWrapper impl = new ProtocolWrapper(); @@ -88,8 +89,4 @@ public class ProtocolWrapper { 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..25b3f215 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -0,0 +1,184 @@ +/* + * 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 de.steamwar.techhider.BlockIds; +import de.steamwar.techhider.ProtocolUtils; +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; + +@Deprecated +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((type, playerObjectBiFunction) -> TinyProtocol.instance.addFilter((Class) type, playerObjectBiFunction)); + } + + 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; + } + } +}