diff --git a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/FlyingFrequency.java b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/FlyingFrequency.java index c21745b7..3fcd5fe1 100644 --- a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/FlyingFrequency.java +++ b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/FlyingFrequency.java @@ -1,5 +1,6 @@ package fr.neatmonster.nocheatplus.net.protocollib; +import java.util.List; import java.util.Map; import org.bukkit.entity.Player; @@ -7,13 +8,13 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.components.JoinLeaveListener; -import fr.neatmonster.nocheatplus.config.ConfPaths; -import fr.neatmonster.nocheatplus.config.ConfigFile; -import fr.neatmonster.nocheatplus.config.ConfigManager; +import fr.neatmonster.nocheatplus.net.NetConfig; +import fr.neatmonster.nocheatplus.net.NetConfigCache; import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.utilities.ActionFrequency; import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW; @@ -27,30 +28,44 @@ import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW; */ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener { - // TODO: Silent cancel count. - // TODO: Configuration. - // TODO: Optimized options (receive only, other?). - // TODO: Forced async version ? + // TODO: Most efficient registration + optimize (primary thread or asynchronous). - private final Map freqMap = new LinkedHashMapCOW(); + private class FFData { + public static final int numBooleans = 3; + public static final int indexOnGround = 0; + public static final int indexhasPos = 1; + public static final int indexhasLook = 2; + + public final ActionFrequency all; + // Last move. + public final double[] doubles = new double[3]; // x, y, z + public final float[] floats = new float[2]; // yaw, pitch + //public final boolean[] booleans = new boolean[3]; // ground, hasPos, hasLook + public boolean onGround = false; + public FFData(int seconds) { + all = new ActionFrequency(seconds, 1000L); + } + } + + private final Map freqMap = new LinkedHashMapCOW(); private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class); private final int idSilent = counters.registerKey("packet.flying.silentcancel"); + private final int idRedundant = counters.registerKey("packet.flying.silentcancel.redundant"); private final int idNullPlayer = counters.registerKey("packet.flying.nullplayer"); - private final int seconds; - private final float maxPackets; + private boolean cancelRedundant = true; - public FlyingFrequency(Plugin plugin) { + private final NetConfigCache configs; + + public FlyingFrequency(NetConfigCache configs, Plugin plugin) { // PacketPlayInFlying[3, legacy: 10] super(plugin, PacketType.Play.Client.FLYING); // TODO: How does POS and POS_LOOK relate/translate? - ConfigFile config = ConfigManager.getConfigFile(); - seconds = Math.max(1, config.getInt(ConfPaths.NET_FLYINGFREQUENCY_SECONDS)); - maxPackets = Math.max(1, config.getInt(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS)); + this.configs = configs; } @Override public void playerJoins(Player player) { - getFreq(player.getName()); + // Ignore. } @Override @@ -58,12 +73,12 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener freqMap.remove(player.getName()); } - private ActionFrequency getFreq(final String name) { - final ActionFrequency freq = this.freqMap.get(name); + private FFData getFreq(final String name, final NetConfig cc) { + final FFData freq = this.freqMap.get(name); if (freq != null) { return freq; } else { - final ActionFrequency newFreq = new ActionFrequency(seconds, 1000); + final FFData newFreq = new FFData(cc.flyingFrequencySeconds); this.freqMap.put(name, newFreq); return newFreq; } @@ -71,40 +86,84 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener @Override public void onPacketReceiving(final PacketEvent event) { + final Player player = event.getPlayer(); if (player == null) { - if (onNullPlayer(event)) { - event.setCancelled(true); - } + // TODO: Need config? + counters.add(idNullPlayer, 1); + event.setCancelled(true); return; } - // TODO: Consider using similar heuristic as CB does for when to count. - // TODO: Consider detecting "untracked moves" early. - // TODO: Add more counters/cases (at least has look + has pos individually, maybe none/onground) - final ActionFrequency freq = getFreq(player.getName()); - freq.add(System.currentTimeMillis(), 1f); - if (freq.score(1f) > maxPackets) { - if (onViolation(player)) { + + final NetConfig cc = configs.getConfig(player.getWorld()); + if (!cc.flyingFrequencyActive) { + return; + } + + final FFData freq = getFreq(player.getName(), cc); + final long t = System.currentTimeMillis(); + // Counting all packets. + freq.all.add(t, 1f); + final float allScore = freq.all.score(1f); + if (allScore > cc.flyingFrequencyMaxPackets) { + counters.add(idSilent, 1); // Until it is sure if we can get these async. + event.setCancelled(true); + return; + } + + // Cancel redundant packets, when frequency is high anyway. + if (!cancelRedundant || !cc.flyingFrequencyCancelRedundant) { + return; + } + // TODO: Consider to detect if a moving event would fire (...). + boolean redundant = true; + final PacketContainer packet = event.getPacket(); + final List booleans = packet.getBooleans().getValues(); + if (booleans.size() != FFData.numBooleans) { + cancelRedundant = false; + return; + } + final boolean onGround = booleans.get(FFData.indexOnGround).booleanValue(); + if (onGround != freq.onGround) { + redundant = false; + } + freq.onGround = onGround; + // TODO: Consider to verify on ground somehow. + if (booleans.get(FFData.indexhasPos)) { + final List doubles = packet.getDoubles().getValues(); + if (doubles.size() != freq.doubles.length) { + cancelRedundant = false; + return; + } + for (int i = 0; i < freq.doubles.length; i++) { + final double val = doubles.get(i).doubleValue(); + if (val != freq.doubles[i]) { + redundant = false; + freq.doubles[i] = val; + } + } + } + if (booleans.get(FFData.indexhasLook)) { + final List floats = packet.getFloat().getValues(); + if (floats.size() != freq.floats.length) { + cancelRedundant = false; + return; + } + for (int i = 0; i < freq.floats.length; i++) { + final float val = floats.get(i).floatValue(); + if (val != freq.floats[i]) { + redundant = false; + freq.floats[i] = val; + } + } + } + if (redundant) { + // TODO: Could check first bucket or even just 50 ms to last packet. + if (allScore / cc.flyingFrequencySeconds > 20f) { + counters.add(idRedundant, 1); event.setCancelled(true); } } } - private boolean onViolation(final Player player) { - // TODO: Get from a NetConfig (optimized). - if (ConfigManager.getConfigFile(player.getWorld().getName()).getBoolean(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE)) { - counters.add(idSilent, 1); // Until it is sure if we can get these async. - return true; - } - else { - return false; - } - } - - private boolean onNullPlayer(final PacketEvent event) { - // TODO: Config (global?) ? - counters.add(idNullPlayer, 1); - return true; - } - } diff --git a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/ProtocolLibComponent.java b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/ProtocolLibComponent.java index 36256214..ae90d04c 100644 --- a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/ProtocolLibComponent.java +++ b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/ProtocolLibComponent.java @@ -18,6 +18,7 @@ import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigManager; import fr.neatmonster.nocheatplus.logging.StaticLog; +import fr.neatmonster.nocheatplus.net.NetConfigCache; import fr.neatmonster.nocheatplus.utilities.StringUtil; /** @@ -25,8 +26,9 @@ import fr.neatmonster.nocheatplus.utilities.StringUtil; * @author dev1mc * */ -public class ProtocolLibComponent implements DisableListener, INotifyReload{ +public class ProtocolLibComponent implements DisableListener, INotifyReload { + private final NetConfigCache configs = new NetConfigCache(); private final List registeredPacketAdapters = new LinkedList(); public ProtocolLibComponent(Plugin plugin) { @@ -34,7 +36,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{ register(plugin); } - private void register(Plugin plugin) { + private void register(Plugin plugin) { // Register Classes having a constructor with Plugin as argument. if (ConfigManager.isTrueForAnyConfig(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE)) { register(FlyingFrequency.class, plugin); @@ -55,7 +57,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{ private void register(Class clazz, Plugin plugin) { try { // Construct a new instance using reflection. - PacketAdapter adapter = clazz.getDeclaredConstructor(Plugin.class).newInstance(plugin); + PacketAdapter adapter = clazz.getDeclaredConstructor(Plugin.class).newInstance(configs, plugin); ProtocolLibrary.getProtocolManager().addPacketListener(adapter); registeredPacketAdapters.add(adapter); } catch (Throwable t) { @@ -87,6 +89,7 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload{ } } registeredPacketAdapters.clear(); + configs.clearAllConfigs(); } } diff --git a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/SoundDistance.java b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/SoundDistance.java index 86edd978..3929008b 100644 --- a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/SoundDistance.java +++ b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/net/protocollib/SoundDistance.java @@ -10,9 +10,8 @@ import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.StructureModifier; -import fr.neatmonster.nocheatplus.config.ConfPaths; -import fr.neatmonster.nocheatplus.config.ConfigFile; -import fr.neatmonster.nocheatplus.config.ConfigManager; +import fr.neatmonster.nocheatplus.net.NetConfig; +import fr.neatmonster.nocheatplus.net.NetConfigCache; import fr.neatmonster.nocheatplus.utilities.TrigUtil; public class SoundDistance extends PacketAdapter { @@ -36,14 +35,12 @@ public class SoundDistance extends PacketAdapter { return false; } - /** Maximum distance for thunder effects (squared). */ - private final double distSq; + private final NetConfigCache configs; + private final Location useLoc = new Location(null, 0, 0, 0); - public SoundDistance(Plugin plugin) { + public SoundDistance(NetConfigCache configs, Plugin plugin) { super(plugin, PacketType.Play.Server.NAMED_SOUND_EFFECT); - ConfigFile config = ConfigManager.getConfigFile(); - double dist = config.getDouble(ConfPaths.NET_SOUNDDISTANCE_MAXDISTANCE); - distSq = dist * dist; + this.configs = configs; } @Override @@ -56,16 +53,18 @@ public class SoundDistance extends PacketAdapter { } final Player player = event.getPlayer(); - final Location loc = player.getLocation(); // TODO: Use getLocation(useLoc) [synced if async]. + final NetConfig cc = configs.getConfig(player.getWorld()); + if (!cc.soundDistanceActive) { + return; + } + final Location loc = player.getLocation(useLoc); // Compare distance of player to the weather location. final StructureModifier ints = packetContainer.getIntegers(); - if (TrigUtil.distanceSquared(ints.read(0) / 8, ints.read(2) / 8, loc.getX(), loc.getZ()) > distSq) { - // TODO: Get from a NetConfig (optimized). - if (ConfigManager.getConfigFile(player.getWorld().getName()).getBoolean(ConfPaths.NET_SOUNDDISTANCE_ACTIVE)) { - event.setCancelled(true); - } + if (TrigUtil.distanceSquared(ints.read(0) / 8, ints.read(2) / 8, loc.getX(), loc.getZ()) > cc.soundDistanceSq) { + event.setCancelled(true); } + useLoc.setWorld(null); } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java index 53624966..7bad6f7b 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -598,7 +598,6 @@ public abstract class ConfPaths { private static final String NET_SOUNDDISTANCE = NET + "sounddistance."; public static final String NET_SOUNDDISTANCE_ACTIVE = NET_SOUNDDISTANCE + "active"; - @GlobalConfig public static final String NET_SOUNDDISTANCE_MAXDISTANCE = NET_SOUNDDISTANCE + "maxdistance"; private static final String NET_FLYINGFREQUENCY = NET + "flyingfrequency."; @@ -607,6 +606,7 @@ public abstract class ConfPaths { public static final String NET_FLYINGFREQUENCY_SECONDS = NET_FLYINGFREQUENCY + "seconds"; @GlobalConfig public static final String NET_FLYINGFREQUENCY_MAXPACKETS = NET_FLYINGFREQUENCY + "maxpackets"; + public static final String NET_FLYINGFREQUENCY_CANCELREDUNDANT = NET_FLYINGFREQUENCY + "cancelredundant"; public static final String STRINGS = "strings"; diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfigCache.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfigCache.java new file mode 100644 index 00000000..6409efad --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfigCache.java @@ -0,0 +1,79 @@ +package fr.neatmonster.nocheatplus.config; + +import java.util.HashMap; +import java.util.Map; + +import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW; + +/** + * Simple cache for configurations, adding some convenience functionality. + * @author web4web1 + * + */ +public abstract class ConfigCache { + + private final Map configs; + private final boolean cow; + + /** + * + * @param cow Set to true, to use copy-on-write and synchronize writing. + * @param initialCapacity + */ + public ConfigCache(boolean cow, int initialCapacity) { + this.cow = cow; + // Linked or not linked ? + if (cow) { + configs = new LinkedHashMapCOW(initialCapacity); + } else { + configs = new HashMap(initialCapacity); + } + } + + public boolean hasConfig(final K key) { + return configs.containsKey(key); + } + + public C getConfig(final K key) { + final C config = configs.get(key); + if (config == null) { + return createConfig(key); + } else { + return config; + } + } + + private C createConfig(final K key) { + final C config = newConfig(key); + if (!cow) { + configs.put(key, config); + return config; + } else { + return addConfigCOW(key, config); + } + } + + private C addConfigCOW(final K key, final C config) { + // Re-check to ensure FCFS. + synchronized (configs) { + if (configs.containsKey(key)) { + return configs.get(key); + } else { + configs.put(key, config); + return config; + } + } + } + + public void clearAllConfigs() { + configs.clear(); + } + + /** + * Factory method. In case of cow, not all returned instances might be used. + * @param key + * @return + */ + protected abstract C newConfig(final K key); + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java index 1e3d0e1f..93e287f7 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -435,6 +435,7 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE, true); set(ConfPaths.NET_FLYINGFREQUENCY_SECONDS, 5); set(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS, 300); + set(ConfPaths.NET_FLYINGFREQUENCY_CANCELREDUNDANT, true); // SoundDistance set(ConfPaths.NET_SOUNDDISTANCE_ACTIVE, true); diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/WorldConfigCache.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/WorldConfigCache.java new file mode 100644 index 00000000..61b24be2 --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/WorldConfigCache.java @@ -0,0 +1,32 @@ +package fr.neatmonster.nocheatplus.config; + +import org.bukkit.World; + +/** + * World-specific cache for configurations. World names are used as keys, convenience methods are added. + * @author web4web1 + * + * @param + */ +public abstract class WorldConfigCache extends ConfigCache { + + public WorldConfigCache(boolean cow) { + this(cow, 10); + } + + public WorldConfigCache(boolean cow, int initialCapacity) { + super(cow, initialCapacity); + } + + public C getConfig(final World world) { + return getConfig(world.getName()); + } + + @Override + protected C newConfig(final String key) { + return newConfig(key, ConfigManager.getConfigFile(key)); + } + + protected abstract C newConfig(String key, ConfigFile configFile); + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfig.java new file mode 100644 index 00000000..e8cfbab1 --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfig.java @@ -0,0 +1,37 @@ +package fr.neatmonster.nocheatplus.net; + +import fr.neatmonster.nocheatplus.config.ConfPaths; +import fr.neatmonster.nocheatplus.config.ConfigFile; +import fr.neatmonster.nocheatplus.config.ConfigManager; + +/** + * Configuration for the net checks (fast version, sparse). + * @author web4web1 + * + */ +public class NetConfig { + + public final boolean flyingFrequencyActive; + public final int flyingFrequencySeconds; + public final int flyingFrequencyMaxPackets; + public final boolean flyingFrequencyCancelRedundant; + + public final boolean soundDistanceActive; + /** Maximum distance for lightning effects (squared). */ + public final double soundDistanceSq; + + public NetConfig(final ConfigFile config) { + + final ConfigFile globalConfig = ConfigManager.getConfigFile(); + flyingFrequencyActive = config.getBoolean(ConfPaths.NET_FLYINGFREQUENCY_ACTIVE); + flyingFrequencySeconds = Math.max(1, globalConfig.getInt(ConfPaths.NET_FLYINGFREQUENCY_SECONDS)); + flyingFrequencyMaxPackets = Math.max(1, globalConfig.getInt(ConfPaths.NET_FLYINGFREQUENCY_MAXPACKETS)); + flyingFrequencyCancelRedundant = config.getBoolean(ConfPaths.NET_FLYINGFREQUENCY_CANCELREDUNDANT); + + soundDistanceActive = config.getBoolean(ConfPaths.NET_SOUNDDISTANCE_ACTIVE); + double dist = config.getDouble(ConfPaths.NET_SOUNDDISTANCE_MAXDISTANCE); + soundDistanceSq = dist * dist; + + } + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfigCache.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfigCache.java new file mode 100644 index 00000000..c64b84d7 --- /dev/null +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/net/NetConfigCache.java @@ -0,0 +1,17 @@ +package fr.neatmonster.nocheatplus.net; + +import fr.neatmonster.nocheatplus.config.ConfigFile; +import fr.neatmonster.nocheatplus.config.WorldConfigCache; + +public class NetConfigCache extends WorldConfigCache { + + public NetConfigCache() { + super(true); + } + + @Override + protected NetConfig newConfig(String key, ConfigFile configFile) { + return new NetConfig(configFile); + } + +}