diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index 85ff16cc..5972ebba 100644 --- a/SpigotCore/SpigotCore_Main/build.gradle.kts +++ b/SpigotCore/SpigotCore_Main/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { compileOnly(libs.netty) compileOnly(libs.authlib) compileOnly(libs.fastutil) + compileOnly(libs.nms21) implementation(libs.anvilgui) } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java new file mode 100644 index 00000000..097de47d --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHiderUpdated.java @@ -0,0 +1,127 @@ +package de.steamwar.techhider; + +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import com.comphenix.tinyprotocol.TinyProtocol; + +import io.netty.channel.Channel; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ClientboundKeepAlivePacket; +import net.minecraft.network.protocol.common.ClientboundPingPacket; +import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundBlockEventPacket; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundBossEventPacket; +import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket; +import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; +import net.minecraft.network.protocol.game.ClientboundCommandsPacket; +import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundServerDataPacket; +import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; +import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket; +import net.minecraft.network.protocol.game.ClientboundSetHealthPacket; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTimePacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; +import net.minecraft.network.protocol.game.ClientboundTabListPacket; +import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket; +import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; +import net.minecraft.network.protocol.login.ClientboundHelloPacket; +import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket; +import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket; + +/** + * 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 TechHiderUpdated { + private final Set>> bypassingPackets; + private final Map>, BiFunction, Packet>> packetProcessors; + private final TinyProtocol interceptor; + + + public TechHiderUpdated(Plugin plugin) { + this.bypassingPackets = Set.of( + // --- Handshake & Login Protocol --- + // These must be whitelisted to allow the player to actually reach the 'Play' + // state + ClientboundHelloPacket.class, // Encryption request + ClientboundLoginFinishedPacket.class, // Login Success + ClientboundLoginCompressionPacket.class, // Set Compression + ClientboundCustomQueryPacket.class, // Velocity/Bungee/Mod identity checks + + // --- Protocol & Connection --- + ClientboundKeepAlivePacket.class, + ClientboundPingPacket.class, + ClientboundCustomPayloadPacket.class, + ClientboundServerDataPacket.class, + + // --- UI & Communication --- + ClientboundSystemChatPacket.class, + ClientboundDisguisedChatPacket.class, + ClientboundBossEventPacket.class, + ClientboundTabListPacket.class, + ClientboundSetTitleTextPacket.class, + ClientboundSetSubtitleTextPacket.class, + ClientboundSetTitlesAnimationPacket.class, + ClientboundClearTitlesPacket.class, + ClientboundPlayerChatPacket.class, + + // --- Player Stats & Logic --- + ClientboundSetExperiencePacket.class, + ClientboundSetHealthPacket.class, + ClientboundPlayerAbilitiesPacket.class, + ClientboundUpdateAttributesPacket.class, + ClientboundCommandSuggestionsPacket.class, + + // --- World Metadata (Non-Structural) --- + ClientboundSetTimePacket.class, + ClientboundSetDefaultSpawnPositionPacket.class, + ClientboundGameEventPacket.class, // Weather, Gamemode changes, etc. + ClientboundCommandsPacket.class // Command tree for tab-complete + ); + + BiFunction, Packet> tossPacket = (p, packet) -> null; + + this.packetProcessors = Map.of( + ClientboundBlockEventPacket.class, tossPacket, + ClientboundBlockUpdatePacket.class, tossPacket, + ClientboundBlockEntityDataPacket.class, tossPacket, + ClientboundSectionBlocksUpdatePacket.class, tossPacket, + ClientboundLevelChunkWithLightPacket.class, tossPacket + ); + + this.interceptor = new TinyProtocol(plugin) { + @Override + public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { + if (bypassingPackets.stream().anyMatch(clazz -> clazz.isInstance(packet))) { + return packet; + } + else if (packetProcessors.containsKey(packet.getClass())) { + return packetProcessors.get(packet.getClass()).apply(receiver, (Packet) packet); + } + else { + return null; + } + + } + }; + } +}