Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord Config + ArmAnimation #1631

Draft
wants to merge 21 commits into
base: 2.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2130ff3
Update DiscordManager & its yml files.
Anthony01M May 1, 2024
6edc729
i forgot to add .url after webhook
Anthony01M May 2, 2024
1941771
fix "description" value
Anthony01M May 2, 2024
7594e42
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 7, 2024
7eb048c
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 13, 2024
a59205a
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 20, 2024
a33a2fe
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 22, 2024
bfba1e2
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 24, 2024
edb1271
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 26, 2024
c01eeb9
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 27, 2024
54a1528
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 28, 2024
9b11eb3
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M May 31, 2024
0e8a872
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M Jun 8, 2024
158f816
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M Jun 15, 2024
2d37354
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M Jun 18, 2024
6a50f53
Merge branch 'GrimAnticheat:2.0' into 2.0
Anthony01M Aug 2, 2024
4dee33a
ADD: ArmAnimation & related + AutoClickA
Anthony01M Aug 6, 2024
cd911e5
fix NCE -> Grim
Anthony01M Aug 6, 2024
4894f70
UPD: remove comment which apparently some people got confused due to it
Anthony01M Aug 8, 2024
32fbf66
fix: grrim -> grim
Anthony01M Aug 11, 2024
fa03f45
Merge branch 'GrimAnticheat:2.0' into detect-cps
Anthony01M Aug 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ac.grim.grimac.checks.impl.autoclicker;

import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.ArmAnimationCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.ArmAnimationUpdate;

/*
Information in regard to why this was added can be found here:
https://github.com/GrimAnticheat/Grim/pull/1631
*/
@CheckData(name = "AutoClickerA", alternativeName = "AutoclickerA")
public class AutoClickerA extends Check implements ArmAnimationCheck {
private int MAX_CPS;

public AutoClickerA(final GrimPlayer player) {
super(player);
}

@Override
public void process(final ArmAnimationUpdate armAnimationUpdate) {
final int cps = armAnimationUpdate.getLeftClicks();
if (cps > MAX_CPS) alert("cps=" + cps + ", max=" + MAX_CPS);
}

@Override
public void reload() {
super.reload();
MAX_CPS = getConfig().getIntElse("AutoClicker.max_cps", 20);
}
}
11 changes: 11 additions & 0 deletions src/main/java/ac/grim/grimac/checks/type/ArmAnimationCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ac.grim.grimac.checks.type;

import ac.grim.grimac.api.AbstractCheck;
import ac.grim.grimac.utils.anticheat.update.ArmAnimationUpdate;

