/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package fr.neatmonster.nocheatplus.checks.net.protocollib; import java.lang.reflect.Method; import java.util.Arrays; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction; import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.net.AttackFrequency; import fr.neatmonster.nocheatplus.checks.net.NetConfig; import fr.neatmonster.nocheatplus.checks.net.NetData; import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.utilities.ReflectionUtil; public class UseEntityAdapter extends BaseAdapter { private static class LegacyReflectionSet { /** Hacks. */ final Class packetClass_legacy; final Class enumClassAction_legacy; final Method methodGetAction_legacy; final Method methodName_legacy; /** * * @param versionDetail * @throws RuntimeException * if not matching/supported. */ private LegacyReflectionSet(String versionDetail) { Class packetClass = ReflectionUtil.getClass("net.minecraft.server." + versionDetail + ".PacketPlayInUseEntity"); Class actionClass = ReflectionUtil.getClass("net.minecraft.server." + versionDetail + ".EnumEntityUseAction"); Method methodGetAction = (packetClass == null || actionClass == null) ? null : ReflectionUtil.getMethodNoArgs(packetClass, "c", actionClass); if (packetClass == null || actionClass == null || methodGetAction == null) { this.packetClass_legacy = null; this.enumClassAction_legacy = null; this.methodGetAction_legacy = null; this.methodName_legacy = null; } else { this.packetClass_legacy = packetClass; this.enumClassAction_legacy = actionClass; this.methodGetAction_legacy = methodGetAction; this.methodName_legacy = ReflectionUtil.getMethodNoArgs(enumClassAction_legacy, "name", String.class); } if (this.methodName_legacy == null) { throw new RuntimeException("Not supported."); } } String getActionFromNMSPacket(Object handle) { final Class clazz = handle.getClass(); if (clazz != packetClass_legacy) { return null; } final Object action = ReflectionUtil.invokeMethodNoArgs(methodGetAction_legacy, handle); if (action == null) { return null; } final Object actionName = ReflectionUtil.invokeMethodNoArgs(methodName_legacy, action); if (actionName instanceof String) { return (String) actionName; } else { return null; } } } private static final int INTERPRETED = 0x01; private static final int ATTACK = 0x02; private final AttackFrequency attackFrequency; private final LegacyReflectionSet legacySet; public UseEntityAdapter(Plugin plugin) { super(plugin, PacketType.Play.Client.USE_ENTITY); this.checkType = CheckType.NET_ATTACKFREQUENCY; // Add feature tags for checks. if (NCPAPIProvider.getNoCheatPlusAPI().getWorldDataManager().isActiveAnywhere( CheckType.NET_ATTACKFREQUENCY)) { NCPAPIProvider.getNoCheatPlusAPI().addFeatureTags( "checks", Arrays.asList(AttackFrequency.class.getSimpleName())); } attackFrequency = new AttackFrequency(); LegacyReflectionSet set = null; for (String versionDetail : new String[] {"v1_7_R4", "v1_7_R1"}) { try { set = new LegacyReflectionSet(versionDetail); if (set != null) { break; } } catch (RuntimeException e) {} // +- } this.legacySet = set; NCPAPIProvider.getNoCheatPlusAPI().addComponent(attackFrequency); } @Override public void onPacketReceiving(final PacketEvent event) { final long time = System.currentTimeMillis(); final Player player = event.getPlayer(); if (player == null) { // TODO: Warn once? return; } final IPlayerData pData = DataManager.getPlayerData(player); final NetData data = pData.getGenericInstance(NetData.class); // Always set last received time. data.lastKeepAliveTime = time; // Quick return, if no checks are active. if (!pData.isCheckActive(CheckType.NET_ATTACKFREQUENCY, player)) { return; } final PacketContainer packet = event.getPacket(); // MIGHT: use entity, use block both on packet level? boolean isAttack = false; boolean packetInterpreted = false; if (legacySet != null) { // Attempt to extract legacy information. final int flags = getAction_legacy(packet); if ((flags & INTERPRETED) != 0 ) { packetInterpreted = true; if ((flags & ATTACK) != 0) { isAttack = true; } } } if (!packetInterpreted) { // Handle as if latest. final StructureModifier actions; actions = packet.getEntityUseActions(); if (actions.size() == 1 && actions.read(0) == EntityUseAction.ATTACK) { isAttack = true; packetInterpreted = true; } } if (!packetInterpreted) { // TODO: Log warning once, if the packet could not be interpreted. return; } // Run checks. boolean cancel = false; // AttackFrequency if (isAttack) { final NetConfig cc = pData.getGenericInstance(NetConfig.class); if (attackFrequency.isEnabled(player, pData) && attackFrequency.check(player, time, data, cc, pData)) { cancel = true; } } if (cancel) { event.setCancelled(true); } } private int getAction_legacy(final PacketContainer packetContainer) { // (For some reason the object didn't appear work with equality checks, thus compare the short string.) final String actionName = legacySet.getActionFromNMSPacket(packetContainer.getHandle()); return actionName == null ? 0 : (INTERPRETED | ("ATTACK".equals(actionName) ? ATTACK : 0)); } }