diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderA.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderA.java index a34120eb56..ca824a1914 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderA.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderA.java @@ -41,7 +41,7 @@ public void onPacketReceive(PacketReceiveEvent event) { public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (; invalid >= 1; invalid--) { flagAndAlert(); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderE.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderE.java index aaf111e4de..e594c4bd69 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderE.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderE.java @@ -8,13 +8,15 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import java.util.ArrayDeque; + @CheckData(name = "PacketOrderE", experimental = true) public class PacketOrderE extends Check implements PostPredictionCheck { public PacketOrderE(final GrimPlayer player) { super(player); } - private int invalid; + private final ArrayDeque flags = new ArrayDeque<>(); private boolean setback; @Override @@ -30,9 +32,17 @@ public void onPacketReceive(PacketReceiveEvent event) { || player.packetOrderProcessor.isStartingToGlide() || player.packetOrderProcessor.isJumpingWithMount() ) { - if (player.canSkipTicks() || flagAndAlert()) { - invalid++; - + String verbose = "attacking=" + player.packetOrderProcessor.isAttacking() + + ", rightClicking=" + player.packetOrderProcessor.isRightClicking() + + ", openingInventory=" + player.packetOrderProcessor.isOpeningInventory() + + ", releasing=" + player.packetOrderProcessor.isReleasing() + + ", sneaking=" + player.packetOrderProcessor.isSneaking() + + ", sprinting=" + player.packetOrderProcessor.isSprinting() + + ", bed=" + player.packetOrderProcessor.isLeavingBed() + + ", sprinting=" + player.packetOrderProcessor.isSprinting() + + ", gliding=" + player.packetOrderProcessor.isStartingToGlide() + + ", mountJumping=" + player.packetOrderProcessor.isJumpingWithMount(); + if (player.canSkipTicks() && flags.add(verbose) || flagAndAlert(verbose)) { if (player.packetOrderProcessor.isUsing()) { setback = true; } @@ -51,15 +61,16 @@ public void onPredictionComplete(PredictionComplete predictionComplete) { return; } - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { - for (; invalid >= 1; invalid--) { - if (flagAndAlert() && setback) { + if (player.isTickingReliablyFor(3)) { + for (String verbose : flags) { + if (flagAndAlert(verbose) && setback) { setback = false; setbackIfAboveSetbackVL(); } } } - invalid = 0; + setback = false; + flags.clear(); } } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderF.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderF.java index d5236b3389..d4f9a9566f 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderF.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderF.java @@ -11,13 +11,15 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; +import java.util.ArrayDeque; + @CheckData(name = "PacketOrderF", experimental = true) public class PacketOrderF extends Check implements PostPredictionCheck { public PacketOrderF(GrimPlayer player) { super(player); } - private int invalid; + private final ArrayDeque flags = new ArrayDeque<>(); @Override public void onPacketReceive(PacketReceiveEvent event) { @@ -29,8 +31,16 @@ public void onPacketReceive(PacketReceiveEvent event) { || (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) ) if (player.packetOrderProcessor.isSprinting() || player.packetOrderProcessor.isSneaking()) { + String verbose = "action=" + (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY ? "interact" + : event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT ? "place" + : event.getPacketType() == PacketType.Play.Client.USE_ITEM ? "use" + : event.getPacketType() == PacketType.Play.Client.PICK_ITEM ? "pick" + : event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING ? "dig" + : "openInventory") + + ", sprinting=" + player.packetOrderProcessor.isSprinting() + + ", sneaking=" + player.packetOrderProcessor.isSneaking(); if (!player.canSkipTicks()) { - if (flagAndAlert() && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING && new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEASE_USE_ITEM ) return; // don't cause a noslow @@ -39,7 +49,7 @@ && new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEAS player.onPacketCancel(); } } else { - invalid++; + flags.add(verbose); } } } @@ -48,12 +58,12 @@ && new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEAS public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { - for (; invalid >= 1; invalid--) { - flagAndAlert(); + if (player.isTickingReliablyFor(3)) { + for (String verbose : flags) { + flagAndAlert(verbose); } } - invalid = 0; + flags.clear(); } } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderG.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderG.java index ac675e186e..5b7b9e00e7 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderG.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderG.java @@ -11,22 +11,23 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; +import java.util.ArrayDeque; + @CheckData(name = "PacketOrderG", experimental = true) public class PacketOrderG extends Check implements PostPredictionCheck { public PacketOrderG(GrimPlayer player) { super(player); } - private int invalid; + private final ArrayDeque flags = new ArrayDeque<>(); @Override public void onPacketReceive(PacketReceiveEvent event) { - if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING - || (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS - && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) - ) { + if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING || (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS + && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT)) { + DiggingAction action = null; if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) { - final DiggingAction action = new WrapperPlayClientPlayerDigging(event).getAction(); + action = new WrapperPlayClientPlayerDigging(event).getAction(); if (action == DiggingAction.RELEASE_USE_ITEM || action == DiggingAction.START_DIGGING || action == DiggingAction.CANCELLED_DIGGING @@ -40,13 +41,19 @@ && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClie || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging() ) { + String verbose = "action=" + (action == null ? "openInventory" : action == DiggingAction.SWAP_ITEM_WITH_OFFHAND ? "swap" : "drop") + + ", attacking=" + player.packetOrderProcessor.isAttacking() + + ", releasing=" + player.packetOrderProcessor.isReleasing() + + ", rightClicking=" + player.packetOrderProcessor.isRightClicking() + + ", picking=" + player.packetOrderProcessor.isPicking() + + ", digging=" + player.packetOrderProcessor.isDigging(); if (!player.canSkipTicks()) { - if (flagAndAlert() && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } else { - invalid++; + flags.add(verbose); } } } @@ -56,12 +63,12 @@ && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClie public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { - for (; invalid >= 1; invalid--) { - flagAndAlert(); + if (player.isTickingReliablyFor(3)) { + for (String verbose : flags) { + flagAndAlert(verbose); } } - invalid = 0; + flags.clear(); } } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderH.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderH.java index 92156adc0a..03edffc6f3 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderH.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderH.java @@ -51,7 +51,7 @@ public void onPacketReceive(PacketReceiveEvent event) { public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (; invalid >= 1; invalid--) { flagAndAlert(); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderI.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderI.java index b6b373fb2a..0280201e94 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderI.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderI.java @@ -33,36 +33,42 @@ public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) { if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) { if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) { + String verbose = "type=attack, rightClicking=" + player.packetOrderProcessor.isRightClicking() + + ", picking=" + player.packetOrderProcessor.isPicking() + + ", releasing=" + player.packetOrderProcessor.isReleasing() + + ", digging=" + player.packetOrderProcessor.isDigging(); if (!player.canSkipTicks()) { - if (flagAndAlert("attack") && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } else { - flags.add("attack"); + flags.add(verbose); } } } else if (player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) { + String verbose = "type=interact, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + player.packetOrderProcessor.isDigging(); if (!player.canSkipTicks()) { - if (flagAndAlert("interact") && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } else { - flags.add("interact"); + flags.add(verbose); } } } if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT || event.getPacketType() == PacketType.Play.Client.USE_ITEM) { if (player.packetOrderProcessor.isReleasing() || digging) { + String verbose = "type=place/use, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + digging; if (!player.canSkipTicks()) { - if (flagAndAlert("place/use") && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } else { - flags.add("place"); + flags.add(verbose); } } } @@ -73,12 +79,16 @@ public void onPacketReceive(PacketReceiveEvent event) { switch (packet.getAction()) { case RELEASE_USE_ITEM: if (player.packetOrderProcessor.isAttacking() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) { + String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttacking() + + ", rightClicking=" + player.packetOrderProcessor.isRightClicking() + + ", picking=" + player.packetOrderProcessor.isPicking() + + ", digging=" + player.packetOrderProcessor.isDigging(); if (!player.canSkipTicks()) { - if (flagAndAlert("release")) { + if (flagAndAlert(verbose)) { setback = true; } } else { - flags.add("release"); + flags.add(verbose); setback = true; } } @@ -112,7 +122,7 @@ public void onPredictionComplete(PredictionComplete predictionComplete) { return; } - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (String verbose : flags) { if (flagAndAlert(verbose) && setback) { setbackIfAboveSetbackVL(); diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderJ.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderJ.java index bdded5deb7..f268bd9e44 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderJ.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderJ.java @@ -36,7 +36,7 @@ public void onPacketReceive(PacketReceiveEvent event) { public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (; invalid >= 1; invalid--) { flagAndAlert(); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderK.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderK.java index 82eef65b88..7a446b70fc 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderK.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderK.java @@ -24,10 +24,11 @@ public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) { if (new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) { if (player.packetOrderProcessor.isClickingInInventory() || player.packetOrderProcessor.isClosingInventory()) { + String verbose = "open, clicking=" + player.packetOrderProcessor.isClickingInInventory() + ", closing=" + player.packetOrderProcessor.isClosingInventory(); if (!player.canSkipTicks()) { - flagAndAlert("open"); + flagAndAlert(verbose); } else { - flags.add("open"); + flags.add(verbose); } } } @@ -35,13 +36,14 @@ public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW || event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) { if (player.packetOrderProcessor.isOpeningInventory()) { + String verbose = event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW ? "click" : "close"; if (!player.canSkipTicks()) { - if (flagAndAlert(event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW ? "click" : "close") && shouldModifyPackets()) { + if (flagAndAlert(verbose) && shouldModifyPackets()) { event.setCancelled(true); player.onPacketCancel(); } } else { - flags.add(event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW ? "click" : "close"); + flags.add(verbose); } } } @@ -51,7 +53,7 @@ public void onPacketReceive(PacketReceiveEvent event) { public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (String verbose : flags) { flagAndAlert(verbose); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderL.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderL.java index a00a2c62b0..99b1ea77bc 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderL.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderL.java @@ -58,7 +58,7 @@ public void onPacketReceive(PacketReceiveEvent event) { public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (String verbose : flags) { flagAndAlert(verbose); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderM.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderM.java index c2cbd3158c..101faa5d8c 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderM.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderM.java @@ -57,7 +57,7 @@ && new WrapperPlayClientPlayerBlockPlacement(event).getFace() == BlockFace.OTHER public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (; invalid >= 1; invalid--) { flagAndAlert(); } diff --git a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderN.java b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderN.java index 3b0a5f3303..2bbf7f9bd8 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderN.java +++ b/src/main/java/ac/grim/grimac/checks/impl/packetorder/PacketOrderN.java @@ -54,7 +54,7 @@ && new WrapperPlayClientPlayerBlockPlacement(event).getFace() == BlockFace.OTHER public void onPredictionComplete(PredictionComplete predictionComplete) { if (!player.canSkipTicks()) return; - if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) { + if (player.isTickingReliablyFor(3)) { for (; invalid >= 1; invalid--) { flagAndAlert(); } diff --git a/src/main/java/ac/grim/grimac/commands/GrimDump.java b/src/main/java/ac/grim/grimac/commands/GrimDump.java new file mode 100644 index 0000000000..72f9d45b4c --- /dev/null +++ b/src/main/java/ac/grim/grimac/commands/GrimDump.java @@ -0,0 +1,88 @@ +package ac.grim.grimac.commands; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.utils.anticheat.MessageUtil; +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import com.github.retrooper.packetevents.PacketEvents; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.github.retrooper.packetevents.util.folia.FoliaScheduler; +import io.github.retrooper.packetevents.util.viaversion.ViaVersionUtil; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +@CommandAlias("grim|grimac") +public class GrimDump extends BaseCommand { + + @Subcommand("dump") + @CommandPermission("grim.dump") + public void onCommand(CommandSender sender) { + if (link != null) { + MessageUtil.sendMessage(sender, MessageUtil.miniMessage(GrimAPI.INSTANCE.getConfigManager().getConfig() + .getStringElse("upload-log", "%prefix% &fUploaded debug to: %url%") + .replace("%url%", link))); + return; + } + //TODO: change this back to application/json once allowed + GrimLog.sendLogAsync(sender, generateDump(), string -> link = string, "text/yaml"); + } + + private String link = null; // these links should not expire for a while + + // this will help for debugging & replicating issues + private String generateDump() { + JsonObject base = new JsonObject(); + base.addProperty("type", "dump"); + base.addProperty("timestamp", System.currentTimeMillis()); + // versions + JsonObject versions = new JsonObject(); + base.add("versions", versions); + versions.addProperty("grim", GrimAPI.INSTANCE.getExternalAPI().getGrimVersion()); + versions.addProperty("packetevents", PacketEvents.getAPI().getVersion().toString()); + versions.addProperty("server", PacketEvents.getAPI().getServerManager().getVersion().getReleaseName()); + versions.addProperty("implementation", Bukkit.getVersion()); + // properties + JsonArray properties = new JsonArray(); + base.add("properties", properties); + if (PAPER) properties.add("paper"); + if (FoliaScheduler.isFolia()) properties.add("folia"); + if (ViaVersionUtil.isAvailable()) properties.add("viaversion"); + // system + JsonObject system = new JsonObject(); + base.add("system", system); + system.addProperty("os", System.getProperty("os.name")); + system.addProperty("java", System.getProperty("java.version")); + // plugins + JsonArray plugins = new JsonArray(); + base.add("plugins", plugins); + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + JsonObject pluginJson = new JsonObject(); + pluginJson.addProperty("enabled", plugin.isEnabled()); + pluginJson.addProperty("name", plugin.getName()); + pluginJson.addProperty("version", plugin.getDescription().getVersion()); + plugins.add(pluginJson); + } + return gson.toJson(base); + } + + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + private static final boolean PAPER = hasClass("com.destroystokyo.paper.PaperConfig") + || hasClass("io.papermc.paper.configuration.Configuration"); + + private static boolean hasClass(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/src/main/java/ac/grim/grimac/commands/GrimLog.java b/src/main/java/ac/grim/grimac/commands/GrimLog.java index 24669602f3..ce19c5082f 100644 --- a/src/main/java/ac/grim/grimac/commands/GrimLog.java +++ b/src/main/java/ac/grim/grimac/commands/GrimLog.java @@ -11,9 +11,12 @@ import io.github.retrooper.packetevents.util.folia.FoliaScheduler; import org.bukkit.command.CommandSender; +import java.io.IOException; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.function.Consumer; @CommandAlias("grim|grimac") public class GrimLog extends BaseCommand { @@ -23,48 +26,24 @@ public class GrimLog extends BaseCommand { @CommandAlias("gl") public void onLog(CommandSender sender, int flagId) { StringBuilder builder = SuperDebug.getFlag(flagId); - if (builder == null) { String failure = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("upload-log-not-found", "%prefix% &cUnable to find that log"); failure = MessageUtil.replacePlaceholders(sender, failure); MessageUtil.sendMessage(sender, MessageUtil.miniMessage(failure)); return; } + sendLogAsync(sender, builder.toString(), string -> {}, "text/yaml"); + } + public static void sendLogAsync(CommandSender sender, String log, Consumer consumer, String type) { String success = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("upload-log", "%prefix% &fUploaded debug to: %url%"); String failure = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("upload-log-upload-failure", "%prefix% &cSomething went wrong while uploading this log, see console for more information."); - String uploading = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("upload-log-start", "%prefix% &fUploading log... please wait"); uploading = MessageUtil.replacePlaceholders(sender, uploading); MessageUtil.sendMessage(sender, MessageUtil.miniMessage(uploading)); - FoliaScheduler.getAsyncScheduler().runNow(GrimAPI.INSTANCE.getPlugin(), (dummy) -> { try { - URL mUrl = new URL("https://paste.grim.ac/data/post"); - HttpURLConnection urlConn = (HttpURLConnection) mUrl.openConnection(); - urlConn.setDoOutput(true); - urlConn.setRequestMethod("POST"); - urlConn.addRequestProperty("User-Agent", "GrimAC/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion()); - urlConn.addRequestProperty("Content-Type", "text/yaml"); // Not really yaml, but looks nicer than plaintext - urlConn.setRequestProperty("Content-Length", Integer.toString(builder.length())); - urlConn.getOutputStream().write(builder.toString().getBytes(StandardCharsets.UTF_8)); - - urlConn.getOutputStream().close(); - - int response = urlConn.getResponseCode(); - - if (response == HttpURLConnection.HTTP_CREATED) { - String responseURL = urlConn.getHeaderField("Location"); - String message = success.replace("%url%", "https://paste.grim.ac/" + responseURL); - message = MessageUtil.replacePlaceholders(sender, message); - MessageUtil.sendMessage(sender, MessageUtil.miniMessage(message)); - } else { - String message = MessageUtil.replacePlaceholders(sender, failure); - MessageUtil.sendMessage(sender, MessageUtil.miniMessage(message)); - LogUtil.error("Returned response code " + response + ": " + urlConn.getResponseMessage()); - } - - urlConn.disconnect(); + sendLog(sender, log, success, failure, consumer, type); } catch (Exception e) { String message = MessageUtil.replacePlaceholders(sender, failure); MessageUtil.sendMessage(sender, MessageUtil.miniMessage(message)); @@ -72,4 +51,34 @@ public void onLog(CommandSender sender, int flagId) { } }); } + + private static void sendLog(CommandSender sender, String log, String success, String failure, Consumer consumer, String type) throws IOException { + URL mUrl = new URL("https://paste.grim.ac/data/post"); + HttpURLConnection urlConn = (HttpURLConnection) mUrl.openConnection(); + try { + urlConn.setDoOutput(true); + urlConn.setRequestMethod("POST"); + urlConn.addRequestProperty("User-Agent", "GrimAC/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion()); + urlConn.addRequestProperty("Content-Type", type); // Not really yaml, but looks nicer than plaintext + urlConn.setRequestProperty("Content-Length", Integer.toString(log.length())); + try (OutputStream stream = urlConn.getOutputStream()) { + stream.write(log.getBytes(StandardCharsets.UTF_8)); + } + final int response = urlConn.getResponseCode(); + if (response == HttpURLConnection.HTTP_CREATED) { + String responseURL = urlConn.getHeaderField("Location"); + String message = success.replace("%url%", "https://paste.grim.ac/" + responseURL); + consumer.accept(message); + message = MessageUtil.replacePlaceholders(sender, message); + MessageUtil.sendMessage(sender, MessageUtil.miniMessage(message)); + } else { + String message = MessageUtil.replacePlaceholders(sender, failure); + MessageUtil.sendMessage(sender, MessageUtil.miniMessage(message)); + LogUtil.error("Returned response code " + response + ": " + urlConn.getResponseMessage()); + } + } finally { + urlConn.disconnect(); + } + } + } diff --git a/src/main/java/ac/grim/grimac/commands/GrimVersion.java b/src/main/java/ac/grim/grimac/commands/GrimVersion.java new file mode 100644 index 0000000000..e1f2197d44 --- /dev/null +++ b/src/main/java/ac/grim/grimac/commands/GrimVersion.java @@ -0,0 +1,128 @@ +package ac.grim.grimac.commands; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.utils.anticheat.LogUtil; +import ac.grim.grimac.utils.anticheat.MessageUtil; +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.github.retrooper.packetevents.util.folia.FoliaScheduler; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.CommandSender; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +@CommandAlias("grim|grimac") +public class GrimVersion extends BaseCommand { + + @Subcommand("version") + @CommandPermission("grim.version") + public void onCommand(CommandSender sender) { + checkForUpdatesAsync(sender); + } + + private static long lastCheck; + private static final AtomicReference updateMessage = new AtomicReference<>(); + + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + + public static void checkForUpdatesAsync(CommandSender sender) { + String current = GrimAPI.INSTANCE.getExternalAPI().getGrimVersion(); + MessageUtil.sendMessage(sender, Component.text() + .append(Component.text("Grim Version: ").color(NamedTextColor.GRAY)) + .append(Component.text(current).color(NamedTextColor.AQUA)) + .build()); + // use cached message if last check was less than 1 minute ago + final long now = System.currentTimeMillis(); + if (now - lastCheck < 60000) { + Component message = updateMessage.get(); + if (message != null) MessageUtil.sendMessage(sender, message); + return; + } + lastCheck = now; + FoliaScheduler.getAsyncScheduler().runNow(GrimAPI.INSTANCE.getPlugin(), (dummy) -> checkForUpdates(sender)); + } + + // Using UserAgent format recommended by https://docs.modrinth.com/api/ + private static void checkForUpdates(CommandSender sender) { + String current = GrimAPI.INSTANCE.getExternalAPI().getGrimVersion(); + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.modrinth.com/v2/project/LJNGWSvH/version")) + .GET() + .header("User-Agent", "GrimAnticheat/Grim/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion()) + .header("Content-Type", "application/json") + .timeout(Duration.of(5, ChronoUnit.SECONDS)) + .build(); + + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + Component msg = updateMessage.get(); + MessageUtil.sendMessage(sender, Objects.requireNonNullElseGet(msg, () -> Component.text() + .append(Component.text("Failed to check latest version.").color(NamedTextColor.RED)) + .build())); + LogUtil.error("Failed to check latest GrimAC version. Response code: " + response.statusCode()); + return; + } + JsonObject object = JsonParser.parseString(response.body()).getAsJsonArray().get(0).getAsJsonObject(); + String latest = object.get("version_number").getAsString(); + Status status = compareVersions(current, latest); + Component msg = switch (status) { + case AHEAD -> + Component.text("You are using a development version of GrimAC").color(NamedTextColor.LIGHT_PURPLE); + case UPDATED -> + Component.text("You are using the latest version of GrimAC").color(NamedTextColor.GREEN); + case OUTDATED -> Component.text() + .append(Component.text("New GrimAC version found!").color(NamedTextColor.AQUA)) + .append(Component.text(" Version ").color(NamedTextColor.GRAY)) + .append(Component.text(latest).color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC)) + .append(Component.text(" is available to be downloaded here: ").color(NamedTextColor.GRAY)) + .append(Component.text("https://modrinth.com/plugin/grimac").color(NamedTextColor.GRAY).decorate(TextDecoration.UNDERLINED) + .clickEvent(ClickEvent.openUrl("https://modrinth.com/plugin/grimac"))) + .build(); + }; + updateMessage.set(msg); + MessageUtil.sendMessage(sender, msg); + } catch (Exception ignored) { + MessageUtil.sendMessage(sender, Component.text("Failed to check latest version.").color(NamedTextColor.RED)); + LogUtil.error("Failed to check latest GrimAC version.", ignored); + } + } + + private enum Status { + AHEAD, + UPDATED, + OUTDATED + } + + private static Status compareVersions(String local, String latest) { + if (local.equals(latest)) return Status.UPDATED; + String[] localParts = local.split("\\."); + String[] latestParts = latest.split("\\."); + int length = Math.max(localParts.length, latestParts.length); + for (int i = 0; i < length; i++) { + int localPart = i < localParts.length ? Integer.parseInt(localParts[i]) : 0; + int latestPart = i < latestParts.length ? Integer.parseInt(latestParts[i]) : 0; + if (localPart < latestPart) { + return Status.OUTDATED; + } else if (localPart > latestPart) { + return Status.AHEAD; + } + } + return Status.UPDATED; + } + +} diff --git a/src/main/java/ac/grim/grimac/manager/CheckManager.java b/src/main/java/ac/grim/grimac/manager/CheckManager.java index fd1686c617..93ad034065 100644 --- a/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -19,7 +19,6 @@ import ac.grim.grimac.checks.impl.multiactions.*; import ac.grim.grimac.checks.impl.packetorder.*; import ac.grim.grimac.checks.impl.post.Post; -import ac.grim.grimac.checks.impl.multiactions.*; import ac.grim.grimac.checks.impl.prediction.DebugHandler; import ac.grim.grimac.checks.impl.prediction.GroundSpoof; import ac.grim.grimac.checks.impl.prediction.OffsetHandler; diff --git a/src/main/java/ac/grim/grimac/manager/InitManager.java b/src/main/java/ac/grim/grimac/manager/InitManager.java index 6eebdfcdf1..76c80b18ca 100644 --- a/src/main/java/ac/grim/grimac/manager/InitManager.java +++ b/src/main/java/ac/grim/grimac/manager/InitManager.java @@ -5,6 +5,7 @@ import ac.grim.grimac.manager.init.load.PacketEventsInit; import ac.grim.grimac.manager.init.start.*; import ac.grim.grimac.manager.init.stop.TerminatePacketEvents; +import ac.grim.grimac.utils.anticheat.MessageUtil; import com.google.common.collect.ImmutableList; import lombok.Getter; @@ -40,6 +41,11 @@ public InitManager() { .add(GrimAPI.INSTANCE.getViolationDatabaseManager()) .add(new JavaVersion()) .add(new ViaVersion()) + .add(() -> { + if (MessageUtil.hasPlaceholderAPI) { + new PlaceholderAPIExpansion().register(); + } + }) .build(); initializersOnStop = ImmutableList.builder() diff --git a/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java b/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java index 9d1640f4c9..e4b330357a 100644 --- a/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java +++ b/src/main/java/ac/grim/grimac/manager/init/start/CommandRegister.java @@ -4,6 +4,7 @@ import ac.grim.grimac.commands.*; import ac.grim.grimac.manager.init.Initable; import co.aikar.commands.PaperCommandManager; +import org.bukkit.Bukkit; public class CommandRegister implements Initable { @Override @@ -23,6 +24,8 @@ public void start() { commandManager.registerCommand(new GrimStopSpectating()); commandManager.registerCommand(new GrimLog()); commandManager.registerCommand(new GrimVerbose()); + commandManager.registerCommand(new GrimVersion()); + commandManager.registerCommand(new GrimDump()); commandManager.registerCommand(new GrimHistory()); commandManager.getCommandCompletions().registerCompletion("stopspectating", GrimStopSpectating.completionHandler); diff --git a/src/main/java/ac/grim/grimac/manager/init/start/PlaceholderAPIExpansion.java b/src/main/java/ac/grim/grimac/manager/init/start/PlaceholderAPIExpansion.java new file mode 100644 index 0000000000..3500fbc0d7 --- /dev/null +++ b/src/main/java/ac/grim/grimac/manager/init/start/PlaceholderAPIExpansion.java @@ -0,0 +1,65 @@ +package ac.grim.grimac.manager.init.start; + +import ac.grim.grimac.GrimAPI; +import ac.grim.grimac.api.GrimUser; +import ac.grim.grimac.player.GrimPlayer; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public class PlaceholderAPIExpansion extends PlaceholderExpansion { + @Override + public @NotNull String getIdentifier() { + return "grim"; + } + + public @NotNull String getAuthor() { + return String.join(", ", GrimAPI.INSTANCE.getPlugin().getDescription().getAuthors()); + } + + @Override + public @NotNull String getVersion() { + return GrimAPI.INSTANCE.getExternalAPI().getGrimVersion(); + } + + @Override + public @NotNull List getPlaceholders() { + Set placeholders = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().keySet(); + placeholders.addAll(GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().keySet()); + return List.copyOf(placeholders); + } + + @Override + public String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { + for (Map.Entry entry : GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().entrySet()) { + String key = entry.getKey().equals("%grim_version%") + ? "version" + : entry.getKey().replaceAll("%", ""); + if (params.equalsIgnoreCase(key)) { + return entry.getValue(); + } + } + + if (offlinePlayer instanceof Player player) { + GrimPlayer grimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player); + if (grimPlayer == null) return null; + + for (Map.Entry> entry : GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().entrySet()) { + String key = entry.getKey().equals("%player%") + ? "player" + : "player_" + entry.getKey().replaceAll("%", ""); + if (params.equalsIgnoreCase(key)) { + return entry.getValue().apply(grimPlayer); + } + } + } + + return null; + } +} diff --git a/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/src/main/java/ac/grim/grimac/player/GrimPlayer.java index e94aa49b25..72cf8c2375 100644 --- a/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -7,8 +7,8 @@ import ac.grim.grimac.checks.Check; import ac.grim.grimac.checks.impl.aim.processor.AimProcessor; import ac.grim.grimac.checks.impl.misc.ClientBrand; -import ac.grim.grimac.checks.impl.packetorder.PacketOrderProcessor; import ac.grim.grimac.checks.impl.packetorder.PacketOrderD; +import ac.grim.grimac.checks.impl.packetorder.PacketOrderProcessor; import ac.grim.grimac.events.packets.CheckManagerListener; import ac.grim.grimac.manager.*; import ac.grim.grimac.predictionengine.MovementCheckRunner; diff --git a/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java b/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java index 1534275f75..d2d2597e35 100644 --- a/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java +++ b/src/main/java/ac/grim/grimac/utils/anticheat/LogUtil.java @@ -22,6 +22,11 @@ public void error(final String error) { getLogger().severe(error); } + public void error(final String description, final Throwable throwable) { + getLogger().severe(description); + throwable.printStackTrace(); + } + public Logger getLogger() { return GrimAPI.INSTANCE.getPlugin().getLogger(); } @@ -33,4 +38,5 @@ public void console(final String info) { public void console(final Component info) { MessageUtil.sendMessage(Bukkit.getConsoleSender(), info); } + } diff --git a/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java b/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java index 906b2d0ee2..a7aa824e17 100644 --- a/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java +++ b/src/main/java/ac/grim/grimac/utils/anticheat/MessageUtil.java @@ -23,7 +23,7 @@ public class MessageUtil { private final Pattern HEX_PATTERN = Pattern.compile("([&§]#[A-Fa-f0-9]{6})|([&§]x([&§][A-Fa-f0-9]){6})"); private final BukkitAudiences adventure = BukkitAudiences.create(GrimAPI.INSTANCE.getPlugin()); - private final boolean hasPlaceholderAPI = Reflection.getClassByNameWithoutException("me.clip.placeholderapi.PlaceholderAPI") != null; + public final boolean hasPlaceholderAPI = Reflection.getClassByNameWithoutException("me.clip.placeholderapi.PlaceholderAPI") != null; public @NotNull String toUnlabledString(@Nullable Vector3i vec) { return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z; diff --git a/src/main/java/ac/grim/grimac/utils/data/tags/SyncedTags.java b/src/main/java/ac/grim/grimac/utils/data/tags/SyncedTags.java index 00fc33908d..361a17fe83 100644 --- a/src/main/java/ac/grim/grimac/utils/data/tags/SyncedTags.java +++ b/src/main/java/ac/grim/grimac/utils/data/tags/SyncedTags.java @@ -54,7 +54,7 @@ public SyncedTags(GrimPlayer player) { } @SafeVarargs - private final void trackTags(ResourceLocation location, Function remapper, SyncedTag.Builder... syncedTags) { + private void trackTags(ResourceLocation location, Function remapper, SyncedTag.Builder... syncedTags) { final Map> tags = new HashMap<>(syncedTags.length); for (SyncedTag.Builder syncedTag : syncedTags) { syncedTag.remapper(remapper);