public interface ArmAnimationCheck extends AbstractCheck {

default void process(final ArmAnimationUpdate armAnimationUpdate) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import org.bukkit.util.Vector;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;

public class CheckManagerListener extends PacketListenerAbstract {
Expand All @@ -58,6 +60,12 @@ public CheckManagerListener() {
super(PacketListenerPriority.LOW);
}

private static final long TIME_WINDOW = TimeUnit.SECONDS.toMillis(1),
ANIMATION_WINDOW = 200; // 200 milliseconds for right-click animation
private long lastCPSCheck = 0;
private final ArrayDeque<Long> leftClickTimestamps = new ArrayDeque<>(),
rightClickTimestamps = new ArrayDeque<>();

// Copied from MCP...
// Returns null if there isn't anything.
//
Expand Down Expand Up @@ -379,6 +387,9 @@ public void onPacketReceive(PacketReceiveEvent event) {
if (event.getConnectionState() != ConnectionState.PLAY) return;
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
long currentTime = System.currentTimeMillis();

boolean digging = false;

// Determine if teleport BEFORE we call the pre-prediction vehicle
if (event.getPacketType() == PacketType.Play.Client.VEHICLE_MOVE) {
Expand Down Expand Up @@ -461,6 +472,7 @@ public void onPacketReceive(PacketReceiveEvent event) {
player.checkManager.getPacketCheck(BadPacketsZ.class).handle(event, dig);

if (dig.getAction() == DiggingAction.FINISHED_DIGGING) {
digging = false;
// Not unbreakable
if (!block.getType().isAir() && block.getType().getHardness() != -1.0f && !event.isCancelled()) {
player.compensatedWorld.startPredicting();
Expand All @@ -470,6 +482,7 @@ public void onPacketReceive(PacketReceiveEvent event) {
}

if (dig.getAction() == DiggingAction.START_DIGGING && !event.isCancelled()) {
digging = true;
double damage = BlockBreakSpeed.getBlockDamage(player, dig.getBlockPosition());

//Instant breaking, no damage means it is unbreakable by creative players (with swords)
Expand All @@ -486,8 +499,13 @@ public void onPacketReceive(PacketReceiveEvent event) {
}
}

if (dig.getAction() == DiggingAction.CANCELLED_DIGGING) {
digging = false;
}

if (!event.isCancelled()) {
if (dig.getAction() == DiggingAction.START_DIGGING || dig.getAction() == DiggingAction.FINISHED_DIGGING || dig.getAction() == DiggingAction.CANCELLED_DIGGING) {
leftClickTimestamps.clear();
player.compensatedWorld.handleBlockBreakPrediction(dig);
}
}
Expand Down Expand Up @@ -566,6 +584,21 @@ public void onPacketReceive(PacketReceiveEvent event) {
player.lastBlockPlaceUseItem = System.currentTimeMillis();
}

// Handle left-clicks
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
if (isRecentRightClick(currentTime)) return;
if (digging) return;
handleLeftClick(currentTime);
}

if (currentTime - lastCPSCheck >= TIME_WINDOW) {
final ArmAnimationUpdate update = new ArmAnimationUpdate(leftClickTimestamps.size(), rightClickTimestamps.size());
player.checkManager.onArmAnimation(update);
leftClickTimestamps.clear();
rightClickTimestamps.clear();
lastCPSCheck = currentTime;
}

// Call the packet checks last as they can modify the contents of the packet
// Such as the NoFall check setting the player to not be on the ground
player.checkManager.onPacketReceive(event);
Expand Down Expand Up @@ -827,6 +860,24 @@ private static HitData getNearestHitResult(GrimPlayer player, StateType heldItem
});
}

private boolean isRecentRightClick(final long currentTime) {
return !rightClickTimestamps.isEmpty() && currentTime - rightClickTimestamps.peekLast() <= ANIMATION_WINDOW;
}

private void handleLeftClick(final long currentTime) {
leftClickTimestamps.addLast(currentTime);
removeOldTimestamps(currentTime, leftClickTimestamps);
}

private void handleRightClick(final long currentTime) {
rightClickTimestamps.addLast(currentTime);
removeOldTimestamps(currentTime, rightClickTimestamps);
}

private void removeOldTimestamps(final long currentTime, final ArrayDeque<Long> timestamps) {
while (!timestamps.isEmpty() && currentTime - timestamps.peek() > TIME_WINDOW) timestamps.pollFirst();
}

@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getConnectionState() != ConnectionState.PLAY) return;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/ac/grim/grimac/manager/CheckManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ac.grim.grimac.checks.impl.aim.AimDuplicateLook;
import ac.grim.grimac.checks.impl.aim.AimModulo360;
import ac.grim.grimac.checks.impl.aim.processor.AimProcessor;
import ac.grim.grimac.checks.impl.autoclicker.AutoClickerA;
import ac.grim.grimac.checks.impl.badpackets.*;
import ac.grim.grimac.checks.impl.baritone.Baritone;
import ac.grim.grimac.checks.impl.combat.Reach;
Expand Down Expand Up @@ -44,6 +45,7 @@
import com.google.common.collect.ImmutableClassToInstanceMap;

public class CheckManager {
ClassToInstanceMap<ArmAnimationCheck> armAnimationCheck;
ClassToInstanceMap<PacketCheck> packetChecks;
ClassToInstanceMap<PositionCheck> positionCheck;
ClassToInstanceMap<RotationCheck> rotationCheck;
Expand All @@ -56,6 +58,11 @@ public class CheckManager {
public ClassToInstanceMap<AbstractCheck> allChecks;

public CheckManager(GrimPlayer player) {

armAnimationCheck = new ImmutableClassToInstanceMap.Builder<ArmAnimationCheck>()
.put(AutoClickerA.class, new AutoClickerA(player))
.build();

// Include post checks in the packet check too
packetChecks = new ImmutableClassToInstanceMap.Builder<PacketCheck>()
.put(Reach.class, new Reach(player))
Expand Down Expand Up @@ -162,6 +169,7 @@ public CheckManager(GrimPlayer player) {
.build();

allChecks = new ImmutableClassToInstanceMap.Builder<AbstractCheck>()
.putAll(armAnimationCheck)
.putAll(packetChecks)
.putAll(positionCheck)
.putAll(rotationCheck)
Expand Down Expand Up @@ -253,6 +261,12 @@ public void onPostFlyingBlockPlace(final BlockPlace place) {
}
}

public void onArmAnimation(final ArmAnimationUpdate armAnimationUpdate) {
for (ArmAnimationCheck check : armAnimationCheck.values()) {
check.process(armAnimationUpdate);
}
}

public ExplosionHandler getExplosionHandler() {
return getPostPredictionCheck(ExplosionHandler.class);
}
Expand Down
133 changes: 113 additions & 20 deletions src/main/java/ac/grim/grimac/manager/DiscordManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import java.awt.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -19,14 +19,16 @@ public class DiscordManager implements Initable {
private static WebhookClient client;
private int embedColor;
private String staticContent = "";
// Custom fields - not ideal but it works + it's easy to configure
private List<Map<String, Object>> customFields;

public static final Pattern WEBHOOK_PATTERN = Pattern.compile("(?:https?://)?(?:\\w+\\.)?\\w+\\.\\w+/api(?:/v\\d+)?/webhooks/(\\d+)/([\\w-]+)(?:/(?:\\w+)?)?");

@Override
public void start() {
try {
if (!GrimAPI.INSTANCE.getConfigManager().getConfig().getBooleanElse("enabled", false)) return;
String webhook = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("webhook", "");
String webhook = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("webhook.url", "");
if (webhook.isEmpty()) {
LogUtil.warn("Discord webhook is empty, disabling Discord alerts");
client = null;
Expand All @@ -46,49 +48,140 @@ public void start() {
LogUtil.warn("Discord embed color is invalid");
}
StringBuilder sb = new StringBuilder();
for (String string : GrimAPI.INSTANCE.getConfigManager().getConfig().getStringListElse("violation-content", getDefaultContents())) {
for (String string : GrimAPI.INSTANCE.getConfigManager().getConfig().getStringListElse("webhook.description", getDefaultDescription())) {
sb.append(string).append("\n");
}
staticContent = sb.toString();

// never tested what would happen if fields is empty
customFields = new ArrayList<>();
List<Map<String, Object>> fieldMaps = GrimAPI.INSTANCE.getConfigManager().getConfig().getListElse("webhook.fields", getDefaultFields());
customFields.addAll(fieldMaps);
} catch (Exception e) {
e.printStackTrace();
}
}

private List<String> getDefaultContents() {
private List<String> getDefaultDescription() {
List<String> list = new ArrayList<>();
list.add("**Player**: %player%");
list.add("**Check**: %check%");
list.add("**Violations**: %violations%");
list.add("**Client Version**: %version%");
list.add("**Brand**: %brand%");
list.add("**Ping**: %ping%");
list.add("**TPS**: %tps%");
list.add("Grim Version: %grim_version%");
return list;
}

public List<Map<String, Object>> getDefaultFields() {
List<Map<String, Object>> fields = new ArrayList<>();

// Server Information field
Map<String, Object> serverInfoField = new HashMap<>();
serverInfoField.put("name", "Server Information");
serverInfoField.put("value", Arrays.asList(
"```properties",
"Server: %server%",
"TPS: %tps%",
"```"
));
serverInfoField.put("inline", false);
fields.add(serverInfoField);

// Client Information field
Map<String, Object> clientInfoField = new HashMap<>();
clientInfoField.put("name", "Client Information");
clientInfoField.put("value", Arrays.asList(
"```properties",
"Version: %version%",
"Brand: %brand%",
"```"
));
clientInfoField.put("inline", false);
fields.add(clientInfoField);

// Player Information field
Map<String, Object> playerInfoField = new HashMap<>();
playerInfoField.put("name", "Player Information");
playerInfoField.put("value", Arrays.asList(
"```properties",
"Player: %player%",
"UUID: %uuid%",
"Ping: %ping%",
"Horizontal Sensitivity: %h_sensitivity%%",
"Vertical Sensitivity: %v_sensitivity%%",
"Fast Math: %fast_math%",
"```"
));
playerInfoField.put("inline", false);
fields.add(playerInfoField);

// Check Information field
Map<String, Object> checkInfoField = new HashMap<>();
checkInfoField.put("name", "Check Information");
checkInfoField.put("value", Arrays.asList(
"```properties",
"Check: %check%",
"Violations: %violations%",
"```"
));
checkInfoField.put("inline", false);
fields.add(checkInfoField);

return fields;
}

public void sendAlert(GrimPlayer player, String verbose, String checkName, String violations) {
if (client != null) {

String content = staticContent + "";
content = content.replace("%check%", checkName);
content = content.replace("%violations%", violations);
content = GrimAPI.INSTANCE.getExternalAPI().replaceVariables(player, content, false);
content = content.replace("_", "\\_");
String description = staticContent;
description = description.replace("%check%", checkName);
description = description.replace("%violations%", violations);
description = description.replace("%grim_version%", GrimAPI.INSTANCE.getPlugin().getDescription().getVersion());
description = GrimAPI.INSTANCE.getExternalAPI().replaceVariables(player, description, false);
description = description.replaceAll("_", "\\_");

WebhookEmbedBuilder embed = new WebhookEmbedBuilder()
.setImageUrl("https://i.stack.imgur.com/Fzh0w.png") // Constant width
.setThumbnailUrl("https://crafthead.net/helm/" + player.user.getProfile().getUUID())
.setColor(embedColor)
.setTitle(new WebhookEmbed.EmbedTitle("**Grim Alert**", null))
.setDescription(content)
.setDescription(description)
.setTimestamp(Instant.now())
.setFooter(new WebhookEmbed.EmbedFooter("", "https://grim.ac/images/grim.png"));

if (!verbose.isEmpty()) {
embed.addField(new WebhookEmbed.EmbedField(true, "Verbose", verbose));
if (GrimAPI.INSTANCE.getConfigManager().getConfig().getBooleanElse("webhook.show-player-head", true)) {
embed.setThumbnailUrl("https://crafthead.net/helm/" + player.user.getProfile().getUUID());
}

// Add custom fields if they exist
for (Map<String, Object> field : customFields) {
String name = (String) field.get("name");
List<String> value = (List<String>) field.get("value");
boolean inline = (boolean) field.getOrDefault("inline", false);

// Replace placeholders in field values
List<String> fieldValue = new ArrayList<>();
for (String line : value) {
line = line.replace("%check%", checkName); // Replace %check% placeholder
line = line.replace("%violations%", violations); // Replace %violations% placeholder
line = line.replace("%grim_version%", GrimAPI.INSTANCE.getPlugin().getDescription().getVersion());
line = GrimAPI.INSTANCE.getExternalAPI().replaceVariables(player, line, false);
line = line.replaceAll("_", "\\_");
fieldValue.add(line);
}

StringBuilder fieldValueString = new StringBuilder();
for (String line : fieldValue) {
fieldValueString.append(line).append("\n");
}

embed.addField(new WebhookEmbed.EmbedField(inline, name, fieldValueString.toString()));
}

if (!verbose.isEmpty()) {
embed.addField(new WebhookEmbed.EmbedField(
true,
"Verbose",
"```properties\n" +
verbose +
"```"
));
}
sendWebhookEmbed(embed);
}
}
Expand Down
Loading