diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java index 4820b072..4744e41d 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java @@ -38,6 +38,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index 3b02bffc..92a5c59e 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -39,6 +39,7 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.*; import org.bukkit.util.Vector; +import de.steamwar.techhider.legacy.TechHider; import java.util.HashSet; import java.util.Set; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java index 1ddfe6f7..55bcbeeb 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java @@ -40,6 +40,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; +import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.function.BiFunction; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index be822c7c..d3339975 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -110,6 +110,7 @@ public class FightSystem extends JavaPlugin { 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/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 3b4555c6..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,17 +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 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; @@ -54,8 +44,6 @@ 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 { @@ -79,10 +67,13 @@ public class HullHider implements Listener { 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) { 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 c840d84f..8e0f7f04 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -38,6 +38,7 @@ 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; @@ -60,7 +61,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { public TechHiderWrapper(HullHider hullHider) { - super(ENABLED, FightState.Schem); + super(ENABLED, FightState.All); Set blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream() .map(CraftMagicNumbers::getBlock) .collect(Collectors.toUnmodifiableSet()); @@ -91,6 +92,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { return !hiddenRegion.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); @@ -99,12 +101,12 @@ public class TechHiderWrapper extends StateDependent implements Listener { return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ()); } else { - return false; + return true; } } @Override - public boolean isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type) { + public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type) { Region hiddenRegion = getHiddenRegion(p); return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); } @@ -113,9 +115,14 @@ public class TechHiderWrapper extends StateDependent implements Listener { public boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) { return getHiddenRegions().stream().allMatch(region -> region.chunkOutside(chunkX, chunkZ)); } + + @Override + public boolean isPlayerPrivalegedToPerformAction(Player p) { + return !(p.getGameMode() == GameMode.SPECTATOR); + } }; - new StateDependentListener(ENABLED, FightState.Schem, this); + new StateDependentListener(ENABLED, FightState.All, this); register(); } diff --git a/FightSystem/build.gradle.kts b/FightSystem/build.gradle.kts index a9a84cbd..c6b14b8b 100644 --- a/FightSystem/build.gradle.kts +++ b/FightSystem/build.gradle.kts @@ -60,7 +60,7 @@ tasks.register("WarGear21") { 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/de/steamwar/techhider/AccessPrivilegeProvider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java new file mode 100644 index 00000000..1b34fe65 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -0,0 +1,32 @@ +/* + * 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 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 isPlayerPrivalegedToPerformAction(Player p); +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index 2c9bc074..6d9cdee8 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -38,7 +38,7 @@ import java.util.*; import java.util.function.UnaryOperator; -public abstract class ChunkHider { +public class ChunkHider { private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); @@ -54,12 +54,14 @@ public abstract class ChunkHider { private final int BLOCKS_PER_SECTION = 4096; private final int BIOMES_PER_SECTION = 64; - private final byte BITS_PER_LONG = 64; + private final byte BITS_PER_LONG = Long.SIZE; private final int blockIdUsedForHiding; + private final AccessPrivilegeProvider accessPrivilegeProvider; - public ChunkHider(Block blockUsedForObfuscation) { + public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) { blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState()); + this.accessPrivilegeProvider = accessPrivilegeProvider; } private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) { @@ -208,7 +210,7 @@ public abstract class ChunkHider { - if(isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) { + if(accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block)) { obfuscatedData[blockDataIndex] = blockId; } else { @@ -245,10 +247,6 @@ public abstract class ChunkHider { return reEncodedData.getRaw(); } - public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ); - public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block); - public abstract boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); - private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket); ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData()); @@ -278,7 +276,7 @@ public abstract class ChunkHider { int x = SectionPos.sectionRelativeX((short) packedXZ); int z = SectionPos.sectionRelativeZ((short) packedXZ); - return isPlayerPrivilegedToAccessPosition(player, x, y, z) && isPlayerPrivilegedToAccessBlockEntity(player, x, y, z, type); + return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, x, y, z) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, x, y, z, type); }).toList(); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 24cc88b6..00c6cd33 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -1,3 +1,22 @@ +/* + * 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 com.comphenix.tinyprotocol.TinyProtocol; @@ -7,7 +26,6 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.shorts.ShortArraySet; import it.unimi.dsi.fastutil.shorts.ShortSets; import net.minecraft.network.protocol.game.*; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.phys.Vec3; import org.bukkit.entity.Player; @@ -41,35 +59,20 @@ import java.util.function.ToIntFunction; * any packet must be explicitly whitelisted or processed in a * way that is also compliant with the principle of default-deny. */ -public abstract class TechHider { +public abstract class TechHider implements AccessPrivilegeProvider { private final Set> bypassingPackets; private final Map>, BiFunction, Packet>> packetProcessors; private final Block blockUsedForObfuscation; private final BlockState blockStateUsedForObfuscation; - private ChunkHider chunkHider; + private final ChunkHider chunkHider; // TODO handle packet bundle - public TechHider(Block blockUsedForObfuscation) { + protected TechHider(Block blockUsedForObfuscation) { this.blockUsedForObfuscation = blockUsedForObfuscation; this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState(); - this.chunkHider = new ChunkHider(blockUsedForObfuscation) { - @Override - public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) { - return TechHider.this.isPlayerPrivilegedToAccessBlock(p, blockX, blockY, blockZ, block); - } - - @Override - public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type) { - return TechHider.this.isPlayerPrivilegedToAccessBlocEntity(p, blockX, blockY, blockZ, type); - } - - @Override - public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) { - return TechHider.this.isPlayerPrivilegedToAccessPosition(p, blockX, blockY, blockZ); - } - }; + this.chunkHider = new ChunkHider(blockUsedForObfuscation, this); this.bypassingPackets = new HashSet<>(List.of( // --- 5.1.x Login Protocol --- @@ -190,6 +193,8 @@ public abstract class TechHider { ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in) ClientboundStopSoundPacket.class, // 7.1.118 Stop Sound: sound state side channel + ClientboundLightUpdatePacket.class, // 7.1.48 Update Light + ClientboundContainerSetContentPacket.class, // 7.1.19 Set Container Content ClientboundContainerSetDataPacket.class, // 7.1.20 Set Container Property ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot @@ -274,10 +279,6 @@ public abstract class TechHider { // 7.1.47 Particle: particle type and position can reveal hidden machinery. processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet)); - // --- Lighting packets --- - // 7.1.48 Update Light: lighting can reveal hidden blocks/operations. - processors.put(ClientboundLightUpdatePacket.class, tossPacket); - // --- Sound packets --- // 7.1.115 Entity Sound Effect: entity id and sound. processors.put(ClientboundSoundEntityPacket.class, this.buildEntityPacketProcessor(ClientboundSoundEntityPacket::getId)); @@ -330,6 +331,9 @@ public abstract class TechHider { return null; } }); + + TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (p, packet) -> isPlayerPrivalegedToPerformAction(p) ? packet : null); + TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (p, packet) -> isPlayerPrivalegedToPerformAction(p) ? packet : null); } private Packet processPacket(Player player, Packet packet) { @@ -449,7 +453,7 @@ public abstract class TechHider { int blockY = blockPos.getY(); int blockZ = blockPos.getZ(); - if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlocEntity(player, blockX, blockY, blockZ, blockEntityType)) { + if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) { return packet; } else { return null; @@ -565,6 +569,8 @@ public abstract class TechHider { public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ); public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block); public abstract boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId); - public abstract boolean isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); public abstract boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ); + public abstract boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); + public abstract boolean isPlayerPrivalegedToPerformAction(Player p); + } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java new file mode 100644 index 00000000..0d10088f --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/BlockIds.java @@ -0,0 +1,68 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import net.minecraft.core.IdMapper; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.util.HashSet; +import java.util.Set; + +public class BlockIds { + public static final BlockIds impl = new BlockIds(); + + public int materialToId(Material material) { + return getCombinedId(getBlock(material).defaultBlockState()); + } + + private static final FluidState water = Fluids.WATER.getSource(false); + private static final Iterable registryBlockId = (Iterable) Reflection.getField(TechHider.block, IdMapper.class, 0).get(null); + + public Set materialToAllIds(Material material) { + Set ids = new HashSet<>(); + for (BlockState data : getBlock(material).getStateDefinition().getPossibleStates()) { + ids.add(getCombinedId(data)); + } + + if (material == Material.WATER) { + for (BlockState data : registryBlockId) { + if (data.getFluidState() == water) { + ids.add(getCombinedId(data)); + } + } + } + + return ids; + } + + private Block getBlock(Material material) { + return CraftMagicNumbers.getBlock(material); + } + + public int getCombinedId(BlockState blockData) { + return Block.getId(blockData); + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java new file mode 100644 index 00000000..9394d1b0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -0,0 +1,317 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.SimpleBitStorage; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class ChunkHider { + public static final ChunkHider impl = new ChunkHider(); + + public Class mapChunkPacket() { + return ClientboundLevelChunkWithLightPacket.class; + } + + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); + + private static final Reflection.Field chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); + private static final Reflection.Field chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1); + private static final Reflection.Field chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); + + private static final Reflection.Field dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); + private static final Reflection.Field tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + + public BiFunction chunkHiderGenerator(TechHider techHider) { + return (p, packet) -> { + int chunkX = chunkXField.get(packet); + int chunkZ = chunkZField.get(packet); + if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) { + return packet; + } + + packet = chunkPacketCloner.apply(packet); + Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + + Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); + tileEntities.set(dataWrapper, ((List) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + + ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper)); + ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); + for (int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { + SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset / 16, chunkZ); + section.copyBlockCount(); + + blocks(section); + biomes(section); + } + + if (in.readableBytes() != 0) { + throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left"); + } + + byte[] data = new byte[out.readableBytes()]; + out.readBytes(data); + dataField.set(dataWrapper, data); + + chunkData.set(packet, dataWrapper); + return packet; + }; + } + + private static final Registry> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); + private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); + public static final Class tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); + protected static final Reflection.Field entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0); + + protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { + BlockEntityType type = entityType.get(tile); + String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath(); + return !hiddenBlockEntities.contains(path); + } + + private void blocks(SectionHider section) { + section.copyBitsPerBlock(); + + boolean singleValued = section.getBitsPerBlock() == 0; + if (singleValued) { + int value = ProtocolUtils.readVarInt(section.getIn()); + ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value); + return; + } else if (section.getBitsPerBlock() < 9) { + // Indirect (paletted) storage – only present when bitsPerBlock < 9 in 1.21+ + section.processPalette(); + } + + if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) { + section.skipNewDataArray(4096); + return; + } + + SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); + + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + int pos = (((y * 16) + z) * 16) + x; + + TechHider.State test = section.test(x, y, z); + + switch (test) { + case SKIP: + break; + case CHECK: + if (!section.getObfuscate().contains(values.get(pos))) { + break; + } + case HIDE: + values.set(pos, section.getTarget()); + break; + case HIDE_AIR: + default: + values.set(pos, section.getAir()); + } + } + } + } + + section.writeDataArray(values.getRaw()); + } + + private void biomes(SectionHider section) { + section.copyBitsPerBlock(); + if (section.getBitsPerBlock() == 0) { + section.copyVarInt(); + } else if (section.getBitsPerBlock() < 6) { + section.skipPalette(); + section.skipNewDataArray(64); + } else { + // Direct (global) biome IDs – no palette present + section.skipNewDataArray(64); + } + } + + @Getter + class SectionHider { + private final Player player; + private final TechHider techHider; + private final ByteBuf in; + private final ByteBuf out; + + private final int chunkX; + private final int chunkY; + private final int chunkZ; + private final int offsetX; + private final int offsetY; + private final int offsetZ; + + private final boolean skipSection; + + private boolean paletted; + private int bitsPerBlock; + private int blockCount; + private int air; + private int target; + private Set obfuscate; + + public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) { + this.player = player; + this.techHider = techHider; + this.in = in; + this.out = out; + this.chunkX = chunkX; + this.chunkY = chunkY; + this.chunkZ = chunkZ; + this.offsetX = 16 * chunkX; + this.offsetY = 16 * chunkY; + this.offsetZ = 16 * chunkZ; + this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ); + + this.paletted = false; + this.bitsPerBlock = 0; + this.air = TechHider.AIR_ID; + this.target = techHider.getObfuscationTargetId(); + this.obfuscate = techHider.getObfuscateIds(); + } + + public boolean blockPrecise() { + return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ); + } + + public TechHider.State test(int x, int y, int z) { + return techHider.getLocationEvaluator().check(player, offsetX + x, offsetY + y, offsetZ + z); + } + + public void copyBlockCount() { + this.blockCount = in.readShort(); + out.writeShort(blockCount); + } + + public void copyBitsPerBlock() { + bitsPerBlock = in.readByte(); + out.writeByte(bitsPerBlock); + } + + public int copyVarInt() { + int value = ProtocolUtils.readVarInt(in); + ProtocolUtils.writeVarInt(out, value); + return value; + } + + public void skipPalette() { + int paletteLength = copyVarInt(); + for (int i = 0; i < paletteLength; i++) { + copyVarInt(); + } + } + + public void processPalette() { + if (skipSection) { + skipPalette(); + return; + } + + int paletteLength = copyVarInt(); + if (paletteLength == 0) return; + + paletted = true; + air = 0; + target = 0; + + for (int i = 0; i < paletteLength; i++) { + int entry = ProtocolUtils.readVarInt(in); + if (obfuscate.contains(entry)) { + entry = techHider.getObfuscationTargetId(); + } + + if (entry == TechHider.AIR_ID) { + air = i; + } else if (entry == techHider.getObfuscationTargetId()) { + target = i; + } + + ProtocolUtils.writeVarInt(out, entry); + } + obfuscate = Collections.emptySet(); + } + + public void skipDataArray() { + int dataArrayLength = copyVarInt(); + out.writeBytes(in, dataArrayLength * 8); + } + + public void skipNewDataArray(int entries) { + if (bitsPerBlock == 0) { + return; + } + + char valuesPerLong = (char) (64 / bitsPerBlock); + int i1 = (entries + valuesPerLong - 1) / valuesPerLong; + out.writeBytes(in, i1 * Long.BYTES); + } + + public long[] readDataArray() { + long[] array = new long[copyVarInt()]; + for (int i = 0; i < array.length; i++) { + array[i] = in.readLong(); + } + + return array; + } + + public long[] readNewDataArray(int entries) { + if (bitsPerBlock == 0) { + return new long[entries]; + } + + char valuesPerLong = (char) (64 / bitsPerBlock); + int i1 = (entries + valuesPerLong - 1) / valuesPerLong; + long[] array = new long[i1]; + for (int i = 0; i < i1; i++) { + array[i] = in.readLong(); + } + + return array; + } + + public void writeDataArray(long[] array) { + for (long l : array) { + out.writeLong(l); + } + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java new file mode 100644 index 00000000..5e9a7c78 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolUtils.java @@ -0,0 +1,203 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import com.google.common.primitives.Bytes; +import de.steamwar.Reflection; +import io.netty.buffer.ByteBuf; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public class ProtocolUtils { + private ProtocolUtils() { + } + + @Deprecated + public static BiFunction, Object> arrayCloneGenerator(Class elementClass) { + return (array, worker) -> { + int length = Array.getLength(array); + Object result = Array.newInstance(elementClass, length); + + for (int i = 0; i < length; i++) { + Array.set(result, i, worker.apply(Array.get(array, i))); + } + + return result; + }; + } + + public static UnaryOperator shallowCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); + + return source -> { + Object clone = Reflection.newInstance(clazz); + filler.accept(source, clone); + return clone; + }; + } + + public static UnaryOperator shallowTypedCloneGenerator(Class clazz) { + BiConsumer filler = shallowFill(clazz); + + return source -> { + Object clone = Reflection.newInstance(clazz); + filler.accept(source, clone); + return (T) clone; + }; + } + + private static BiConsumer shallowFill(Class clazz) { + if (clazz == null) { + return (source, clone) -> { + }; + } + + BiConsumer superFiller = shallowFill(clazz.getSuperclass()); + + Field[] fds = clazz.getDeclaredFields(); + List fields = new ArrayList<>(); + for (Field field : fds) { + if (Modifier.isStatic(field.getModifiers())) continue; + + field.setAccessible(true); + fields.add(field); + } + + return (source, clone) -> { + superFiller.accept(source, clone); + try { + for (Field field : fields) { + field.set(clone, field.get(source)); + } + } catch (IllegalAccessException e) { + throw new IllegalStateException("Could not set field", e); + } + + }; + } + + public static int posToChunk(int c) { + int chunk = c / 16; + if (c < 0) chunk--; + return chunk; + } + + @Deprecated + public static int readVarInt(byte[] array, int startPos) { + int numRead = 0; + int result = 0; + byte read; + do { + read = array[startPos + numRead]; + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return result; + } + + public static int readVarInt(ByteBuf buf) { + int numRead = 0; + int result = 0; + byte read; + do { + read = buf.readByte(); + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + if (++numRead > 5) throw new SecurityException("VarInt too long"); + } while ((read & 0b10000000) != 0); + + return result; + } + + public static void writeVarInt(ByteBuf buf, int value) { + do { + int temp = value & 0b01111111; + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buf.writeByte(temp); + } while (value != 0); + } + + @Deprecated + public static int readVarIntLength(byte[] array, int startPos) { + int numRead = 0; + byte read; + do { + read = array[startPos + numRead]; + numRead++; + if (numRead > 5) { + break; + } + } while ((read & 0b10000000) != 0); + + return numRead; + } + + @Deprecated + public static byte[] writeVarInt(int value) { + List buffer = new ArrayList<>(5); + do { + byte temp = (byte) (value & 0b01111111); + // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buffer.add(temp); + } while (value != 0); + return Bytes.toArray(buffer); + } + + @Deprecated + public static class ChunkPos { + final int x; + final int z; + + public ChunkPos(int x, int z) { + this.x = x; + this.z = z; + } + + public final int x() { + return x; + } + + public final int z() { + return z; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java new file mode 100644 index 00000000..9b6ac216 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java @@ -0,0 +1,95 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import de.steamwar.Reflection; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.function.BiFunction; + +public class ProtocolWrapper { + public static final ProtocolWrapper impl = new ProtocolWrapper(); + + private static final Reflection.Field multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0); + private static final Reflection.Field multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0); + private static final Reflection.Field multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0); + + public BiFunction multiBlockChangeGenerator(TechHider techHider) { + return (p, packet) -> { + TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator(); + Object chunkCoords = multiBlockChangeChunk.get(packet); + int chunkX = TechHider.blockPositionX.get(chunkCoords); + int chunkY = TechHider.blockPositionY.get(chunkCoords); + int chunkZ = TechHider.blockPositionZ.get(chunkCoords); + if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) { + return packet; + } + + packet = TechHider.multiBlockChangeCloner.apply(packet); + final short[] oldPos = multiBlockChangePos.get(packet); + final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet); + ArrayList poss = new ArrayList<>(oldPos.length); + ArrayList blocks = new ArrayList<>(oldPos.length); + for (int i = 0; i < oldPos.length; i++) { + short pos = oldPos[i]; + BlockState block = oldBlocks[i]; + switch (locationEvaluator.check(p, 16 * chunkX + (pos >> 8 & 0xF), 16 * chunkY + (pos & 0xF), 16 * chunkZ + (pos >> 4 & 0xF))) { + case SKIP: + poss.add(pos); + blocks.add(block); + break; + case CHECK: + poss.add(pos); + blocks.add(techHider.iBlockDataHidden(block) ? (BlockState) techHider.getObfuscationTarget() : block); + break; + default: + break; + } + } + + if (blocks.isEmpty()) return null; + + short[] newPos = new short[poss.size()]; + for (int i = 0; i < newPos.length; i++) { + newPos[i] = poss.get(i); + } + + multiBlockChangePos.set(packet, newPos); + multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0])); + return packet; + }; + } + + private static final Reflection.Field tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, BlockEntityType.class, 0); + private static final BlockEntityType signType = Reflection.getField(BlockEntityType.class, BlockEntityType.class, 0, SignBlockEntity.class).get(null); + + public boolean unfilteredTileEntityDataAction(Object packet) { + return tileEntityType.get(packet) != signType; + } + + public BiFunction blockBreakHiderGenerator(Class blockBreakPacket, TechHider techHider) { + return null; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java new file mode 100644 index 00000000..006b50d0 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -0,0 +1,181 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.techhider.legacy; + +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.Reflection; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.network.protocol.game.*; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class TechHider { + + public static final Class blockPosition = BlockPos.class; + private static final Class baseBlockPosition = Vec3i.class; + public static final Reflection.Field blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0); + public static final Reflection.Field blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1); + public static final Reflection.Field blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2); + + public static final Class iBlockData = BlockState.class; + public static final Class block = Block.class; + + public boolean iBlockDataHidden(BlockState iBlockData) { + return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData)); + } + + public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState(); + public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR); + + private final Map, BiFunction> techhiders = new HashMap<>(); + @Getter + private final LocationEvaluator locationEvaluator; + @Getter + private final Object obfuscationTarget; + @Getter + private final int obfuscationTargetId; + @Getter + private final Set obfuscateIds; + @Getter + private final Set hiddenBlockEntities; + + public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { + this.locationEvaluator = locationEvaluator; + this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); + this.hiddenBlockEntities = hiddenBlockEntities; + this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); + this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); + + techhiders.put(blockActionPacket, this::blockActionHider); + techhiders.put(blockChangePacket, this::blockChangeHider); + techhiders.put(tileEntityDataPacket, this::tileEntityDataHider); + techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this)); + techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this)); + techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); + techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); + + } + + public void enable() { + techhiders.forEach(TinyProtocol.instance::addFilter); + } + + public void disable() { + techhiders.forEach(TinyProtocol.instance::removeFilter); + } + + public static final Class multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + + private static final Class blockChangePacket = ClientboundBlockUpdatePacket.class; + private static final Function blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); + private static final Reflection.Field blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0); + private static final Reflection.Field blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0); + + private Object blockChangeHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) { + return packet; + } + case HIDE: + packet = blockChangeCloner.apply(packet); + blockChangeBlockData.set(packet, obfuscationTarget); + return packet; + case HIDE_AIR: + default: + packet = blockChangeCloner.apply(packet); + blockChangeBlockData.set(packet, AIR); + return packet; + } + } + + private static final Class blockActionPacket = ClientboundBlockEventPacket.class; + private static final Reflection.Field blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); + + private Object blockActionHider(Player p, Object packet) { + if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) { + return packet; + } + return null; + } + + public static final Class tileEntityDataPacket = ClientboundBlockEntityDataPacket.class; + private static final Reflection.Field tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); + + private Object tileEntityDataHider(Player p, Object packet) { + switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) { + case SKIP: + return packet; + case CHECK: + if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) { + return packet; + } + default: + return null; + } + } + + public enum State { + SKIP, + CHECK, + HIDE, + HIDE_AIR + } + + public interface LocationEvaluator { + default boolean suppressInteractions(Player player) { + return false; + } + + boolean skipChunk(Player player, int x, int z); + + default boolean skipChunkSection(Player player, int x, int y, int z) { + return skipChunk(player, x, z); + } + + default State check(Player player, int x, int y, int z) { + return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; + } + + default State checkBlockPos(Player player, Object pos) { + return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos)); + } + + default boolean blockPrecise(Player player, int x, int y, int z) { + return false; + } + } +}