Compare commits

..
Author SHA1 Message Date
Axionize 72b8616ffe Merge remote-tracking branch 'origin/2.0' into optimize-entity-packets
# Conflicts:
#	src/main/java/ac/grim/grimac/utils/latency/CompensatedEntities.java
2024-11-25 16:28:14 -05:00
Axionize 0f3bf60549 Minor Optimization to addEntity() 2024-11-17 11:51:15 -05:00
Axionize deb2628f77 Optimize if else in updateEntityMetaData() 2024-11-17 11:50:47 -05:00
352 changed files with 3076 additions and 13041 deletions

No files matched your search

+1 -1
View File
@@ -5,4 +5,4 @@ name = "java"
enabled = true
[analyzers.meta]
runtime_version = "17"
runtime_version = "16"
-17
View File
@@ -1,17 +0,0 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 100
tab_width = 4
# intellij specific configuration
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_wrap_on_typing = false
+17 -78
View File
@@ -15,85 +15,24 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write # Allows the workflow to create or update references (tags)
contents: read
packages: write
actions: write
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Extract Version from build.gradle.kts
id: extract-version
run: |
# Extract the version from build.gradle.kts using grep and sed
VERSION=$(grep "^version\s*=" build.gradle.kts | sed 's/version\s*=\s*"\(.*\)"/\1/')
echo "VERSION: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build Lite Jar with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build -PshadePE=false
- name: Upload Lite Artifact
uses: actions/upload-artifact@v4
with:
name: grimac-lite
path: ${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}-lite.jar
- name: Build Main Jar with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build
- name: Upload Main Artifact
uses: actions/upload-artifact@v4
with:
name: grimac-main
path: ${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}.jar
- name: Generate build number
if: contains(fromJSON('["merge", "release", "main"]'), github.ref_name)
id: buildnumber
uses: onyxmueller/build-tag-number@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to Modrinth
if: contains(fromJSON('["merge", "release", "main"]'), github.ref_name)
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ vars.MODRINTH_ID }}
modrinth-featured: true
modrinth-unfeature-mode: subset
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: |
${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}.jar
${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}-lite.jar
name: Lightning Grim Anticheat ${{ env.VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
version: ${{ env.VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
version-type: release
loaders: |
bukkit
spigot
paper
folia
purpur
game-versions: |
>=1.7
retry-attempts: 2
retry-delay: 10000
fail-mode: fail
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
path: /home/runner/work/Grim/Grim/build/libs/**.jar
+10 -30
View File
@@ -1,41 +1,21 @@
# GrimAC
This project is considered feature complete for the 2.0 (open-source) branch of this project. If you would like a bugfix or enhancement and cannot sponsor the work, pull requests are welcome. You can join the [discord](https://discord.com/invite/kqQAhTmkUF) for jar releases & changelogs.
GrimAC is an open source Minecraft anticheat designed for 1.21 and supports 1.8-1.21. It is free while in beta. It will eventually become paid and/or will include offering additional subscription based paid checks. Geyser players are fully exempt.
This project is considered feature complete for the 2.0 (open-source) branch of this project. If you would like a bugfix or enhancement and cannot sponsor the work, pull requests are welcome.
### Compiling through terminal/command prompt
1. git clone https://github.com/GrimAnticheat/Grim.git (or click the green code button, download ZIP, then unzip it.)
2. cd Grim
3. ./gradlew build
4. The final jar is located in build/libs
## Downloads
- [Modrinth](https://modrinth.com/plugin/grimac)
- [Hangar](https://hangar.papermc.io/GrimAnticheat/GrimAnticheat)
- [SpigotMC](https://www.spigotmc.org/resources/grim-anticheat.99923/)
- *For bleeding edge builds use* [Github artifacts](https://nightly.link/GrimAnticheat/Grim/workflows/gradle-publish/2.0/artifact.zip)
## Installation notes
- Java 8 is fully supported.
- Paper, Spigot, and Folia are currently supported.
- If you use Geyser, place Floodgate on the backend server so grim can exempt bedrock players. Grim cannot access the Floodgate API if it is on the proxy.
- If you use ViaVersion, it should be on the backend server as movement is highly dependent on client version.
## Support & wiki information
- Support & discussion: [Discord](https://discord.com/invite/kqQAhTmkUF)
- Report issues: [Issues](https://github.com/GrimAnticheat/Grim/issues/new/choose)
- Wiki & examples: [Wiki](https://github.com/GrimAnticheat/Grim/wiki)
## Developer API
Grim's API allows you to integrate Grim into your own plugins.
- API repository: [GrimAPI](https://github.com/GrimAnticheat/GrimAPI)
- Wiki info: [Wiki](https://github.com/GrimAnticheat/GrimAPI)
## How to compile
1. `git clone https://github.com/GrimAnticheat/Grim.git`
2. `cd Grim`
3. `./gradlew build`
4. The final jar will compile into the build/libs folder
### API Information
Grim's API allows you to integrate Grim into your own plugins. For more information, check out the API's GitHub repository [here](https://github.com/GrimAnticheat/GrimAPI).
## Grim supremacy
What makes Grim stand out against other anticheats?
Here are the main cores that make Grim stand out against other anticheats
### Movement Simulation Engine
+18 -72
View File
@@ -3,7 +3,7 @@ import net.minecrell.pluginyml.bukkit.BukkitPluginDescription.Permission
plugins {
id("java")
id("maven-publish")
id("com.gradleup.shadow") version "9.0.0-beta6"
id("com.github.johnrengelman.shadow") version "8.1.1"
id("io.freefair.lombok") version "8.6"
id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
id("com.diffplug.spotless") version "6.25.0"
@@ -26,10 +26,12 @@ spotless {
}
group = "ac.grim.grimac"
version = "2.3.71"
version = "2.3.68"
description = "Libre simulation anticheat designed for 1.21 with 1.8-1.21 support, powered by PacketEvents 2.0."
java.sourceCompatibility = JavaVersion.VERSION_1_8
java.targetCompatibility = JavaVersion.VERSION_1_8
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
// Set to false for debug builds
// You cannot live reload classes if the jar relocates dependencies
@@ -38,18 +40,8 @@ val relocate: Boolean = project.findProperty("relocate")?.toString()?.toBoolean(
?: System.getenv("RELOCATE_JAR")?.toBoolean()
?: true
// Whether or not to shade PE into the jar, some servers may not want this if they use other plugins
// With PE; this prevents duplicate listening and reduces RAM usage by ~80-100 MB
val shadePE: Boolean = project.findProperty("shadePE")?.toString()?.toBoolean()
?: System.getenv("SHADE_PE")?.toBoolean()
?: true
repositories {
mavenLocal()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // Spigot
maven("https://jitpack.io/") { // Grim API
content {
@@ -63,38 +55,26 @@ repositories {
maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate)
maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents
maven("https://repo.codemc.io/repository/maven-snapshots/")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // placeholderapi
mavenCentral()
// FastUtil, Discord-Webhooks
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT")
// PE dependency is now conditional
if (shadePE) {
implementation("com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT")
} else {
compileOnly("com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT")
}
implementation("com.github.retrooper:packetevents-spigot:2.6.1-SNAPSHOT")
implementation("co.aikar:acf-paper:0.5.1-SNAPSHOT")
implementation("club.minnced:discord-webhooks:0.8.0") // Newer versions include kotlin-stdlib, which leads to incompatibility with plugins that use Kotlin
implementation("it.unimi.dsi:fastutil:8.5.15")
implementation("it.unimi.dsi:fastutil:8.5.13")
implementation("github.scarsz:configuralize:1.4.0")
implementation("com.zaxxer:HikariCP:4.0.3")
// Used for local testing:
//implementation("ac.grim.grimac:GrimAPI:1.0")
implementation("com.github.grimanticheat:grimapi:05e31d62f2")
implementation("net.kyori:adventure-text-minimessage:4.17.0")
implementation("net.kyori:adventure-platform-bukkit:4.3.4")
//implementation("com.github.grimanticheat:grimapi:1193c4fa41")
// Used for local testing: implementation("ac.grim.grimac:GRIMAPI:1.0")
implementation("com.github.grimanticheat:grimapi:fc5634e444")
implementation("org.jetbrains:annotations:24.1.0")
compileOnly("org.geysermc.floodgate:api:2.0-SNAPSHOT")
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
compileOnly("com.viaversion:viaversion-api:5.0.4-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.6")
//
compileOnly("io.netty:netty-all:4.1.85.Final")
}
@@ -106,10 +86,6 @@ bukkit {
apiVersion = "1.13"
foliaSupported = true
if (!shadePE) {
depend = listOf("packetevents") + (depend ?: emptyList())
}
softDepend = listOf(
"ProtocolLib",
"ProtocolSupport",
@@ -119,8 +95,7 @@ bukkit {
"ViaRewind",
"Geyser-Spigot",
"floodgate",
"FastLogin",
"PlaceholderAPI",
"FastLogin"
)
permissions {
@@ -149,11 +124,6 @@ bukkit {
default = Permission.Default.OP
}
register("grim.brand.enable-on-join") {
description = "Enable showing client brands on join"
default = Permission.Default.OP
}
register("grim.sendalert") {
description = "Send cheater alert"
default = Permission.Default.OP
@@ -173,16 +143,6 @@ bukkit {
description = "Exempt from all checks"
default = Permission.Default.FALSE
}
register("grim.verbose") {
description = "Receive verbose alerts for violations. Requires grim.alerts"
default = Permission.Default.OP
}
register("grim.verbose.enable-on-join") {
description = "Enable verbose alerts on join. Requires grim.alerts and grim.alerts.enable-on-join"
default = Permission.Default.FALSE
}
}
}
@@ -195,30 +155,16 @@ tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
disableAutoTargetJvm()
}
publishing.publications.create<MavenPublication>("maven") {
artifact(tasks["shadowJar"])
}
tasks.shadowJar {
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
minimize()
archiveFileName.set("${project.name}-${project.version}${if (shadePE) "" else "-lite"}.jar")
archiveFileName.set("${project.name}-${project.version}.jar")
if (relocate) {
// Only relocate PE if we're shading it
if (shadePE) {
relocate("io.github.retrooper.packetevents", "ac.grim.grimac.shaded.io.github.retrooper.packetevents")
relocate("com.github.retrooper.packetevents", "ac.grim.grimac.shaded.com.github.retrooper.packetevents")
relocate("net.kyori", "ac.grim.grimac.shaded.kyori") // use PE's built-in adventure instead when not shading
}
relocate("io.github.retrooper.packetevents", "ac.grim.grimac.shaded.io.github.retrooper.packetevents")
relocate("com.github.retrooper.packetevents", "ac.grim.grimac.shaded.com.github.retrooper.packetevents")
relocate("co.aikar.commands", "ac.grim.grimac.shaded.acf")
relocate("co.aikar.locale", "ac.grim.grimac.shaded.locale")
relocate("club.minnced", "ac.grim.grimac.shaded.discord-webhooks")
@@ -227,12 +173,12 @@ tasks.shadowJar {
relocate("com.google.code.gson", "ac.grim.grimac.shaded.gson")
relocate("alexh", "ac.grim.grimac.shaded.maps")
relocate("it.unimi.dsi.fastutil", "ac.grim.grimac.shaded.fastutil")
relocate("net.kyori", "ac.grim.grimac.shaded.kyori")
relocate("okhttp3", "ac.grim.grimac.shaded.okhttp3")
relocate("okio", "ac.grim.grimac.shaded.okio")
relocate("org.yaml.snakeyaml", "ac.grim.grimac.shaded.snakeyaml")
relocate("org.json", "ac.grim.grimac.shaded.json")
relocate("org.intellij", "ac.grim.grimac.shaded.intellij")
relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains")
relocate("com.zaxxer", "ac.grim.gimac.shaded.zaxxer")
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -1
View File
@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
+3 -23
View File
@@ -2,11 +2,8 @@ package ac.grim.grimac;
import ac.grim.grimac.manager.*;
import ac.grim.grimac.manager.config.BaseConfigManager;
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.anticheat.PlayerDataManager;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
@Getter
@@ -20,39 +17,22 @@ public enum GrimAPI {
private final PlayerDataManager playerDataManager = new PlayerDataManager();
private final TickManager tickManager = new TickManager();
private final GrimExternalAPI externalAPI = new GrimExternalAPI(this);
private ViolationDatabaseManager violationDatabaseManager;
private InitManager initManager;
private JavaPlugin plugin;
private boolean criticalLoadFailure = false; // Track critical failures
public void load(final JavaPlugin plugin) {
this.plugin = plugin;
this.violationDatabaseManager = new ViolationDatabaseManager(plugin);
initManager = new InitManager();
initManager.load(); // Load all initializers
initManager.load();
}
public void start(final JavaPlugin plugin) {
this.plugin = plugin;
if (initManager != null) {
initManager.start();
}
if (criticalLoadFailure) {
LogUtil.error("GrimAC encountered one or more critical error(s) during initialization.");
Bukkit.getPluginManager().disablePlugin(plugin);
}
initManager.start();
}
public void stop(final JavaPlugin plugin) {
this.plugin = plugin;
if (initManager != null) {
initManager.stop();
}
}
// Method to signal a critical failure
public void signalCriticalFailure() {
criticalLoadFailure = true;
initManager.stop();
}
}
@@ -22,7 +22,6 @@ import org.bukkit.plugin.ServicePriority;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -44,10 +43,8 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, I
}
@Override
public @Nullable GrimUser getGrimUser(UUID uuid) {
Player player = Bukkit.getPlayer(uuid);
if (player == null) return null;
return getGrimUser(player);
public void setServerName(String name) {
variableReplacements.put("%server%", user -> name);
}
@Getter
@@ -56,7 +53,8 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, I
@Getter
private final Map<String, String> staticReplacements = new ConcurrentHashMap<>();
public String replaceVariables(GrimUser user, String content) {
public String replaceVariables(GrimUser user, String content, boolean colors) {
if (colors) content = ChatColor.translateAlternateColorCodes('&', content);
for (Map.Entry<String, String> entry : staticReplacements.entrySet()) {
content = content.replace(entry.getKey(), entry.getValue());
}
@@ -116,16 +114,6 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, I
return configManager;
}
@Override
public boolean hasStarted() {
return started;
}
@Override
public int getCurrentTick() {
return GrimAPI.INSTANCE.getTickManager().currentTick;
}
private ConfigManager configManager = null;
private final ConfigManagerFileImpl configManagerFile = new ConfigManagerFileImpl();
private boolean started = false;
@@ -220,7 +208,7 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, I
variableReplacements.putIfAbsent("%tps%", user -> String.format("%.2f", SpigotReflectionUtil.getTPS()));
variableReplacements.putIfAbsent("%version%", GrimUser::getVersionName);
// static variables
staticReplacements.put("%prefix%", ChatColor.translateAlternateColorCodes('&', GrimAPI.INSTANCE.getConfigManager().getPrefix()));
staticReplacements.putIfAbsent("%prefix%", ChatColor.translateAlternateColorCodes('&', GrimAPI.INSTANCE.getConfigManager().getPrefix()));
staticReplacements.putIfAbsent("%grim_version%", getGrimVersion());
}
+29 -80
View File
@@ -5,10 +5,9 @@ import ac.grim.grimac.api.AbstractCheck;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.api.events.FlagEvent;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.common.ConfigReloadObserver;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import lombok.Getter;
import lombok.Setter;
@@ -16,7 +15,7 @@ import org.bukkit.Bukkit;
// Class from https://github.com/Tecnio/AntiCheatBase/blob/master/src/main/java/me/tecnio/anticheat/check/Check.java
@Getter
public class Check extends GrimProcessor implements AbstractCheck {
public class Check implements AbstractCheck, ConfigReloadObserver {
protected final GrimPlayer player;
public double violations;
@@ -32,16 +31,20 @@ public class Check extends GrimProcessor implements AbstractCheck {
private boolean experimental;
@Setter
private boolean isEnabled;
private boolean exempted;
private boolean exemptPermission;
private boolean noSetbackPermission;
private boolean noModifyPacketPermission;
@Override
public boolean isExperimental() {
return experimental;
}
public Check(final GrimPlayer player) {
this.player = player;
final CheckData checkData = this.getClass().getAnnotation(CheckData.class);
if (checkData != null) {
final Class<?> checkClass = this.getClass();
if (checkClass.isAnnotationPresent(CheckData.class)) {
final CheckData checkData = checkClass.getAnnotation(CheckData.class);
this.checkName = checkData.name();
this.configName = checkData.configName();
// Fall back to check name
@@ -53,31 +56,23 @@ public class Check extends GrimProcessor implements AbstractCheck {
this.description = checkData.description();
this.displayName = this.checkName;
}
//
reload();
}
public boolean shouldModifyPackets() {
return isEnabled && !player.disableGrim && !player.noModifyPacketPermission && !exemptPermission;
return isEnabled && !player.disableGrim && !player.noModifyPacketPermission && !exempted;
}
public void updatePermissions() {
public void updateExempted() {
if (player.bukkitPlayer == null || checkName == null) return;
FoliaScheduler.getEntityScheduler().run(
player.bukkitPlayer,
GrimAPI.INSTANCE.getPlugin(),
t -> {
final String id = checkName.toLowerCase();
exemptPermission = player.bukkitPlayer.hasPermission("grim.exempt." + id);
noSetbackPermission = player.bukkitPlayer.hasPermission("grim.nosetback." + id);
noModifyPacketPermission = player.bukkitPlayer.hasPermission("grim.nomodifypacket." + id);
},
() -> {}
);
FoliaScheduler.getEntityScheduler().run(player.bukkitPlayer, GrimAPI.INSTANCE.getPlugin(),
t -> exempted = player.bukkitPlayer.hasPermission("grim.exempt." + checkName.toLowerCase()),
() -> {});
}
public final boolean flagAndAlert(String verbose) {
if (flag(verbose)) {
if (flag()) {
alert(verbose);
return true;
}
@@ -89,21 +84,16 @@ public class Check extends GrimProcessor implements AbstractCheck {
}
public final boolean flag() {
return flag("");
}
private long lastViolationTime;
public final boolean flag(String verbose) {
if (player.disableGrim || (experimental && !player.isExperimentalChecks()) || exemptPermission)
if (player.disableGrim || (experimental && !player.isExperimentalChecks()) || exempted)
return false; // Avoid calling event if disabled
FlagEvent event = new FlagEvent(player, this, verbose);
FlagEvent event = new FlagEvent(player, this);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return false;
player.punishmentManager.handleViolation(this);
lastViolationTime = System.currentTimeMillis();
violations++;
return true;
}
@@ -116,18 +106,6 @@ public class Check extends GrimProcessor implements AbstractCheck {
return false;
}
public final boolean flagAndAlertWithSetback() {
return flagAndAlertWithSetback("");
}
public final boolean flagAndAlertWithSetback(String verbose) {
if (flagAndAlert(verbose)) {
setbackIfAboveSetbackVL();
return true;
}
return false;
}
public final void reward() {
violations = Math.max(0, violations - decay);
}
@@ -137,10 +115,9 @@ public class Check extends GrimProcessor implements AbstractCheck {
decay = configuration.getDoubleElse(configName + ".decay", decay);
setbackVL = configuration.getDoubleElse(configName + ".setbackvl", setbackVL);
displayName = configuration.getStringElse(configName + ".displayname", checkName);
description = configuration.getStringElse(configName + ".description", description);
if (setbackVL == -1) setbackVL = Double.MAX_VALUE;
updatePermissions();
updateExempted();
onReload(configuration);
}
@@ -154,14 +131,14 @@ public class Check extends GrimProcessor implements AbstractCheck {
}
public boolean setbackIfAboveSetbackVL() {
if (shouldSetback()) {
if (getViolations() > setbackVL) {
return player.getSetbackTeleportUtil().executeViolationSetback();
}
return false;
}
public boolean shouldSetback() {
return !noSetbackPermission && violations > setbackVL;
public boolean isAboveSetbackVl() {
return getViolations() > setbackVL;
}
public String formatOffset(double offset) {
@@ -173,37 +150,9 @@ public class Check extends GrimProcessor implements AbstractCheck {
packetType == PacketType.Play.Client.WINDOW_CONFIRMATION;
}
public boolean isFlying(PacketTypeCommon packetType) {
return WrapperPlayClientPlayerFlying.isFlying(packetType);
}
public boolean isUpdate(PacketTypeCommon packetType) {
return isFlying(packetType)
|| packetType == PacketType.Play.Client.CLIENT_TICK_END
|| isTransaction(packetType);
}
public boolean isTickPacket(PacketTypeCommon packetType) {
if (isTickPacketIncludingNonMovement(packetType)) {
if (isFlying(packetType)) {
return !player.packetStateData.lastPacketWasTeleport && !player.packetStateData.lastPacketWasOnePointSeventeenDuplicate;
}
return true;
}
return false;
}
public boolean isTickPacketIncludingNonMovement(PacketTypeCommon packetType) {
// On 1.21.2+ fall back to the TICK_END packet IF the player did not send a movement packet for their tick
// TickTimer checks to see if player did not send a tick end packet before new flying packet is sent
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)
&& !player.packetStateData.didSendMovementBeforeTickEnd) {
if (packetType == PacketType.Play.Client.CLIENT_TICK_END) {
return true;
}
}
return isFlying(packetType);
@Override
public void reload() {
reload(GrimAPI.INSTANCE.getConfigManager().getConfig());
}
}
@@ -1,17 +0,0 @@
package ac.grim.grimac.checks;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.AbstractProcessor;
import ac.grim.grimac.api.config.ConfigReloadable;
import ac.grim.grimac.utils.common.ConfigReloadObserver;
public abstract class GrimProcessor implements AbstractProcessor, ConfigReloadable, ConfigReloadObserver {
// Not everything has to be a check for it to process packets & be configurable
@Override
public void reload() {
reload(GrimAPI.INSTANCE.getConfigManager().getConfig());
}
}
@@ -1,23 +0,0 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.player.GrimPlayer;
import org.bukkit.entity.Player;
public abstract class AbstractDebugHandler extends Check {
protected final GrimPlayer grimPlayer;
public AbstractDebugHandler(GrimPlayer grimPlayer) {
super(grimPlayer);
this.grimPlayer = grimPlayer;
}
public GrimPlayer getPlayer() {
return grimPlayer;
}
public abstract void toggleListener(Player player);
public abstract boolean toggleConsoleOutput();
}
@@ -1,198 +0,0 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Handles debug visualization of hitboxes and reach calculations for GrimAC.
* Sends debug data to clients through plugin messages that can be visualized by compatible clients.
*/
public class HitboxDebugHandler extends AbstractDebugHandler {
/**
* Set of players currently listening to hitbox debug data
*/
private final Set<Player> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
/**
* Creates a new HitboxDebugHandler for the specified player
*
* @param grimPlayer The GrimAC player instance to debug
*/
public HitboxDebugHandler(GrimPlayer grimPlayer) {
super(grimPlayer);
}
/**
* Toggles whether a player receives hitbox debug visualization data.
* If the player is already listening, they will be removed. If not, they will be added.
*
* @param player The player to toggle debug visualization for
*/
@Override
public void toggleListener(Player player) {
if (!listeners.remove(player)) listeners.add(player);
}
@Override
public boolean toggleConsoleOutput() {
throw new UnsupportedOperationException();
}
/**
* Sends debug visualization data for reach calculations to all listening players.
* The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations.
*
* @param hitboxes Map of entity IDs to their collision boxes
* @param targetEntities Set of entity IDs that are considered targets
* @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights
* @param basePos Base position before eye height adjustments
* @param isPrediction Whether these hitboxes are from a prediction calculation
*
* Packet Format (Version 1):
* - Byte: Version (0)
* - Byte: Global flags
* - Bit 0: isPrediction
* - Bits 1-7: Reserved
* - Double: Player reach/interact distance
* - Vector3d: Base position (3 doubles)
* - VarInt: Number of ray traces
* - For each ray trace:
* - Double: Eye height delta
* - Vector3d: Look vector (3 doubles)
* - VarInt: Number of hitboxes
* - For each hitbox:
* - VarInt: Entity ID
* - Byte: Flags
* - Bit 0: Is target entity
* - Bit 1: Is no collision
* - Bits 2-7: Reserved
* - If not NoCollisionBox:
* - Double: minX
* - Double: minY
* - Double: minZ
* - Double: maxX
* - Double: maxY
* - Double: maxZ
*/
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
List<Pair<Vector, Double>> lookVecsAndEyeHeights, Vector basePos,
boolean isPrediction, double reachDistance) {
if (!isEnabled()) return;
ByteBuf buffer = Unpooled.buffer();
try {
// Version Header
buffer.writeByte(1);
// Global Flags Header
// Pack boolean flags into a single byte
byte global_flags = 0;
global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction?
// Bits 2-7 reserved for future use
buffer.writeByte(global_flags);
// Write reach distance
buffer.writeDouble(reachDistance);
// Write base position
writeVector(buffer, basePos);
// Write number of ray traces
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
// Write all possible ray traces
for (Pair<Vector, Double> pair : lookVecsAndEyeHeights) {
Vector lookVec = pair.first();
double eyeHeight = pair.second();
// Write eye height delta and look vector
// we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer
// then they need to be so grim can report correct reach distance for too far away hits
buffer.writeDouble(eyeHeight);
writeVector(buffer, lookVec);
}
// Write number of hitboxes
ByteBufHelper.writeVarInt(buffer, hitboxes.size());
// Write hitbox data
for (Map.Entry<Integer, CollisionBox> entry : hitboxes.entrySet()) {
int entityId = entry.getKey();
CollisionBox box = entry.getValue();
// Write entity ID
ByteBufHelper.writeVarInt(buffer, entityId);
// Pack boolean flags into a single byte
byte flags = 0;
flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target
flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision
// Bits 2-7 reserved for future use
buffer.writeByte(flags);
// Write box coordinates if it's not a NoCollisionBox
if ((flags & 2) == 0) {
SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box;
buffer.writeDouble(simpleCollisionBox.minX);
buffer.writeDouble(simpleCollisionBox.minY);
buffer.writeDouble(simpleCollisionBox.minZ);
buffer.writeDouble(simpleCollisionBox.maxX);
buffer.writeDouble(simpleCollisionBox.maxY);
buffer.writeDouble(simpleCollisionBox.maxZ);
}
}
// Convert buffer to byte array
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
// Create and send packet
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(
"grim:debug_hitbox",
data
);
// Send to all listeners
for (Player listener : listeners) {
if (listener != null) {
PacketEvents.getAPI().getPlayerManager().sendPacket(listener, packet);
}
}
} finally {
// Release buffer to prevent memory leaks
buffer.release();
}
}
public boolean isEnabled() {
return !listeners.isEmpty();
}
/**
* Helper method to write a Vector to the ByteBuf
* @param buffer The buffer to write to
* @param vector The vector to write
*/
private void writeVector(ByteBuf buffer, Vector vector) {
buffer.writeDouble(vector.getX());
buffer.writeDouble(vector.getY());
buffer.writeDouble(vector.getZ());
}
}
@@ -16,7 +16,7 @@ public class AimDuplicateLook extends Check implements RotationCheck {
@Override
public void process(final RotationUpdate rotationUpdate) {
if (player.packetStateData.lastPacketWasTeleport || player.packetStateData.lastPacketWasOnePointSeventeenDuplicate || player.compensatedEntities.self.getRiding() != null) {
if (player.packetStateData.lastPacketWasTeleport || player.packetStateData.lastPacketWasOnePointSeventeenDuplicate || player.compensatedEntities.getSelf().getRiding() != null) {
exempt = true;
return;
}
@@ -20,8 +20,7 @@ public class AimModulo360 extends Check implements RotationCheck {
@Override
public void process(final RotationUpdate rotationUpdate) {
// Exempt for teleport or entering a vehicle due to rotation reset
if (player.packetStateData.lastPacketWasTeleport || player.vehicleData.wasVehicleSwitch) {
if (player.packetStateData.lastPacketWasTeleport) {
lastDeltaYaw = rotationUpdate.getDeltaXRot();
return;
}
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientHeldItemChange;
@CheckData(name = "BadPacketsA", description = "Sent duplicate slot id")
@CheckData(name = "BadPacketsA")
public class BadPacketsA extends Check implements PacketCheck {
int lastSlot = -1;
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.vehicle;
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -8,9 +8,9 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientSteerVehicle;
@CheckData(name = "VehicleA", description = "Impossible input values")
public class VehicleA extends Check implements PacketCheck {
public VehicleA(GrimPlayer player) {
@CheckData(name = "BadPacketsB")
public class BadPacketsB extends Check implements PacketCheck {
public BadPacketsB(GrimPlayer player) {
super(player);
}
@@ -5,11 +5,10 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsC", description = "Interacted with self")
@CheckData(name = "BadPacketsC")
public class BadPacketsC extends Check implements PacketCheck {
public BadPacketsC(GrimPlayer player) {
super(player);
@@ -18,7 +17,6 @@ public class BadPacketsC extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (player.gamemode == GameMode.SPECTATOR) return;
if (new WrapperPlayClientInteractEntity(event).getEntityId() == player.entityID) {
// Instant ban
if (flagAndAlert() && shouldModifyPackets()) {
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "BadPacketsD", description = "Impossible pitch")
@CheckData(name = "BadPacketsD")
public class BadPacketsD extends Check implements PacketCheck {
public BadPacketsD(GrimPlayer player) {
super(player);
@@ -4,11 +4,8 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "BadPacketsE")
@@ -21,14 +18,13 @@ public class BadPacketsE extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
final boolean isViaPleaseStopUsingProtocolHacksOnYourServer = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) || PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_2);
if (event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION ||
event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION) {
noReminderTicks = 0;
} else if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) {
noReminderTicks++;
} else if (event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE
|| (isViaPleaseStopUsingProtocolHacksOnYourServer && player.inVehicle())) {
} else if (event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE ||
event.getPacketType() == PacketType.Play.Client.VEHICLE_MOVE) {
noReminderTicks = 0; // Exempt vehicles
}
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "BadPacketsF", description = "Sent duplicate sprinting status")
@CheckData(name = "BadPacketsF")
public class BadPacketsF extends Check implements PacketCheck {
public boolean lastSprinting;
public boolean exemptNext = true; // Support 1.14+ clients starting on either true or false sprinting, we don't know
@@ -8,9 +8,10 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "BadPacketsG", description = "Sent duplicate sneaking status")
@CheckData(name = "BadPacketsG")
public class BadPacketsG extends Check implements PacketCheck {
private boolean lastSneaking, respawn;
boolean wasTeleport;
boolean lastSneaking;
public BadPacketsG(GrimPlayer player) {
super(player);
@@ -18,12 +19,13 @@ public class BadPacketsG extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
wasTeleport = player.packetStateData.lastPacketWasTeleport || wasTeleport;
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction packet = new WrapperPlayClientEntityAction(event);
if (packet.getAction() == WrapperPlayClientEntityAction.Action.START_SNEAKING) {
// The player may send two START_SNEAKING packets if they respawned
if (lastSneaking && !respawn) {
if (lastSneaking && !wasTeleport) {
if (flagAndAlert("state=true") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -31,9 +33,8 @@ public class BadPacketsG extends Check implements PacketCheck {
} else {
lastSneaking = true;
}
respawn = false;
} else if (packet.getAction() == WrapperPlayClientEntityAction.Action.STOP_SNEAKING) {
if (!lastSneaking && !respawn) {
if (!lastSneaking && !wasTeleport) {
if (flagAndAlert("state=false") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -41,13 +42,7 @@ public class BadPacketsG extends Check implements PacketCheck {
} else {
lastSneaking = false;
}
respawn = false;
}
}
}
public void handleRespawn() {
// Clients could potentially not send a STOP_SNEAKING packet when they die, so we need to track it
respawn = true;
}
}
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsH", description = "Did not swing for attack")
@CheckData(name = "BadPacketsH")
public class BadPacketsH extends Check implements PacketCheck {
// 1.9 packet order: INTERACT -> ANIMATION
@@ -41,9 +41,9 @@ public class BadPacketsH extends Check implements PacketCheck {
// I will simply disable this check for 1.8- clients on 1.9+ servers as I can't be bothered to find a way around this.
// Stop supporting such old clients on modern servers!
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) return;
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_8)) return;
if (!sentAnimation && flagAndAlert() && shouldModifyPackets()) {
if (!sentAnimation && flagAndAlert()) {
event.setCancelled(true);
player.onPacketCancel();
}
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerAbilities;
@CheckData(name = "BadPacketsI", description = "Claimed to be flying while unable to fly")
@CheckData(name = "BadPacketsI")
public class BadPacketsI extends Check implements PacketCheck {
public BadPacketsI(GrimPlayer player) {
super(player);
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.vehicle;
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -7,16 +7,16 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "VehicleB", description = "Claimed to be in a vehicle while not in a vehicle")
public class VehicleB extends Check implements PacketCheck {
public VehicleB(GrimPlayer player) {
@CheckData(name = "BadPacketsJ")
public class BadPacketsJ extends Check implements PacketCheck {
public BadPacketsJ(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE) {
if (!player.inVehicle()) {
if (!player.compensatedEntities.getSelf().inVehicle()) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
@CheckData(name = "BadPacketsK", description = "Sent spectate packets while not in spectator mode")
@CheckData(name = "BadPacketsK")
public class BadPacketsK extends Check implements PacketCheck {
public BadPacketsK(GrimPlayer player) {
super(player);
@@ -8,11 +8,13 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import java.util.Locale;
@CheckData(name = "BadPacketsL", description = "Sent impossible dig packet")
// checks for impossible dig packets
@CheckData(name = "BadPacketsL")
public class BadPacketsL extends Check implements PacketCheck {
public BadPacketsL(GrimPlayer player) {
@@ -28,20 +30,19 @@ public class BadPacketsL extends Check implements PacketCheck {
// 1.8 and above clients always send digging packets that aren't used for digging at 0, 0, 0, facing DOWN
// 1.7 and below clients do the same, except use SOUTH for RELEASE_USE_ITEM
final int expectedFace = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10) && packet.getAction() == DiggingAction.RELEASE_USE_ITEM
? 255 : 0;
final BlockFace expectedFace = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10) && packet.getAction() == DiggingAction.RELEASE_USE_ITEM
? BlockFace.SOUTH : BlockFace.DOWN;
if (packet.getBlockFaceId() != expectedFace
if (packet.getBlockFace() != expectedFace
|| packet.getBlockPosition().getX() != 0
|| packet.getBlockPosition().getY() != 0
|| packet.getBlockPosition().getZ() != 0
|| packet.getSequence() != 0
) {
if (flagAndAlert("pos="
+ packet.getBlockPosition().getX() + ", " + packet.getBlockPosition().getY() + ", " + packet.getBlockPosition().getZ()
+ ", face=" + packet.getBlockFace()
if (flagAndAlert("xyzF="
+ packet.getBlockPosition().getX() + ", " + packet.getBlockPosition().getY() + ", " + packet.getBlockPosition().getZ() + ", " + packet.getBlockFace()
+ ", sequence=" + packet.getSequence()
+ ", action=" + packet.getAction().toString().toLowerCase(Locale.ROOT)
+ ", action=" + packet.getAction().toString().toLowerCase(Locale.ROOT).replace("_", " ") + " v" + player.getVersionName()
) && shouldModifyPackets() && packet.getAction() != DiggingAction.RELEASE_USE_ITEM) {
event.setCancelled(true);
player.onPacketCancel();
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.packetorder;
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -9,33 +9,25 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "PacketOrderC")
public class PacketOrderC extends Check implements PacketCheck {
public PacketOrderC(final GrimPlayer player) {
@CheckData(name = "BadPacketsM", experimental = true)
public class BadPacketsM extends Check implements PacketCheck {
public BadPacketsM(final GrimPlayer player) {
super(player);
}
// 1.7 players do not send INTERACT_AT, so we cannot check them
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10);
private boolean sentInteractAt = false;
private int requiredEntity;
private InteractionHand requiredHand;
private boolean requiredSneaking;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (exempt) {
return;
}
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && !exempt) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
final WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
final WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
final PacketEntity entity = player.compensatedEntities.entityMap.get(packet.getEntityId());
final PacketEntity entity = player.compensatedEntities.entityMap.get(wrapper.getEntityId());
// For armor stands, vanilla clients send:
// - when renaming the armor stand or in spectator mode: INTERACT_AT + INTERACT
@@ -43,49 +35,27 @@ public class PacketOrderC extends Check implements PacketCheck {
// Just exempt armor stands to be safe
if (entity != null && entity.getType() == EntityTypes.ARMOR_STAND) return;
final boolean sneaking = packet.isSneaking().orElse(false);
switch (packet.getAction()) {
switch (wrapper.getAction()) {
// INTERACT_AT then INTERACT
case INTERACT:
if (!sentInteractAt) {
if (flagAndAlert("Skipped Interact-At") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else if (packet.getEntityId() != requiredEntity || packet.getHand() != requiredHand || sneaking != requiredSneaking) {
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + packet.getEntityId()
+ ", requiredHand=" + requiredHand + ", hand=" + packet.getHand()
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
if (flagAndAlert("Missed Interact-At") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentInteractAt = false;
break;
case INTERACT_AT:
if (sentInteractAt) {
if (flagAndAlert("Skipped Interact") && shouldModifyPackets()) {
if (flagAndAlert("Missed Interact") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
requiredHand = packet.getHand();
requiredEntity = packet.getEntityId();
requiredSneaking = sneaking;
sentInteractAt = true;
break;
}
}
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) {
if (sentInteractAt) {
sentInteractAt = false;
flagAndAlert("Skipped Interact (Tick)");
}
}
}
}
@@ -5,7 +5,7 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "BadPacketsN", setback = 0)
@CheckData(name = "BadPacketsN")
public class BadPacketsN extends Check implements PacketCheck {
public BadPacketsN(final GrimPlayer player) {
super(player);
@@ -8,7 +8,6 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow.WindowClickType;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
@CheckData(name = "BadPacketsP", experimental = true)
@@ -34,39 +33,35 @@ public class BadPacketsP extends Check implements PacketCheck {
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
WrapperPlayClientClickWindow wrapper = new WrapperPlayClientClickWindow(event);
WindowClickType clickType = wrapper.getWindowClickType();
int clickType = wrapper.getWindowClickType().ordinal();
int button = wrapper.getButton();
// TODO: Adjust for containers
boolean flag;
boolean flag = false;
//TODO: Adjust for containers
switch (clickType) {
case PICKUP:
case QUICK_MOVE:
case CLONE:
flag = button > 2 || button < 0;
case 0:
case 1:
case 4:
if (button != 0 && button != 1) flag = true;
break;
case SWAP:
flag = (button > 8 || button < 0) && button != 40;
case 2:
if ((button > 8 || button < 0) && button != 40) flag = true;
break;
case THROW:
flag = button != 0 && button != 1;
case 3:
if (button != 2) flag = true;
break;
case QUICK_CRAFT:
flag = button == 3 || button == 7 || button > 10 || button < 0;
case 5:
if (button == 3 || button == 7 || button > 10 || button < 0) flag = true;
break;
case PICKUP_ALL:
flag = button != 0;
case 6:
if (button != 0) flag = true;
break;
case UNKNOWN:
flag = false;
break;
default:
throw new IllegalStateException("Impossible clickType; Compiler does not know this is unreachable!");
}
// Allowing this to false flag to debug and find issues faster
//Allowing this to false flag to debug and find issues faster
if (flag) {
if (flagAndAlert("clickType=" + clickType.toString().toLowerCase() + ", button=" + button + (wrapper.getWindowId() == containerId ? ", container=" + containerType : "")) && shouldModifyPackets()) {
if (flagAndAlert("clickType=" + clickType + " button=" + button + (wrapper.getWindowId() == containerId ? " container=" + containerType : "")) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
@@ -19,10 +19,8 @@ public class BadPacketsQ extends Check implements PacketCheck {
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
// you are able to send negative jump boost, how and why!?
if (Math.abs(wrapper.getJumpBoost()) > 100
|| wrapper.getEntityId() != player.entityID
|| wrapper.getAction() != Action.START_JUMPING_WITH_HORSE && wrapper.getJumpBoost() != 0) {
if (wrapper.getJumpBoost() < 0 || wrapper.getJumpBoost() > 100 || wrapper.getEntityId() != player.entityID || (wrapper.getAction() != Action.START_JUMPING_WITH_HORSE && wrapper.getJumpBoost() != 0)) {
if (flagAndAlert("boost=" + wrapper.getJumpBoost() + ", action=" + wrapper.getAction() + ", entity=" + wrapper.getEntityId()) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -25,8 +25,8 @@ public class BadPacketsR extends Check implements PacketCheck {
long ms = (player.getPlayerClockAtLeast() - clock) / 1000000L;
long diff = (System.currentTimeMillis() - lastTransTime);
if (diff > 2000 && ms > 2000) {
if (positions == 0 && clock != 0 && player.gamemode != GameMode.SPECTATOR && !player.compensatedEntities.self.isDead) {
flag("time=" + ms + "ms, " + "lst=" + diff + "ms, positions=" + positions);
if (positions == 0 && clock != 0 && player.gamemode != GameMode.SPECTATOR && !player.compensatedEntities.getSelf().isDead) {
flagAndAlert("time=" + ms + "ms, " + "lst=" + diff + "ms, positions=" + positions);
} else {
reward();
}
@@ -39,10 +39,11 @@ public class BadPacketsR extends Check implements PacketCheck {
}
//
if ((event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION ||
event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION) && !player.inVehicle()) {
event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION) && !player.compensatedEntities.getSelf().inVehicle()) {
positions++;
} else if ((event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE || event.getPacketType() == PacketType.Play.Client.VEHICLE_MOVE)
&& player.inVehicle()) {
} else if ((event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE ||
event.getPacketType() == PacketType.Play.Client.VEHICLE_MOVE)
&& player.compensatedEntities.getSelf().inVehicle()) {
positions++;
}
}
@@ -49,7 +49,7 @@ public class BadPacketsT extends Check implements PacketCheck {
// 27/12/2023 - Dynamic values for more than just one entity type?
// 28/12/2023 - Player-only is fine
// 30/12/2023 - Expansions differ in 1.9+
final float scale = (float) packetEntity.getAttributeValue(Attributes.SCALE);
final float scale = (float) packetEntity.getAttributeValue(Attributes.GENERIC_SCALE);
if (targetVector.y > (minVerticalDisplacement * scale) && targetVector.y < (maxVerticalDisplacement * scale)
&& Math.abs(targetVector.x) < (maxHorizontalDisplacement * scale)
&& Math.abs(targetVector.z) < (maxHorizontalDisplacement * scale)) {
@@ -14,7 +14,7 @@ import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
@CheckData(name = "BadPacketsU", description = "Sent impossible use item packet")
@CheckData(name = "BadPacketsU", experimental = true)
public class BadPacketsU extends Check implements PacketCheck {
public BadPacketsU(GrimPlayer player) {
super(player);
@@ -5,36 +5,26 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsV", description = "Did not move far enough", experimental = true)
@CheckData(name = "BadPacketsV", experimental = true)
public class BadPacketsV extends Check implements PacketCheck {
public BadPacketsV(GrimPlayer player) {
super(player);
}
private int noReminderTicks;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.canSkipTicks() && isTickPacket(event.getPacketType())) {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION || event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION) {
int positionAtLeastEveryNTicks = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) ? 20 : 19;
if (noReminderTicks < positionAtLeastEveryNTicks && !player.uncertaintyHandler.lastTeleportTicks.hasOccurredSince(1)) {
final double deltaSq = new WrapperPlayClientPlayerFlying(event).getLocation().getPosition()
.distanceSquared(new Vector3d(player.lastX, player.lastY, player.lastZ));
if (deltaSq <= player.getMovementThreshold() * player.getMovementThreshold()) {
flagAndAlert("delta=" + Math.sqrt(deltaSq));
}
}
noReminderTicks = 0;
} else {
noReminderTicks++;
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity interactEntity = new WrapperPlayClientInteractEntity(event);
if (interactEntity.getAction() != WrapperPlayClientInteractEntity.InteractAction.ATTACK) return;
if (!player.packetStateData.isSlowedByUsingItem()) return;
ItemStack itemInUse = player.getInventory().getItemInHand(player.packetStateData.eatingHand);
if (flagAndAlert("UseItem=" + itemInUse.getType().getName().getKey()) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
@@ -5,7 +5,7 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "BadPacketsW", description = "Interacted with non-existent entity", experimental = true)
@CheckData(name = "BadPacketsW", experimental = true)
public class BadPacketsW extends Check implements PacketCheck {
public BadPacketsW(GrimPlayer player) {
super(player);
@@ -1,83 +1,73 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "BadPacketsX", experimental = true)
public class BadPacketsX extends Check implements PostPredictionCheck {
@CheckData(name = "BadPacketsX")
public class BadPacketsX extends Check implements PacketCheck {
public BadPacketsX(GrimPlayer player) {
super(player);
}
private boolean sprint;
private boolean sneak;
private int flags;
private int lastTick;
private boolean didLastFlag;
private Vector3i lastBreakLoc;
private StateType lastBlockType;
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) {
if (flags > 0) {
setbackIfAboveSetbackVL();
}
public final boolean noFireHitbox = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_15_2);
flags = 0;
public final void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig, StateType block) {
if (dig.getAction() != DiggingAction.START_DIGGING && dig.getAction() != DiggingAction.FINISHED_DIGGING)
return;
// Fixes false from breaking kelp underwater
// The client sends two start digging packets to the server both in the same tick. BadPacketsX gets called twice, doesn't false the first time, but falses the second
// One ends up breaking the kelp, the other ends up doing nothing besides falsing this check because we think they're trying to mine water
// I am explicitly making this patch as narrow and specific as possible to potentially discover other blocks that exhibit similar behaviour
int newTick = GrimAPI.INSTANCE.getTickManager().currentTick;
if (lastTick == newTick
&& lastBreakLoc.equals(dig.getBlockPosition())
&& !didLastFlag
&& lastBlockType.getHardness() == 0.0F
&& lastBlockType.getBlastResistance() == 0.0F
&& block == StateTypes.WATER
) {
return;
}
lastTick = newTick;
lastBreakLoc = dig.getBlockPosition();
lastBlockType = block;
if (player.isTickingReliablyFor(3)) {
for (; flags > 0; flags--) {
flagAndAlertWithSetback();
}
}
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.getInventory().getHeldItem().is(ItemTypes.LIGHT) || player.getInventory().getOffHand().is(ItemTypes.LIGHT)))
|| block.isAir()
|| block == StateTypes.WATER
|| block == StateTypes.LAVA
|| block == StateTypes.BUBBLE_COLUMN
|| block == StateTypes.MOVING_PISTON
|| (block == StateTypes.FIRE && noFireHitbox)
// or the client claims to have broken an unbreakable block
|| block.getHardness() == -1.0f && dig.getAction() == DiggingAction.FINISHED_DIGGING;
flags = 0;
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
sprint = sneak = false;
return;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
switch (wrapper.getAction()) {
case START_SNEAKING:
case STOP_SNEAKING:
if (sneak) {
if (player.canSkipTicks() || flagAndAlert()) {
flags++;
}
}
sneak = true;
break;
case START_SPRINTING:
case STOP_SPRINTING:
if (player.inVehicle()) {
return;
}
if (sprint) {
if (player.canSkipTicks() || flagAndAlert()) {
flags++;
}
}
sprint = true;
break;
default:
// Handle other cases if necessary
break;
}
if (invalid && flagAndAlert("block=" + block.getName() + ", type=" + dig.getAction()) && shouldModifyPackets()) {
didLastFlag = true;
event.setCancelled(true);
player.onPacketCancel();
} else {
didLastFlag = false;
}
}
}
@@ -8,7 +8,10 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientHeldItemChange;
@CheckData(name = "BadPacketsY", description = "Sent out of bounds slot id")
/**
* Checks for out of bounds slot changes
*/
@CheckData(name = "BadPacketsY")
public class BadPacketsY extends Check implements PacketCheck {
public BadPacketsY(GrimPlayer player) {
super(player);
@@ -1,22 +1,24 @@
package ac.grim.grimac.checks.impl.breaking;
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import static ac.grim.grimac.events.packets.patch.ResyncWorldUtil.resyncPosition;
import static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage;
@CheckData(name = "WrongBreak")
public class WrongBreak extends Check implements BlockBreakCheck {
public WrongBreak(final GrimPlayer player) {
@CheckData(name = "BadPacketsZ")
public class BadPacketsZ extends Check implements PacketCheck {
public BadPacketsZ(final GrimPlayer player) {
super(player);
}
@@ -38,10 +40,9 @@ public class WrongBreak extends Check implements BlockBreakCheck {
return player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || getBlockDamage(player, pos) < 1;
}
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
final Vector3i pos = blockBreak.position;
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig) {
if (dig.getAction() == DiggingAction.START_DIGGING) {
final Vector3i pos = dig.getBlockPosition();
lastBlockWasInstantBreak = getBlockDamage(player, pos) >= 1;
lastCancelledBlock = null;
@@ -49,15 +50,24 @@ public class WrongBreak extends Check implements BlockBreakCheck {
lastBlock = pos;
}
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
final Vector3i pos = blockBreak.position;
if (dig.getAction() == DiggingAction.CANCELLED_DIGGING) {
final Vector3i pos = dig.getBlockPosition();
if (!shouldExempt(pos) && !pos.equals(lastBlock)) {
if (shouldExempt(pos)) {
lastCancelledBlock = pos;
lastLastBlock = null;
lastBlock = null;
return;
}
if (!pos.equals(lastBlock)) {
// https://github.com/GrimAnticheat/Grim/issues/1512
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && pos.equals(lastCancelledBlock))) {
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
event.setCancelled(true);
player.onPacketCancel();
resyncPosition(player, pos);
}
}
}
@@ -69,21 +79,24 @@ public class WrongBreak extends Check implements BlockBreakCheck {
return;
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
final Vector3i pos = blockBreak.position;
if (dig.getAction() == DiggingAction.FINISHED_DIGGING) {
final Vector3i pos = dig.getBlockPosition();
// when a player looks away from the mined block, they send a cancel, and if they look at it again, they don't send another start. (thanks mojang!)
if (!pos.equals(lastCancelledBlock) && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && !pos.equals(lastBlock)) {
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
event.setCancelled(true);
player.onPacketCancel();
resyncPosition(player, pos);
}
}
}
lastCancelledBlock = null;
// 1.14.4+ clients don't send another start break in protected regions
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) {
lastCancelledBlock = null;
lastLastBlock = null;
lastBlock = null;
}
@@ -1,70 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
@CheckData(name = "AirLiquidBreak", description = "Breaking a block that cannot be broken")
public class AirLiquidBreak extends Check implements BlockBreakCheck {
public AirLiquidBreak(GrimPlayer player) {
super(player);
}
private int lastTick;
private boolean didLastFlag;
private Vector3i lastBreakLoc;
private StateType lastBlockType;
public final boolean noFireHitbox = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_15_2);
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action != DiggingAction.START_DIGGING && blockBreak.action != DiggingAction.FINISHED_DIGGING)
return;
final StateType block = blockBreak.block.getType();
// Fixes false from breaking kelp underwater
// The client sends two start digging packets to the server both in the same tick. AirLiquidBreak gets called twice, doesn't false the first time, but falses the second
// One ends up breaking the kelp, the other ends up doing nothing besides falsing this check because we think they're trying to mine water
// I am explicitly making this patch as narrow and specific as possible to potentially discover other blocks that exhibit similar behaviour
int newTick = GrimAPI.INSTANCE.getTickManager().currentTick;
if (lastTick == newTick
&& lastBreakLoc.equals(blockBreak.position)
&& !didLastFlag
&& lastBlockType.getHardness() == 0.0F
&& lastBlockType.getBlastResistance() == 0.0F
&& block == StateTypes.WATER
) return;
lastTick = newTick;
lastBreakLoc = blockBreak.position;
lastBlockType = block;
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.getInventory().getHeldItem().is(ItemTypes.LIGHT) || player.getInventory().getOffHand().is(ItemTypes.LIGHT)))
|| block.isAir()
|| block == StateTypes.WATER
|| block == StateTypes.LAVA
|| block == StateTypes.BUBBLE_COLUMN
|| block == StateTypes.MOVING_PISTON
|| block == StateTypes.FIRE && noFireHitbox
// or the client claims to have broken an unbreakable block
|| block.getHardness() == -1.0f && blockBreak.action == DiggingAction.FINISHED_DIGGING;
if (invalid && flagAndAlert("block=" + block.getName() + ", type=" + blockBreak.action) && shouldModifyPackets()) {
didLastFlag = true;
blockBreak.cancel();
} else {
didLastFlag = false;
}
}
}
@@ -1,45 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.math.VectorUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import org.bukkit.util.Vector;
@CheckData(name = "FarBreak", description = "Breaking blocks too far away", experimental = true)
public class FarBreak extends Check implements BlockBreakCheck {
public FarBreak(GrimPlayer player) {
super(player);
}
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (player.gamemode == GameMode.SPECTATOR || player.inVehicle() || blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // falses
double min = Double.MAX_VALUE;
for (double d : player.getPossibleEyeHeights()) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
Vector eyes = new Vector(player.x, player.y + d, player.z);
Vector best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
}
// getPickRange() determines this?
// With 1.20.5+ the new attribute determines creative mode reach using a modifier
double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
if (player.packetStateData.didLastMovementIncludePosition || player.canSkipTicks()) {
double threshold = player.getMovementThreshold();
maxReach += Math.hypot(threshold, threshold);
}
if (min > maxReach * maxReach && flagAndAlert(String.format("distance=%.2f", Math.sqrt(min))) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
}
@@ -1,122 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import java.util.Collections;
import java.util.Set;
// Based loosely off of Hawk BlockBreakSpeedSurvival
// Also based loosely off of NoCheatPlus FastBreak
// Also based off minecraft wiki: https://minecraft.wiki/w/Breaking#Instant_breaking
@CheckData(name = "FastBreak", description = "Breaking blocks too quickly")
public class FastBreak extends Check implements BlockBreakCheck {
// For some reason these states flag and I don't know why.
// Better to just exempt to not annoy legit players.
private static final Set<StateType> EXEMPT_STATES = Collections.singleton(StateTypes.TRIAL_SPAWNER);
public FastBreak(GrimPlayer playerData) {
super(playerData);
}
// The block the player is currently breaking
Vector3i targetBlock = null;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
// The last time a finish digging packet was sent, to enforce 0.3-second delay after non-instabreak
long lastFinishBreak = 0;
// The time the player started to break the block, to know how long the player waited until they finished breaking the block
long startBreak = 0;
// The buffer to this check
double blockBreakBalance = 0;
double blockDelayBalance = 0;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
WrappedBlockState block = blockBreak.block;
// Exempt all blocks that do not exist in the player version
final WrappedBlockState defaultState = WrappedBlockState.getDefaultState(player.getClientVersion(), block.getType());
if (defaultState.getType() == StateTypes.AIR || EXEMPT_STATES.contains(defaultState.getType())) {
return;
}
startBreak = System.currentTimeMillis() - (targetBlock == null ? 50 : 0); // ???
targetBlock = blockBreak.position;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, targetBlock);
double breakDelay = System.currentTimeMillis() - lastFinishBreak;
if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
} else { // Otherwise, increase buffer
blockDelayBalance += 300 - breakDelay;
}
if (blockDelayBalance > 1000) { // If more than a second of advantage
if (flagAndAlert("delay=" + breakDelay + "ms, type=" + blockBreak.block.getType()) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
clampBalance();
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlock != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;
clampBalance();
if (diff < 25) { // Reduce buffer if "close enough"
blockBreakBalance *= 0.9;
} else { // Otherwise, increase buffer
blockBreakBalance += diff;
}
if (blockBreakBalance > 1000) { // If more than a second of advantage
if (flagAndAlert("diff=" + diff + "ms, balance=" + blockBreakBalance + "ms, type=" + blockBreak.block.getType()) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
// also set start time because the breaking netcode is fucked on 1.14.4+
lastFinishBreak = startBreak = System.currentTimeMillis();
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlock != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, targetBlock));
}
}
private void clampBalance() {
double balance = Math.max(1000, (player.getTransactionPing()));
blockBreakBalance = GrimMath.clamp(blockBreakBalance, -balance, balance); // Clamp not Math.max in case other logic changes
blockDelayBalance = GrimMath.clamp(blockDelayBalance, -balance, balance);
}
}
@@ -1,30 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
@CheckData(name = "InvalidBreak", description = "Sent impossible block face id")
public class InvalidBreak extends Check implements BlockBreakCheck {
public InvalidBreak(GrimPlayer player) {
super(player);
}
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.faceId == 255 && blockBreak.action == DiggingAction.CANCELLED_DIGGING && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10)) {
return;
}
if (blockBreak.faceId < 0 || blockBreak.faceId > 5) {
// ban
if (flagAndAlert("face=" + blockBreak.faceId + ", action=" + blockBreak.action) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
}
}
@@ -1,74 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3i;
import java.util.ArrayList;
import java.util.List;
@CheckData(name = "MultiBreak", experimental = true)
public class MultiBreak extends Check implements BlockBreakCheck {
public MultiBreak(GrimPlayer player) {
super(player);
}
private final List<String> flags = new ArrayList<>();
private boolean hasBroken;
private BlockFace lastFace;
private Vector3i lastPos;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
return;
}
if (hasBroken && (blockBreak.face != lastFace || !blockBreak.position.equals(lastPos))) {
final String verbose = "face=" + blockBreak.face + ", lastFace=" + lastFace
+ ", pos=" + MessageUtil.toUnlabledString(blockBreak.position)
+ ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
blockBreak.cancel();
}
} else {
flags.add(verbose);
}
}
lastFace = blockBreak.face;
lastPos = blockBreak.position;
hasBroken = true;
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
hasBroken = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
if (player.isTickingReliablyFor(3)) {
for (String verbose : flags) {
flagAndAlert(verbose);
}
}
flags.clear();
}
}
@@ -1,42 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
@CheckData(name = "NoSwingBreak", description = "Did not swing while breaking block", experimental = true)
public class NoSwingBreak extends Check implements BlockBreakCheck {
public NoSwingBreak(GrimPlayer playerData) {
super(playerData);
}
private boolean sentAnimation;
private boolean sentBreak;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action != DiggingAction.CANCELLED_DIGGING) {
sentBreak = true;
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
sentAnimation = true;
}
if (isTickPacket(event.getPacketType())) {
if (sentBreak && !sentAnimation) {
flagAndAlert();
}
sentAnimation = sentBreak = false;
}
}
}
@@ -1,76 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
@CheckData(name = "PositionBreakA")
public class PositionBreakA extends Check implements BlockBreakCheck {
public PositionBreakA(GrimPlayer player) {
super(player);
}
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (player.inVehicle()
|| blockBreak.action == DiggingAction.CANCELLED_DIGGING
|| blockBreak.block.getType() == StateTypes.REDSTONE_WIRE
) return;
SimpleCollisionBox combined = blockBreak.getCombinedBox();
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
double minEyeHeight = Double.MAX_VALUE;
double maxEyeHeight = Double.MIN_VALUE;
for (double height : possibleEyeHeights) {
minEyeHeight = Math.min(minEyeHeight, height);
maxEyeHeight = Math.max(maxEyeHeight, height);
}
SimpleCollisionBox eyePositions = new SimpleCollisionBox(player.x, player.y + minEyeHeight, player.z, player.x, player.y + maxEyeHeight, player.z);
if (!player.packetStateData.didLastMovementIncludePosition || player.canSkipTicks()) {
eyePositions.expand(player.getMovementThreshold());
}
// If the player is inside a block, then they can ray trace through the block and hit the other side of the block
if (eyePositions.isIntersected(combined)) {
return;
}
// So now we have the player's possible eye positions
// So then look at the face that the player has clicked
boolean flag;
switch (blockBreak.face) {
case NORTH:
flag = eyePositions.minZ > combined.minZ; // Z- face
break;
case SOUTH:
flag = eyePositions.maxZ < combined.maxZ; // Z+ face
break;
case EAST:
flag = eyePositions.maxX < combined.maxX; // X+ face
break;
case WEST:
flag = eyePositions.minX > combined.minX; // X- face
break;
case UP:
flag = eyePositions.maxY < combined.maxY; // Y+ face
break;
case DOWN:
flag = eyePositions.minY > combined.minY; // Y- face
break;
default:
flag = false;
break;
}
if (flag && flagAndAlert("action=" + blockBreak.action + ", face=" + blockBreak.face) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
}
@@ -1,37 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
@CheckData(name = "PositionBreakB", experimental = true)
public class PositionBreakB extends Check implements BlockBreakCheck {
public PositionBreakB(GrimPlayer player) {
super(player);
}
private BlockFace lastFace;
private final int releaseFace = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) ? 0 : 255;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
if (blockBreak.face == lastFace) {
lastFace = null;
}
}
if (lastFace != null) {
flagAndAlert("lastFace=" + lastFace + ", action=" + blockBreak.action);
}
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
lastFace = blockBreak.faceId == releaseFace ? null : blockBreak.face;
}
}
}
@@ -1,120 +0,0 @@
package ac.grim.grimac.checks.impl.breaking;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockBreakCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.nmsutil.Ray;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@CheckData(name = "RotationBreak", experimental = true)
public class RotationBreak extends Check implements BlockBreakCheck {
public RotationBreak(GrimPlayer player) {
super(player);
}
private double flagBuffer = 0; // If the player flags once, force them to play legit, or we will cancel the tick before.
private boolean ignorePost = false;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (player.gamemode == GameMode.SPECTATOR) return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return; // falses
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // falses
if (flagBuffer > 0 && !didRayTraceHit(blockBreak)) {
ignorePost = true;
// If the player hit and has flagged this check recently
if (flagAndAlert("pre-flying, action=" + blockBreak.action) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
}
@Override
public void onPostFlyingBlockBreak(BlockBreak blockBreak) {
if (player.gamemode == GameMode.SPECTATOR) return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return; // falses
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // falses
// Don't flag twice
if (ignorePost) {
ignorePost = false;
return;
}
if (didRayTraceHit(blockBreak)) {
flagBuffer = Math.max(0, flagBuffer - 0.1);
} else {
flagBuffer = 1;
flagAndAlert("post-flying, action=" + blockBreak.action);
}
}
private boolean didRayTraceHit(BlockBreak blockBreak) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
// Start checking if player is in the block
double minEyeHeight = Double.MAX_VALUE;
double maxEyeHeight = Double.MIN_VALUE;
for (double height : possibleEyeHeights) {
minEyeHeight = Math.min(minEyeHeight, height);
maxEyeHeight = Math.max(maxEyeHeight, height);
}
SimpleCollisionBox eyePositions = new SimpleCollisionBox(player.x, player.y + minEyeHeight, player.z, player.x, player.y + maxEyeHeight, player.z);
eyePositions.expand(player.getMovementThreshold());
// If the player is inside a block, then they can ray trace through the block and hit the other side of the block
if (eyePositions.isIntersected(box)) {
return true;
}
// End checking if the player is in the block
List<Vector3f> possibleLookDirs = new ArrayList<>(Arrays.asList(
new Vector3f(player.lastXRot, player.yRot, 0),
new Vector3f(player.xRot, player.yRot, 0)
));
// 1.9+ players could be a tick behind because we don't get skipped ticks
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
possibleLookDirs.add(new Vector3f(player.lastXRot, player.lastYRot, 0));
}
// 1.7 players do not have any of these issues! They are always on the latest look vector
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
possibleLookDirs = Collections.singletonList(new Vector3f(player.xRot, player.yRot, 0));
}
final double distance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
for (double d : possibleEyeHeights) {
for (Vector3f lookDir : possibleLookDirs) {
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
if (intercept.first() != null) return true;
}
}
return false;
}
}
@@ -1,13 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "HitboxBlock", configName = "HitboxBlock", setback = 20)
public class HitboxBlock extends Check implements PacketCheck {
public HitboxBlock(GrimPlayer player) {
super(player);
}
}
@@ -1,13 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "HitboxEntity", configName = "HitboxEntity", setback = 30)
public class HitboxEntity extends Check implements PacketCheck {
public HitboxEntity(GrimPlayer player) {
super(player);
}
}
@@ -1,13 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "HitboxMiss", configName = "HitboxMiss", setback = 10)
public class HitboxMiss extends Check implements PacketCheck {
public HitboxMiss(GrimPlayer player) {
super(player);
}
}
@@ -7,12 +7,13 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import java.util.ArrayList;
@CheckData(name = "MultiInteractA", description = "Interacted with multiple entities in the same tick", experimental = true)
@CheckData(name = "MultiInteractA", experimental = true)
public class MultiInteractA extends Check implements PostPredictionCheck {
public MultiInteractA(final GrimPlayer player) {
super(player);
@@ -33,7 +34,7 @@ public class MultiInteractA extends Check implements PostPredictionCheck {
if (hasInteracted && entity != lastEntity) {
String verbose = "lastEntity=" + lastEntity + ", entity=" + entity
+ ", lastSneaking=" + lastSneaking + ", sneaking=" + sneaking;
if (!player.canSkipTicks()) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -48,21 +49,23 @@ public class MultiInteractA extends Check implements PostPredictionCheck {
hasInteracted = true;
}
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) && !player.packetStateData.lastPacketWasTeleport) {
hasInteracted = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
// we don't need to check pre-1.9 players here (no tick skipping)
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) return;
if (player.isTickingReliablyFor(3)) {
if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) {
for (String verbose : flags) {
flagAndAlert(verbose);
}
}
flags.clear();
hasInteracted = false;
}
}
@@ -8,9 +8,10 @@ import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import java.util.ArrayList;
@@ -35,7 +36,7 @@ public class MultiInteractB extends Check implements PostPredictionCheck {
if (hasInteracted && !pos.equals(lastPos)) {
String verbose = "pos=" + MessageUtil.toUnlabledString(pos) + ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
if (!player.canSkipTicks()) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -49,21 +50,23 @@ public class MultiInteractB extends Check implements PostPredictionCheck {
hasInteracted = true;
}
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) && !player.packetStateData.lastPacketWasTeleport) {
hasInteracted = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
// we don't need to check pre-1.9 players here (no tick skipping)
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) return;
if (player.isTickingReliablyFor(3)) {
if (player.isTickingReliablyFor(3) && !player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0)) {
for (String verbose : flags) {
flagAndAlert(verbose);
}
}
flags.clear();
hasInteracted = false;
}
}
@@ -18,18 +18,10 @@ package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.BlockHitData;
import ac.grim.grimac.utils.data.EntityHitData;
import ac.grim.grimac.utils.data.HitData;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.nmsutil.BlockRayTrace;
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
@@ -39,39 +31,28 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.*;
// You may not copy the check unless you are licensed under GPL
@CheckData(name = "Reach", setback = 10)
@CheckData(name = "Reach", configName = "Reach", setback = 10)
public class Reach extends Check implements PacketCheck {
// Only one flag per reach attack, per entity, per tick.
// We store position because lastX isn't reliable on teleports.
private final Int2ObjectMap<Vector3d> playerAttackQueue = new Int2ObjectOpenHashMap<>();
// Used to prevent falses in the wall hit check
private final Set<Vector3i> blocksChangedThisTick = new HashSet<>();
public static final double extraSearchDistance = 3; // extra distance to raytrace beyond reach distance so we know far beyond the legit distance a cheater hit
private static final List<EntityType> blacklisted = Arrays.asList(
EntityTypes.BOAT,
EntityTypes.CHEST_BOAT,
EntityTypes.SHULKER);
private boolean ignoreNonPlayerTargets;
private boolean cancelImpossibleHits;
public double reachThreshold;
private double threshold;
private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach
public Reach(GrimPlayer player) {
@@ -103,10 +84,6 @@ public class Reach extends Check implements PacketCheck {
return;
}
if (ignoreNonPlayerTargets && !entity.getType().equals(EntityTypes.PLAYER)) {
return;
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
@@ -114,7 +91,7 @@ public class Reach extends Check implements PacketCheck {
if (entity.getType() == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) return;
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR) return;
if (player.inVehicle()) return;
if (player.compensatedEntities.getSelf().inVehicle()) return;
if (entity.riding != null) return;
boolean tooManyAttacks = playerAttackQueue.size() > 10;
@@ -131,9 +108,10 @@ public class Reach extends Check implements PacketCheck {
}
// If the player set their look, or we know they have a new tick
final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType());
if (isUpdate(event.getPacketType())) {
tickBetterReachCheckWithAngle(isFlying);
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) ||
event.getPacketType() == PacketType.Play.Client.PONG ||
event.getPacketType() == PacketType.Play.Client.WINDOW_CONFIRMATION) {
tickBetterReachCheckWithAngle();
}
}
@@ -151,55 +129,38 @@ public class Reach extends Check implements PacketCheck {
return false; // exempt
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR) return false;
if (player.inVehicle()) return false;
if (player.compensatedEntities.getSelf().inVehicle()) return false;
// Filter out what we assume to be cheats
if (cancelBuffer != 0) {
return checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), true) != NONE; // If they flagged
return checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), true) != null; // If they flagged
} else {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.getType() == EntityTypes.END_CRYSTAL) {
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
}
return ReachUtils.getMinReachToBox(player, targetBox) > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
return ReachUtils.getMinReachToBox(player, targetBox) > player.compensatedEntities.getSelf().getAttributeValue(Attributes.PLAYER_ENTITY_INTERACTION_RANGE);
}
}
private void tickBetterReachCheckWithAngle(boolean isFlying) {
private void tickBetterReachCheckWithAngle() {
for (Int2ObjectMap.Entry<Vector3d> attack : playerAttackQueue.int2ObjectEntrySet()) {
PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey());
if (reachEntity == null) continue;
CheckResult result = checkReach(reachEntity, attack.getValue(), false);
String added;
switch (result.type()) {
case REACH:
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : ", type=" + reachEntity.getType().getName().getKey();
flagAndAlert(result.verbose() + added);
break;
case HITBOX:
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getPacketCheck(HitboxMiss.class).flagAndAlert(result.verbose() + added);
break;
case BLOCK:
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getPacketCheck(HitboxBlock.class).flagAndAlert(result.verbose() + added);
break;
case ENTITY:
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getPacketCheck(HitboxEntity.class).flagAndAlert(result.verbose() + added);
break;
if (reachEntity != null) {
String result = checkReach(reachEntity, attack.getValue(), false);
if (result != null) {
if (reachEntity.getType() == EntityTypes.PLAYER) {
flagAndAlert(result);
} else {
flagAndAlert(result + " type=" + reachEntity.getType().getName().getKey());
}
}
}
}
playerAttackQueue.clear();
// We can't use transactions for this because of this problem:
// transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses
if (isFlying) blocksChangedThisTick.clear();
}
@NotNull
private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean isPrediction) {
private String checkReach(PacketEntity reachEntity, Vector3d from, boolean isPrediction) {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.getType() == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
@@ -212,33 +173,41 @@ public class Reach extends Check implements PacketCheck {
targetBox.expand(0.1f);
}
targetBox.expand(reachThreshold);
targetBox.expand(threshold);
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
//
// Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement
// However, on 1.21.2+ we do know if they had 0.03 movement
if (!player.packetStateData.didLastLastMovementIncludePosition || player.canSkipTicks())
if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9))
targetBox.expand(player.getMovementThreshold());
double minDistance = Double.MAX_VALUE;
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
// We only need to check for blocking intersections for these
List<Pair<Vector, Double>> lookVecsAndEyeHeights = new ArrayList<>();
// https://bugs.mojang.com/browse/MC-67665
List<Vector> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot)));
final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
// We raytrace for > the player's reach distance so in the case a player is hacking
// We can return in the flag the distance of the reach hit instead of a generic "player failed reach check"
// +3 would be 3 + extraSearchDistance = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + extraSearchDistance;
// If we are a tick behind, we don't know their next look so don't bother doing this
if (!isPrediction) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.yRot));
// 1.9+ players could be a tick behind because we don't get skipped ticks
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.lastYRot));
}
// 1.7 players do not have any of these issues! They are always on the latest look vector
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot));
}
}
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = player.compensatedEntities.getSelf().getAttributeValue(Attributes.PLAYER_ENTITY_INTERACTION_RANGE) + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final Vector[] possibleLookDirs = player.getPossibleLookVectors(isPrediction);
final Vector eyePos = new Vector(from.getX(), 0, from.getZ());
for (Vector lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
eyePos.setY(from.getY() + eye);
Vector eyePos = new Vector(from.getX(), from.getY() + eye, from.getZ());
Vector endReachPos = eyePos.clone().add(new Vector(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance));
Vector intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
@@ -250,191 +219,29 @@ public class Reach extends Check implements PacketCheck {
if (intercept != null) {
minDistance = Math.min(eyePos.distance(intercept), minDistance);
lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye));
}
}
}
if (hitboxDebuggingEnabled())
sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction);
HitData foundHitData = null;
// If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case)
// Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check.
if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) {
// we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target...
// I'm too lazy to do that and we don't need to optimize that much yet so...
final @Nullable Pair<Double, HitData> hitResult = didRayTraceHit(reachEntity, lookVecsAndEyeHeights, from);
HitData hitData = hitResult.second();
// If the returned hit result was NOT the target entity we flag the check
if (hitData instanceof EntityHitData &&
player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
// until I fix block modeling exempt any blocks changed this tick
} else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).getPosition())) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
}
}
// if the entity is not exempt and the entity is alive
if ((!blacklisted.contains(reachEntity.getType()) && reachEntity.isLivingEntity()) || reachEntity.getType() == EntityTypes.END_CRYSTAL) {
if (minDistance == Double.MIN_VALUE && foundHitData != null) {
if (minDistance == Double.MAX_VALUE) {
cancelBuffer = 1;
if (foundHitData instanceof BlockHitData) {
return new CheckResult(ResultType.BLOCK, "Hit block=" + ((BlockHitData) foundHitData).getState().getType().getName() + " ");
} else { // entity hit data
return new CheckResult(ResultType.ENTITY, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().getType().getName() + " ");
}
} else if (minDistance == Double.MAX_VALUE) {
return "Missed hitbox";
} else if (minDistance > player.compensatedEntities.getSelf().getAttributeValue(Attributes.PLAYER_ENTITY_INTERACTION_RANGE)) {
cancelBuffer = 1;
return new CheckResult(ResultType.HITBOX, "");
} else if (minDistance > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE)) {
cancelBuffer = 1;
return new CheckResult(ResultType.REACH, String.format("%.5f", minDistance) + " blocks");
return String.format("%.5f", minDistance) + " blocks";
} else {
cancelBuffer = Math.max(0, cancelBuffer - 0.25);
}
}
return NONE;
}
private static final CheckResult NONE = new CheckResult(ResultType.NONE, "");
private static class CheckResult {
private final ResultType type;
private final String verbose;
public CheckResult(ResultType type, String verbose) {
this.type = type;
this.verbose = verbose;
}
public ResultType type() {
return type;
}
public String verbose() {
return verbose;
}
public boolean isFlag() {
return type != ResultType.NONE;
}
}
private enum ResultType {
REACH, HITBOX, BLOCK, ENTITY, NONE
}
public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) {
if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this
// Only do this for nearby blocks
if (new Vector(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector(player.x, player.y, player.z)) > 6) return;
// Only do this if the state really had any world impact
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
blocksChangedThisTick.add(vector3i);
}
// Checks if it was possible to hit a target entity
// TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction
// NOTE: It should be impossible for the returned Pair to be null
// because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity
// in previous parts of this check when we didn't check for any obstructions like blocks/entities
private @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(PacketEntity targetEntity,
List<Pair<Vector, Double>> possibleLookVecsAndEyeHeights,
Vector3d from) {
HitData firstObstruction = null;
double firstObstructionDistanceSq = 0;
// Check every possible look direction and every possible eye height
for (Pair<Vector, Double> vectorDoublePair : possibleLookVecsAndEyeHeights) {
Vector lookVec = vectorDoublePair.first();
double eye = vectorDoublePair.second();
Vector eyes = new Vector(from.getX(), from.getY() + eye, from.getZ());
// this function is completely 0.03 aware
final HitData hitResult = BlockRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec);
// If we hit the target entity, it's a valid hit
if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) {
double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation());
return new Pair<>(distanceSquared, hitResult); // Legitimate hit
} else if (hitResult != null && firstObstruction == null) {
// Store the first obstruction only
firstObstruction = hitResult;
firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation());
}
}
// Return the first obstruction if no valid hit found
// Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction
assert firstObstruction != null;
return new Pair<>(firstObstructionDistanceSq, firstObstruction);
}
private boolean hitboxDebuggingEnabled() {
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
}
private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List<Pair<Vector, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
Map<Integer, CollisionBox> hitboxes = new HashMap<>();
for (Int2ObjectMap.Entry<PacketEntity> entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) {
PacketEntity entity = entry.getValue();
if (!entity.canHit()) continue;
CollisionBox box;
if (entity.equals(reachEntity)) {
// Target entity gets expanded hitbox
box = entity.getPossibleCollisionBoxes();
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
sBox.expand(player.checkManager.getPacketCheck(Reach.class).reachThreshold);
// Add movement threshold uncertainty for 1.9+ or non-position updates
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(player.getMovementThreshold());
}
} else {
// Non-target entities
box = entity.getMinimumPossibleCollisionBoxes();
if (box instanceof NoCollisionBox) {
hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE);
continue;
} else if (box instanceof SimpleCollisionBox) {
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
sBox.expand(-player.checkManager.getPacketCheck(Reach.class).reachThreshold);
// Shrink non-target entities by movement threshold when applicable
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(-player.getMovementThreshold());
}
}
}
// Add 1.8 and below extra hitbox size
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& box instanceof SimpleCollisionBox) {
((SimpleCollisionBox) box).expand(0.1f);
}
hitboxes.put(entry.getIntKey(), box);
}
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
lookVecsAndEyeHeights,
new Vector(from.getX(), from.getY(), from.getZ()),
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
return null;
}
@Override
public void onReload(ConfigManager config) {
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true);
this.reachThreshold = config.getDoubleElse("Reach.threshold", 0.0005);
this.threshold = config.getDoubleElse("Reach.threshold", 0.0005);
}
}
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
@CheckData(name = "CrashB", description = "Sent creative mode inventory click packets while not in creative mode")
@CheckData(name = "CrashB")
public class CrashB extends Check implements PacketCheck {
public CrashB(GrimPlayer player) {
super(player);
@@ -8,7 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.world.Location;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "CrashC", description = "Sent non-finite position or rotation")
@CheckData(name = "CrashC")
public class CrashC extends Check implements PacketCheck {
public CrashC(GrimPlayer playerData) {
super(playerData);
@@ -13,7 +13,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
@CheckData(name = "CrashD", description = "Clicking slots in lectern window")
@CheckData(name = "CrashD", experimental = false)
public class CrashD extends Check implements PacketCheck {
public CrashD(GrimPlayer playerData) {
@@ -2,13 +2,14 @@ package ac.grim.grimac.checks.impl.crash;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.impl.exploit.ExploitA;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientSettings;
@CheckData(name = "CrashE")
@CheckData(name = "CrashE", experimental = false)
public class CrashE extends Check implements PacketCheck {
public CrashE(GrimPlayer playerData) {
@@ -20,10 +21,12 @@ public class CrashE extends Check implements PacketCheck {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_SETTINGS) {
WrapperPlayClientSettings wrapper = new WrapperPlayClientSettings(event);
int viewDistance = wrapper.getViewDistance();
boolean invalidLocale = player.checkManager.getPrePredictionCheck(ExploitA.class).checkString(wrapper.getLocale());
if (viewDistance < 2) {
flagAndAlert("distance=" + viewDistance);
wrapper.setViewDistance(2);
}
if (invalidLocale) wrapper.setLocale("en_us");
}
}
@@ -13,7 +13,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPl
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUseItem;
@CheckData(name = "CrashG", description = "Sent negative sequence id")
@CheckData(name = "CrashG")
public class CrashG extends Check implements PacketCheck {
public CrashG(GrimPlayer player) {
@@ -32,12 +32,13 @@ public class CrashH extends Check implements PacketCheck {
}
// paper's patch
final int index;
if (length > 64 && ((index = text.indexOf(' ')) == -1 || index >= 64)) {
if (text.length() > 64 && ((index = text.indexOf(' ')) == -1 || index >= 64)) {
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
flagAndAlert("(invalid) length=" + length);
return;
}
}
}
@@ -1,50 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraA", description = "Started gliding while already gliding", experimental = true)
public class ElytraA extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraA(GrimPlayer player) {
super(player);
}
public void onStartGliding(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (player.isGliding && flagAndAlert()) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_15) && event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
) onStartGliding(event);
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,57 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraB", description = "Started gliding without jumping", experimental = true)
public class ElytraB extends Check implements PostPredictionCheck {
private boolean glide;
private boolean setback;
public ElytraB(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.supportsEndTick()
) {
if (player.packetStateData.knownInput.jump()) {
if (flagAndAlert("no release")) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
} else {
glide = true;
}
}
if (isUpdate(event.getPacketType())) {
if (glide && !player.packetStateData.knownInput.jump() && flagAndAlert("no jump")) {
setback = true;
}
glide = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setback = false;
setbackIfAboveSetbackVL();
}
}
}
@@ -1,76 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraC", description = "Started gliding too frequently", experimental = true)
public class ElytraC extends Check implements PostPredictionCheck {
private boolean glideThisTick, glideLastTick, setback;
private int flags;
public ElytraC(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (player.gamemode == GameMode.SPECTATOR) {
glideThisTick = glideLastTick = false;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION && new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA) {
if (glideThisTick || glideLastTick) {
if (player.canSkipTicks()) {
flags++;
} else {
if (flagAndAlert()) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
}
glideThisTick = true;
}
if (isTickPacket(event.getPacketType())) {
glideLastTick = glideThisTick;
glideThisTick = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (player.canSkipTicks()) {
if (player.isTickingReliablyFor(3)) {
for (; flags > 0; flags--) {
flagAndAlert();
}
}
flags = 0;
setback = false;
}
if (setback) {
setback = false;
setbackIfAboveSetbackVL();
}
}
}
@@ -1,48 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraD", description = "Started gliding without an elytra", experimental = true)
public class ElytraD extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraD(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& !player.canGlide()
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,48 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraE", description = "Started gliding while flying", experimental = true)
public class ElytraE extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraE(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.isFlying
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,48 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraF", description = "Started gliding while on ground", experimental = true)
public class ElytraF extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraF(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.clientClaimsLastOnGround
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,46 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.potion.PotionTypes;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraG", description = "Started gliding with levitation", experimental = true)
public class ElytraG extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraG(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16)
&& player.compensatedEntities.self.hasPotionEffect(PotionTypes.LEVITATION)
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,48 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraH", description = "Started gliding in vehicle", experimental = true)
public class ElytraH extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraH(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
return;
}
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.inVehicle()
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -1,45 +0,0 @@
package ac.grim.grimac.checks.impl.elytra;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraI", description = "Started gliding in water", experimental = true)
public class ElytraI extends Check implements PostPredictionCheck {
private boolean setback;
public ElytraI(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
&& player.wasTouchingWater
&& player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_15)
&& flagAndAlert()
) {
setback = true;
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPose();
}
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
setbackIfAboveSetbackVL();
setback = false;
}
}
}
@@ -4,11 +4,12 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientTabComplete;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatMessage;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientNameItem;
import java.util.regex.Pattern;
@CheckData(name = "ExploitA", experimental = true)
public class ExploitA extends Check implements PacketCheck {
@@ -17,18 +18,35 @@ public class ExploitA extends Check implements PacketCheck {
super(playerData);
}
private static final Pattern EXPLOIT_PATTERN = Pattern.compile("\\$\\{.+}");
/*
WARNING: This doesn't fully patch Log4J! It is highly recommended you update your server
jar to a version that has true Log4J migrations instead of using a plugin. However, this
patch is better than nothing and will still be useful for punishing players who attempt
to abuse this exploit.
*/
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.TAB_COMPLETE && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
WrapperPlayClientTabComplete wrapper = new WrapperPlayClientTabComplete(event);
String text = wrapper.getText();
if (text.equals("/") || text.trim().isEmpty()) {
if (flagAndAlert("")) {
event.setCancelled(true);
player.onPacketCancel();
}
if (event.getPacketType() == PacketType.Play.Client.CHAT_MESSAGE) {
if (true) return; // TODO: Fix PacketEvents pls retrooper
WrapperPlayClientChatMessage wrapper = new WrapperPlayClientChatMessage(event);
String message = wrapper.getMessage();
if (checkString(message)) {
event.setCancelled(true);
player.onPacketCancel();
}
} else if (event.getPacketType() == PacketType.Play.Client.NAME_ITEM) {
WrapperPlayClientNameItem wrapper = new WrapperPlayClientNameItem(event);
String name = wrapper.getItemName();
if (checkString(name)) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
public boolean checkString(String message) {
return EXPLOIT_PATTERN.matcher(message).matches() && flagAndAlert();
}
}
@@ -4,29 +4,31 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEditBook;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientTabComplete;
@CheckData(name = "ExploitB", description = "Too long book title")
@CheckData(name = "ExploitB", experimental = true)
public class ExploitB extends Check implements PacketCheck {
public ExploitB(GrimPlayer playerData) {
super(playerData);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.EDIT_BOOK) {
final String title = new WrapperPlayClientEditBook(event).getTitle();
if (title == null) return; // book was not signed
if (title.length() > 15) {
if (flagAndAlert("title=" + title) && shouldModifyPackets()) {
if (event.getPacketType() == PacketType.Play.Client.TAB_COMPLETE && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
WrapperPlayClientTabComplete wrapper = new WrapperPlayClientTabComplete(event);
String text = wrapper.getText();
if (text.equals("/") || text.trim().length() == 0) {
if (flagAndAlert("")) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -1,56 +0,0 @@
package ac.grim.grimac.checks.impl.exploit;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatCommand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatCommandUnsigned;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatMessage;
// this can false from click events, but I doubt this would actually
// happen unless they're trying to flag, or if the server is set up badly
@CheckData(name = "ExploitC", description = "Invalid chat message")
public class ExploitC extends Check implements PacketCheck {
public ExploitC(GrimPlayer playerData) {
super(playerData);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CHAT_MESSAGE) {
String message = new WrapperPlayClientChatMessage(event).getMessage();
if (message.isEmpty() || !message.trim().equals(message) || message.startsWith("/") && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
if (flagAndAlert("message=" + message)) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
if (event.getPacketType() == PacketType.Play.Client.CHAT_COMMAND_UNSIGNED) {
String command = "/" + new WrapperPlayClientChatCommandUnsigned(event).getCommand();
String strippedCommand = command.replaceAll("\\s+$", ""); // Remove trailing whitespace
if (!strippedCommand.equals(command)) {
if (flagAndAlert("command=" + command)) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
if (event.getPacketType() == PacketType.Play.Client.CHAT_COMMAND) {
String command = "/" + new WrapperPlayClientChatCommand(event).getCommand();
if (!command.trim().equals(command)) {
if (flagAndAlert("command=" + command)) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -17,12 +17,12 @@ import java.util.List;
// Catches NoFalls for LOOK and GROUND packets
// This check runs AFTER the predictions
@CheckData(name = "NoFall", setback = 10)
public class NoFall extends Check implements PacketCheck {
@CheckData(name = "NoFall", configName = "nofall", setback = 10)
public class NoFallA extends Check implements PacketCheck {
public boolean flipPlayerGroundStatus = false;
public NoFall(GrimPlayer player) {
public NoFallA(GrimPlayer player) {
super(player);
}
@@ -42,7 +42,7 @@ public class NoFall extends Check implements PacketCheck {
if (wrapper.isOnGround() && !hasPosition) {
if (!isNearGround(wrapper.isOnGround())) { // If player isn't near ground
// 1.8 boats have a mind on their own... only flag if they're not near a boat or are on 1.9+
if (!GhostBlockDetector.isGhostBlock(player)) flagAndAlertWithSetback();
if (!GhostBlockDetector.isGhostBlock(player) && flagWithSetback()) alert("");
if (shouldModifyPackets()) {
wrapper.setOnGround(false);
event.markForReEncode(true);
@@ -1,42 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open")
public class InventoryA extends InventoryCheck {
public InventoryA(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
if (wrapper.getAction() != InteractAction.ATTACK) return;
// Is not possible to attack while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission())
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -1,35 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open")
public class InventoryB extends InventoryCheck {
public InventoryB(GrimPlayer player) {
super(player);
}
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) {
if (wrapper.getAction() != DiggingAction.START_DIGGING) return;
// Is not possible to start digging a block while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -1,30 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open")
public class InventoryC extends InventoryCheck {
public InventoryC(GrimPlayer player) {
super(player);
}
public void onBlockPlace(final BlockPlace place) {
// It is not possible to place a block while the inventory is open
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
if (shouldModifyPackets()) {
place.resync();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -1,85 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VectorData.MoveVectorData;
import ac.grim.grimac.utils.data.VehicleData;
import java.util.StringJoiner;
@CheckData(name = "InventoryD", setback = 1, decay = 0.25)
public class InventoryD extends InventoryCheck {
private int horseJumpVerbose;
public InventoryD(GrimPlayer player) {
super(player);
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (!predictionComplete.isChecked() ||
predictionComplete.getData().isTeleport() ||
player.getSetbackTeleportUtil().blockOffsets ||
player.packetStateData.lastPacketWasTeleport ||
player.packetStateData.isSlowedByUsingItem() ||
System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) {
return;
}
if (player.hasInventoryOpen) {
boolean inVehicle = player.inVehicle();
boolean isJumping, isMoving;
if (inVehicle) {
VehicleData vehicle = player.vehicleData;
// Will flag once if player open anything with pressed space bar
isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1;
isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0;
} else {
MoveVectorData move = findMovement(player.predictedVelocity);
isJumping = player.predictedVelocity.isJump();
isMoving = move != null && (move.x != 0 || move.z != 0);
}
if (!isMoving && !isJumping) {
reward();
return;
}
if (flag()) {
if (!isNoSetbackPermission())
closeInventory();
StringJoiner joiner = new StringJoiner(" ");
if (isMoving) joiner.add("moving");
if (isJumping) joiner.add("jumping");
if (inVehicle) joiner.add("inVehicle");
alert(joiner.toString());
}
} else {
horseJumpVerbose = 0;
}
}
private MoveVectorData findMovement(VectorData vectorData) {
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
while (vectorData != null) {
vectorData = vectorData.lastVector;
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
}
return null;
}
}
@@ -1,50 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open")
public class InventoryE extends InventoryCheck {
private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID
public InventoryE(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
// It is not possible to change hotbar slots with held item change while the inventory is open
// A container click packet would be sent instead
if (player.hasInventoryOpen) {
if (this.lastTransaction < player.lastTransactionReceived.get()
&& flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.getInventory().needResend = true;
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) {
this.lastTransaction = player.lastTransactionSent.get();
}
}
}
@@ -1,46 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true)
public class InventoryF extends InventoryCheck {
public InventoryF(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted
// once we are ahead of ViaVersion
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
}
@@ -1,40 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true)
public class InventoryG extends InventoryCheck {
public InventoryG(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.lastPacketWasTeleport) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
WrapperPlayClientEntityAction.Action action = wrapper.getAction();
if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING
|| action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
return;
}
if (player.hasInventoryOpen) {
if (flagAndAlert() && !isNoSetbackPermission()) {
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -2,24 +2,19 @@ package ac.grim.grimac.checks.impl.misc;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.impl.exploit.ExploitA;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.configuration.client.WrapperConfigClientPluginMessage;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPluginMessage;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
public class ClientBrand extends Check implements PacketCheck {
@Getter
private String brand = "vanilla";
private boolean hasBrand = false;
String brand = "vanilla";
boolean hasBrand = false;
public ClientBrand(GrimPlayer player) {
super(player);
@@ -29,38 +24,40 @@ public class ClientBrand extends Check implements PacketCheck {
public void onPacketReceive(final PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.PLUGIN_MESSAGE) {
WrapperPlayClientPluginMessage packet = new WrapperPlayClientPluginMessage(event);
handle(packet.getChannelName(), packet.getData());
} else if (event.getPacketType() == PacketType.Configuration.Client.PLUGIN_MESSAGE) {
WrapperConfigClientPluginMessage packet = new WrapperConfigClientPluginMessage(event);
handle(packet.getChannelName(), packet.getData());
String channelName = packet.getChannelName();
handle(channelName, packet.getData());
}
}
public void handle(String channel, byte[] data) {
if (channel.equalsIgnoreCase("minecraft:brand") || // 1.13+
channel.equals("MC|Brand")) { // 1.12
if (data.length > 64 || data.length == 0) {
brand = "sent " + data.length + " bytes as brand";
} else if (!hasBrand) {
byte[] minusLength = new byte[data.length - 1];
System.arraycopy(data, 1, minusLength, 0, minusLength.length);
private void handle(String channel, byte[] data) {
final String expectedChannel = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) ? "minecraft:brand" : "MC|Brand";
if (!channel.equals(expectedChannel)) return;
if (data.length > 64 || data.length == 0) {
brand = "sent " + data.length + " bytes as brand";
} else if (!hasBrand) {
byte[] minusLength = new byte[data.length - 1];
System.arraycopy(data, 1, minusLength, 0, minusLength.length);
brand = new String(minusLength).replace(" (Velocity)", ""); //removes velocity's brand suffix
brand = ChatColor.stripColor(brand); //strip color codes from client brand
if (!GrimAPI.INSTANCE.getConfigManager().isIgnoredClient(brand)) {
String message = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("client-brand-format", "%prefix% &f%player% joined using %brand%");
Component component = MessageUtil.replacePlaceholders(player, MessageUtil.miniMessage(message));
for (Player player : Bukkit.getOnlinePlayers()) {
if (GrimAPI.INSTANCE.getAlertManager().hasBrandsEnabled(player)) {
MessageUtil.sendMessage(player, component);
brand = new String(minusLength).replace(" (Velocity)", ""); //removes velocity's brand suffix
brand = ChatColor.stripColor(brand); //strip color codes from client brand
if (player.checkManager.getPrePredictionCheck(ExploitA.class).checkString(brand)) brand = "sent log4j";
if (!GrimAPI.INSTANCE.getConfigManager().isIgnoredClient(brand)) {
String message = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("client-brand-format", "%prefix% &f%player% joined using %brand%");
message = GrimAPI.INSTANCE.getExternalAPI().replaceVariables(getPlayer(), message, true);
// sendMessage is async safe while broadcast isn't due to adventure
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.hasPermission("grim.brand")) {
player.sendMessage(message);
}
}
}
}
}
hasBrand = true;
hasBrand = true;
}
}
public String getBrand() {
return brand;
}
}
@@ -0,0 +1,155 @@
package ac.grim.grimac.checks.impl.misc;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAcknowledgeBlockChanges;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
// Based loosely off of Hawk BlockBreakSpeedSurvival
// Also based loosely off of NoCheatPlus FastBreak
// Also based off minecraft wiki: https://minecraft.wiki/w/Breaking#Instant_breaking
@CheckData(name = "FastBreak", experimental = false)
public class FastBreak extends Check implements PacketCheck {
public FastBreak(GrimPlayer playerData) {
super(playerData);
}
// The block the player is currently breaking
Vector3i targetBlock = null;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
// The last time a finish digging packet was sent, to enforce 0.3-second delay after non-instabreak
long lastFinishBreak = 0;
// The time the player started to break the block, to know how long the player waited until they finished breaking the block
long startBreak = 0;
// The buffer to this check
double blockBreakBalance = 0;
double blockDelayBalance = 0;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlock != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, targetBlock));
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging digging = new WrapperPlayClientPlayerDigging(event);
final Vector3i blockPosition = digging.getBlockPosition();
if (digging.getAction() == DiggingAction.START_DIGGING) {
WrappedBlockState block = player.compensatedWorld.getWrappedBlockStateAt(blockPosition);
// Exempt all blocks that do not exist in the player version
if (WrappedBlockState.getDefaultState(player.getClientVersion(), block.getType()).getType() == StateTypes.AIR) {
return;
}
startBreak = System.currentTimeMillis() - (targetBlock == null ? 50 : 0); // ???
targetBlock = blockPosition;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, targetBlock);
double breakDelay = System.currentTimeMillis() - lastFinishBreak;
if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
} else { // Otherwise, increase buffer
blockDelayBalance += 300 - breakDelay;
}
if (blockDelayBalance > 1000) { // If more than a second of advantage
flagAndAlert("Delay=" + breakDelay);
if (shouldModifyPackets()) {
event.setCancelled(true); // Cancelling start digging will cause server to reject block break
player.onPacketCancel();
}
}
clampBalance();
}
if (digging.getAction() == DiggingAction.FINISHED_DIGGING && targetBlock != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;
clampBalance();
if (diff < 25) { // Reduce buffer if "close enough"
blockBreakBalance *= 0.9;
} else { // Otherwise, increase buffer
blockBreakBalance += diff;
}
if (blockBreakBalance > 1000) { // If more than a second of advantage
FoliaScheduler.getEntityScheduler().execute(player.bukkitPlayer, GrimAPI.INSTANCE.getPlugin(), () -> {
Player bukkitPlayer = player.bukkitPlayer;
if (bukkitPlayer == null || !bukkitPlayer.isOnline()) return;
if (bukkitPlayer.getLocation().distance(new Location(bukkitPlayer.getWorld(), blockPosition.getX(), blockPosition.getY(), blockPosition.getZ())) < 64) {
final int chunkX = blockPosition.getX() >> 4;
final int chunkZ = blockPosition.getZ() >> 4;
if (!bukkitPlayer.getWorld().isChunkLoaded(chunkX, chunkZ)) return; // Don't load chunks sync
Chunk chunk = bukkitPlayer.getWorld().getChunkAt(chunkX, chunkZ);
Block block = chunk.getBlock(blockPosition.getX() & 15, blockPosition.getY(), blockPosition.getZ() & 15);
int blockId;
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
// Cache this because strings are expensive
blockId = WrappedBlockState.getByString(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), block.getBlockData().getAsString(false)).getGlobalId();
} else {
blockId = (block.getType().getId() << 4) | block.getData();
}
player.user.sendPacket(new WrapperPlayServerBlockChange(blockPosition, blockId));
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) { // Via will handle this for us pre-1.19
player.user.sendPacket(new WrapperPlayServerAcknowledgeBlockChanges(digging.getSequence())); // Make 1.19 clients apply the changes
}
}
}, null, 0);
if (flagAndAlert("Diff=" + diff + ",Balance=" + blockBreakBalance) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
lastFinishBreak = System.currentTimeMillis();
}
}
}
private void clampBalance() {
double balance = Math.max(1000, (player.getTransactionPing()));
blockBreakBalance = GrimMath.clamp(blockBreakBalance, -balance, balance); // Clamp not Math.max in case other logic changes
blockDelayBalance = GrimMath.clamp(blockDelayBalance, -balance, balance);
}
}
@@ -5,9 +5,12 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "TransactionOrder")
@CheckData(name = "TransactionOrder", experimental = false)
public class TransactionOrder extends Check implements PacketCheck {
public TransactionOrder(GrimPlayer player) {
super(player);
}
}
@@ -0,0 +1,17 @@
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "Entity control", configName = "EntityControl")
public class EntityControl extends Check implements PostPredictionCheck {
public EntityControl(GrimPlayer player) {
super(player);
}
public void rewardPlayer() {
reward();
}
}
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.timer;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.checks.CheckData;
@@ -7,10 +7,10 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
@CheckData(name = "NegativeTimer", setback = -1, experimental = true)
public class NegativeTimer extends Timer implements PostPredictionCheck {
@CheckData(name = "NegativeTimer", configName = "NegativeTimer", setback = -1, experimental = true)
public class NegativeTimerCheck extends TimerCheck implements PostPredictionCheck {
public NegativeTimer(GrimPlayer player) {
public NegativeTimerCheck(GrimPlayer player) {
super(player);
timerBalanceRealTime = System.nanoTime() + clockDrift;
}
@@ -18,13 +18,13 @@ public class NegativeTimer extends Timer implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// We can't negative timer check a 1.9+ player who is standing still.
if (player.uncertaintyHandler.lastPointThree.hasOccurredSince(2) || !predictionComplete.isChecked()) {
if (!player.canThePlayerBeCloseToZeroMovement(2) || !predictionComplete.isChecked()) {
timerBalanceRealTime = System.nanoTime() + clockDrift;
}
if (timerBalanceRealTime < lastMovementPlayerClock - clockDrift) {
int lostMS = (int) ((System.nanoTime() - timerBalanceRealTime) / 1e6);
flagAndAlertWithSetback("-" + lostMS);
if (flagAndAlert("-" + lostMS)) setbackIfAboveSetbackVL();
timerBalanceRealTime += 50e6;
}
}
@@ -8,8 +8,8 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "NoSlow", description = "Was not slowed while using an item", setback = 5)
public class NoSlow extends Check implements PostPredictionCheck {
@CheckData(name = "NoSlowA (Prediction)", configName = "NoSlowA", setback = 5)
public class NoSlowA extends Check implements PostPredictionCheck {
double offsetToFlag;
double bestOffset = 1;
// The player sends that they switched items the next tick if they switch from an item that can be used
@@ -17,7 +17,7 @@ public class NoSlow extends Check implements PostPredictionCheck {
public boolean didSlotChangeLastTick = false;
public boolean flaggedLastTick = false;
public NoSlow(GrimPlayer player) {
public NoSlowA(GrimPlayer player) {
super(player);
}
@@ -34,8 +34,8 @@ public class NoSlow extends Check implements PostPredictionCheck {
}
if (bestOffset > offsetToFlag) {
if (flaggedLastTick) {
flagAndAlertWithSetback();
if (flaggedLastTick && flagWithSetback()) {
alert("");
}
flaggedLastTick = true;
} else {
@@ -52,6 +52,6 @@ public class NoSlow extends Check implements PostPredictionCheck {
@Override
public void onReload(ConfigManager config) {
offsetToFlag = config.getDoubleElse(getConfigName() + ".threshold", 0.001);
offsetToFlag = config.getDoubleElse("NoSlowA.threshold", 0.001);
}
}
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.sprint;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -7,10 +7,10 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "SprintA", description = "Sprinting with too low hunger", setback = 0)
public class SprintA extends Check implements PacketCheck {
@CheckData(name = "NoSlowB", setback = 5)
public class NoSlowB extends Check implements PacketCheck {
public SprintA(GrimPlayer player) {
public NoSlowB(GrimPlayer player) {
super(player);
}
@@ -21,15 +21,14 @@ public class SprintA extends Check implements PacketCheck {
if (player.canFly) return;
if (player.food < 6.0F && player.isSprinting) {
if (flagAndAlert()) {
if (flag()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (shouldSetback()) {
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
}
alert("");
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
}
} else {
reward();
@@ -0,0 +1,37 @@
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "NoSlowC", setback = 5, experimental = true)
public class NoSlowC extends Check implements PostPredictionCheck {
public NoSlowC(GrimPlayer player) {
super(player);
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (!predictionComplete.isChecked()) return;
if (player.isSlowMovement) {
ClientVersion client = player.getClientVersion();
// https://bugs.mojang.com/browse/MC-152728
if (client.isNewerThanOrEquals(ClientVersion.V_1_14_2)) {
return;
}
if (player.isSprinting
// you can sneak and swim in 1.13 - 1.14.1
&& (!player.isSwimming || client.isNewerThan(ClientVersion.V_1_14_1) || client.isOlderThan(ClientVersion.V_1_13))
&& player.sneakingSpeedMultiplier < 0.8f
) {
if (flagWithSetback()) alert("");
} else reward();
}
}
}
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.sprint;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -7,9 +7,9 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "SprintC", description = "Sprinting while using an item", setback = 5, experimental = true)
public class SprintC extends Check implements PostPredictionCheck {
public SprintC(GrimPlayer player) {
@CheckData(name = "NoSlowD", setback = 5, experimental = true)
public class NoSlowD extends Check implements PostPredictionCheck {
public NoSlowD(GrimPlayer player) {
super(player);
}
@@ -17,16 +17,16 @@ public class SprintC extends Check implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (player.packetStateData.isSlowedByUsingItem()) {
ClientVersion version = player.getClientVersion();
if (!predictionComplete.isChecked()) return;
if (player.packetStateData.isSlowedByUsingItem()) {
// https://bugs.mojang.com/browse/MC-152728
if (version.isNewerThanOrEquals(ClientVersion.V_1_14_2) && version != ClientVersion.V_1_21_4) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14_2)) {
return;
}
if (player.isSprinting && (!player.wasTouchingWater || version.isOlderThan(ClientVersion.V_1_13))) {
if (flaggedLastTick) flagAndAlertWithSetback();
if (player.isSprinting) {
if (flaggedLastTick && flagWithSetback()) alert("");
flaggedLastTick = true;
} else {
reward();
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.sprint;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
@@ -11,9 +11,9 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEn
import static com.github.retrooper.packetevents.protocol.potion.PotionTypes.BLINDNESS;
@CheckData(name = "SprintD", description = "Started sprinting while having blindness", setback = 5, experimental = true)
public class SprintD extends Check implements PostPredictionCheck {
public SprintD(GrimPlayer player) {
@CheckData(name = "NoSlowE", setback = 5, experimental = true)
public class NoSlowE extends Check implements PostPredictionCheck {
public NoSlowE(GrimPlayer player) {
super(player);
}
@@ -30,9 +30,11 @@ public class SprintD extends Check implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (player.compensatedEntities.self.hasPotionEffect(BLINDNESS)) {
if (!predictionComplete.isChecked()) return;
if (player.compensatedEntities.getSelf().hasPotionEffect(BLINDNESS)) {
if (player.isSprinting && !startedSprintingBeforeBlind) {
flagAndAlertWithSetback();
if (flagWithSetback()) alert("");
} else reward();
}
}
@@ -12,7 +12,7 @@ public class PredictionRunner extends Check implements PositionCheck {
@Override
public void onPositionUpdate(final PositionUpdate positionUpdate) {
if (!player.inVehicle()) {
if (!player.compensatedEntities.getSelf().inVehicle()) {
player.movementCheckRunner.processAndCheckMovementPacket(positionUpdate);
}
}
@@ -32,7 +32,7 @@ public class SetbackBlocker extends Check implements PacketCheck {
}
// Look is the only valid packet to send while in a vehicle
if (player.inVehicle() && event.getPacketType() != PacketType.Play.Client.PLAYER_ROTATION && !player.packetStateData.lastPacketWasTeleport) {
if (player.compensatedEntities.getSelf().inVehicle() && event.getPacketType() != PacketType.Play.Client.PLAYER_ROTATION && !player.packetStateData.lastPacketWasTeleport) {
event.setCancelled(true);
}
@@ -42,7 +42,7 @@ public class SetbackBlocker extends Check implements PacketCheck {
}
// Player is dead
if (player.compensatedEntities.self.isDead) {
if (player.compensatedEntities.getSelf().isDead) {
event.setCancelled(true);
}
}
@@ -53,7 +53,7 @@ public class SetbackBlocker extends Check implements PacketCheck {
}
// Don't let a player move a vehicle when not in a vehicle
if (!player.inVehicle()) {
if (!player.compensatedEntities.getSelf().inVehicle()) {
event.setCancelled(true);
}
@@ -63,7 +63,7 @@ public class SetbackBlocker extends Check implements PacketCheck {
}
// Player is dead
if (player.compensatedEntities.self.isDead) {
if (player.compensatedEntities.getSelf().isDead) {
event.setCancelled(true);
}
}
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.timer;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.checks.Check;
@@ -8,19 +8,20 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
@CheckData(name = "Timer", configName = "TimerA", setback = 10)
public class Timer extends Check implements PacketCheck {
public class TimerCheck extends Check implements PacketCheck {
long timerBalanceRealTime = 0;
// Default value is real time minus max keep-alive time
long knownPlayerClockTime = (long) (System.nanoTime() - 6e10);
long lastMovementPlayerClock = (long) (System.nanoTime() - 6e10);
// How long should the player be able to fall back behind their ping? (nanos)
// How long should the player be able to fall back behind their ping?
// Default: 120 milliseconds
long clockDrift;
// At what ping should we start to limit the balance advantage? (nanos)
long limitAbuseOverPing;
boolean hasGottenMovementAfterTransaction = false;
@@ -52,7 +53,7 @@ public class Timer extends Check implements PacketCheck {
//
// We then take the last transaction before this to increase stability with these lag spikes and
// to guarantee that we are at least 50 ms back before adding the time
public Timer(GrimPlayer player) {
public TimerCheck(GrimPlayer player) {
super(player);
}
@@ -72,18 +73,27 @@ public class Timer extends Check implements PacketCheck {
doCheck(event);
}
public void doCheck(final PacketReceiveEvent event) {
if (timerBalanceRealTime > System.nanoTime()) {
if (flagAndAlert()) {
final double transactionPing = player.getTransactionPing();
// Limit using transaction ping if over 1000ms (default)
final boolean needsAdjustment = limitAbuseOverPing != -1 && transactionPing >= limitAbuseOverPing;
final boolean wouldFailNormal = timerBalanceRealTime > System.nanoTime();
final boolean failsAdjusted = needsAdjustment && (timerBalanceRealTime + ((transactionPing * 1e6) - clockDrift - 50e6)) > System.nanoTime();
if (wouldFailNormal || failsAdjusted) {
if (flag()) {
// Cancel the packet
// Only cancel if not an adjustment setback
if (shouldModifyPackets()) {
if (wouldFailNormal && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (shouldSetback()) {
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
if (isAboveSetbackVl()) player.getSetbackTeleportUtil().executeNonSimulatingSetback();
if (wouldFailNormal) {
// Only alert if we would fail without adjusted limit
alert("");
}
}
@@ -91,12 +101,7 @@ public class Timer extends Check implements PacketCheck {
timerBalanceRealTime -= 50e6;
}
// Limit using transaction ping if over 1000ms (default)
long playerClock = lastMovementPlayerClock;
if (System.nanoTime() - playerClock > limitAbuseOverPing) {
playerClock = System.nanoTime() - limitAbuseOverPing;
}
timerBalanceRealTime = Math.max(timerBalanceRealTime, playerClock - clockDrift);
timerBalanceRealTime = Math.max(timerBalanceRealTime, lastMovementPlayerClock - clockDrift);
}
public boolean checkForTransaction(PacketTypeCommon packetType) {
@@ -106,12 +111,13 @@ public class Timer extends Check implements PacketCheck {
public boolean shouldCountPacketForTimer(PacketTypeCommon packetType) {
// If not flying, or this was a teleport, or this was a duplicate 1.17 mojang stupidity packet
return isTickPacket(packetType);
return WrapperPlayClientPlayerFlying.isFlying(packetType) &&
!player.packetStateData.lastPacketWasTeleport && !player.packetStateData.lastPacketWasOnePointSeventeenDuplicate;
}
@Override
public void onReload(ConfigManager config) {
clockDrift = (long) (config.getDoubleElse(getConfigName() + ".drift", 120.0) * 1e6);
limitAbuseOverPing = (long) (config.getDoubleElse(getConfigName() + ".ping-abuse-limit-threshold", 1000) * 1e6);
limitAbuseOverPing = (long) (config.getDoubleElse(getConfigName() + ".ping-abuse-limit-threshold", 1000));
}
}
@@ -1,4 +1,4 @@
package ac.grim.grimac.checks.impl.timer;
package ac.grim.grimac.checks.impl.movement;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.player.GrimPlayer;
@@ -6,7 +6,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
@CheckData(name = "Timer - Vehicle", configName = "TimerVehicle", setback = 10)
public class VehicleTimer extends Timer {
public class VehicleTimer extends TimerCheck {
boolean isDummy = false;
public VehicleTimer(GrimPlayer player) {
@@ -1,29 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "MultiActionsA", description = "Attacked while using an item", experimental = true)
public class MultiActionsA extends Check implements PacketCheck {
public MultiActionsA(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.eatingHand == InteractionHand.OFF_HAND) && event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -1,41 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import static ac.grim.grimac.events.packets.patch.ResyncWorldUtil.resyncPosition;
@CheckData(name = "MultiActionsB", description = "Breaking blocks while using an item", experimental = true)
public class MultiActionsB extends Check implements PacketCheck {
public MultiActionsB(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.eatingHand == InteractionHand.OFF_HAND) && event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
// this is vanilla on 1.7
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10)) {
return;
}
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
if (packet.getAction() == DiggingAction.START_DIGGING || packet.getAction() == DiggingAction.CANCELLED_DIGGING || packet.getAction() == DiggingAction.FINISHED_DIGGING) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
resyncPosition(player, packet.getBlockPosition());
}
}
}
}
}
@@ -1,40 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while sprinting", experimental = true)
public class MultiActionsC extends Check implements PacketCheck {
public MultiActionsC(GrimPlayer player) {
super(player);
}
private boolean serverOpenedInventoryThisTick;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
if (player.isSprinting && !player.isSwimming && !serverOpenedInventoryThisTick && flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
if (isTickPacket(event.getPacketType())) {
serverOpenedInventoryThisTick = false;
}
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> serverOpenedInventoryThisTick = true);
}
}
}
@@ -1,25 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "MultiActionsD", description = "Closed inventory while sprinting", experimental = true)
public class MultiActionsD extends Check implements PacketCheck {
public MultiActionsD(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
if (player.isSprinting && !player.isSwimming && flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
@@ -1,43 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "MultiActionsE", description = "Swinging while using an item", experimental = true)
public class MultiActionsE extends Check implements PacketCheck {
public MultiActionsE(GrimPlayer player) {
super(player);
}
private boolean dropping;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!dropping && player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.eatingHand == InteractionHand.OFF_HAND) && event.getPacketType() == PacketType.Play.Client.ANIMATION) {
// this is possible to false on 1.7
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10)) {
return;
}
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
dropping = false;
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_15)) {
DiggingAction action = new WrapperPlayClientPlayerDigging(event).getAction();
dropping = action == DiggingAction.DROP_ITEM || action == DiggingAction.DROP_ITEM_STACK;
}
}
}
@@ -1,92 +0,0 @@
package ac.grim.grimac.checks.impl.multiactions;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockPlaceCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import java.util.ArrayList;
import java.util.List;
import static ac.grim.grimac.events.packets.patch.ResyncWorldUtil.resyncPosition;
@CheckData(name = "MultiActionsF", description = "Interacting with a block and an entity in the same tick", experimental = true)
public class MultiActionsF extends BlockPlaceCheck {
public MultiActionsF(GrimPlayer player) {
super(player);
}
private final List<String> flags = new ArrayList<>();
boolean entity, block;
@Override
public void onBlockPlace(BlockPlace place) {
block = true;
if (entity) {
if (!player.canSkipTicks()) {
if (flagAndAlert("place") && shouldModifyPackets() && shouldCancel()) {
place.resync();
}
} else {
flags.add("place");
}
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
entity = true;
if (block) {
if (!player.canSkipTicks()) {
if (flagAndAlert("entity") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add("entity");
}
}
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
if (packet.getAction() == DiggingAction.START_DIGGING || packet.getAction() == DiggingAction.FINISHED_DIGGING) {
block = true;
if (entity) {
if (!player.canSkipTicks()) {
if (flagAndAlert("dig") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
resyncPosition(player, packet.getBlockPosition());
}
} else {
flags.add("dig");
}
}
}
}
if (isTickPacket(event.getPacketType())) {
block = entity = false;
}
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
if (player.isTickingReliablyFor(3)) {
for (String verbose : flags) {
flagAndAlert(verbose);
}
}
flags.clear();
}
}
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff. Show more