NoCheatPlus/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/UseEntityAdapter.java

198 lines
7.5 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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<EntityUseAction> 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));
}
}